Convert monster into a class
FossilOrigin-Name: e8f80b0482846dfbd5bf97ed375b13687d325e9ef7e2a79176dac49722180124
This commit is contained in:
parent
a7db00c740
commit
2c5f2d0342
16 changed files with 319 additions and 224 deletions
|
@ -29,24 +29,10 @@
|
|||
@property (nonatomic) int health, armour, armourType, quadMillis;
|
||||
@property (nonatomic) int gunSelect, gunWait;
|
||||
@property (nonatomic) int lastAction, lastAttackGun, lastMove;
|
||||
@property (nonatomic) bool attacking;
|
||||
@property (readonly, nonatomic) int *ammo;
|
||||
// one of M_* below, M_NONE means human
|
||||
@property (nonatomic) int monsterState;
|
||||
// see monster.m
|
||||
@property (nonatomic) int monsterType;
|
||||
// monster wants to kill this entity
|
||||
@property (nonatomic) DynamicEntity *enemy;
|
||||
// monster wants to look in this direction
|
||||
@property (nonatomic) float targetYaw;
|
||||
@property (nonatomic) bool attacking;
|
||||
// used by physics to signal ai
|
||||
@property (nonatomic) bool blocked, moving;
|
||||
// millis at which transition to another monsterstate takes place
|
||||
@property (nonatomic) int trigger;
|
||||
// delayed attacks
|
||||
@property (nonatomic) OFVector3D attackTarget;
|
||||
// how many times already hit by fellow monster
|
||||
@property (nonatomic) int anger;
|
||||
@property (copy, nonatomic) OFString *name, *team;
|
||||
|
||||
+ (instancetype)entity;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "cube.h"
|
||||
|
||||
#import "Monster.h"
|
||||
|
||||
struct dynent {
|
||||
OFVector3D origin, velocity;
|
||||
float yaw, pitch, roll;
|
||||
|
@ -113,16 +115,8 @@ struct dynent {
|
|||
for (size_t i = 0; i < NUMGUNS; i++)
|
||||
copy->_ammo[i] = _ammo[i];
|
||||
|
||||
copy->_monsterState = _monsterState;
|
||||
copy->_monsterType = _monsterType;
|
||||
copy->_enemy = _enemy;
|
||||
copy->_targetYaw = _targetYaw;
|
||||
copy->_blocked = _blocked;
|
||||
copy->_moving = _moving;
|
||||
copy->_trigger = _trigger;
|
||||
copy->_attackTarget = _attackTarget;
|
||||
copy->_anger = _anger;
|
||||
|
||||
copy->_name = [_name copy];
|
||||
copy->_team = [_team copy];
|
||||
|
||||
|
@ -169,14 +163,18 @@ struct dynent {
|
|||
.lastAttackGun = _lastAttackGun,
|
||||
.lastMove = _lastMove,
|
||||
.attacking = _attacking,
|
||||
.monsterState = _monsterState,
|
||||
.monsterType = _monsterType,
|
||||
.targetYaw = _targetYaw,
|
||||
.blocked = _blocked,
|
||||
.moving = _moving,
|
||||
.trigger = _trigger,
|
||||
.attackTarget = _attackTarget,
|
||||
.anger = _anger };
|
||||
.moving = _moving };
|
||||
|
||||
if ([self isKindOfClass:Monster.class]) {
|
||||
Monster *monster = (Monster *)self;
|
||||
data.monsterState = monster.monsterState;
|
||||
data.monsterType = monster.monsterType;
|
||||
data.targetYaw = monster.targetYaw;
|
||||
data.trigger = monster.trigger;
|
||||
data.attackTarget = monster.attackTarget;
|
||||
data.anger = monster.anger;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUMGUNS; i++)
|
||||
data.ammo[i] = _ammo[i];
|
||||
|
@ -236,14 +234,18 @@ struct dynent {
|
|||
for (int i = 0; i < NUMGUNS; i++)
|
||||
_ammo[i] = d.ammo[i];
|
||||
|
||||
_monsterState = d.monsterState;
|
||||
_monsterType = d.monsterType;
|
||||
_targetYaw = d.targetYaw;
|
||||
_blocked = d.blocked;
|
||||
_moving = d.moving;
|
||||
_trigger = d.trigger;
|
||||
_attackTarget = d.attackTarget;
|
||||
_anger = d.anger;
|
||||
|
||||
if ([self isKindOfClass:Monster.class]) {
|
||||
Monster *monster = (Monster *)self;
|
||||
monster.monsterState = d.monsterState;
|
||||
monster.monsterType = d.monsterType;
|
||||
monster.targetYaw = d.targetYaw;
|
||||
monster.trigger = d.trigger;
|
||||
monster.attackTarget = d.attackTarget;
|
||||
monster.anger = d.anger;
|
||||
}
|
||||
|
||||
_name = [[OFString alloc] initWithUTF8String:d.name];
|
||||
_team = [[OFString alloc] initWithUTF8String:d.team];
|
||||
|
|
39
src/Monster.h
Normal file
39
src/Monster.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
#import "DynamicEntity.h"
|
||||
|
||||
@interface Monster: DynamicEntity
|
||||
@property (class, readonly, nonatomic) OFMutableArray<Monster *> *monsters;
|
||||
// one of M_*
|
||||
@property (nonatomic) int monsterState;
|
||||
// see Monster.m
|
||||
@property (nonatomic) int monsterType;
|
||||
// monster wants to kill this entity
|
||||
@property (nonatomic) DynamicEntity *enemy;
|
||||
// monster wants to look in this direction
|
||||
@property (nonatomic) float targetYaw;
|
||||
// millis at which transition to another monsterstate takes place
|
||||
@property (nonatomic) int trigger;
|
||||
// delayed attacks
|
||||
@property (nonatomic) OFVector3D attackTarget;
|
||||
// how many times already hit by fellow monster
|
||||
@property (nonatomic) int anger;
|
||||
|
||||
// called after map start of when toggling edit mode to reset/spawn all
|
||||
// monsters to initial state
|
||||
+ (void)restoreAll;
|
||||
+ (void)resetAll;
|
||||
+ (void)thinkAll;
|
||||
+ (void)renderAll;
|
||||
// TODO: Move this somewhere else
|
||||
+ (void)endSinglePlayerWithAllKilled:(bool)allKilled;
|
||||
+ (instancetype)monsterWithType:(int)type
|
||||
yaw:(int)yaw
|
||||
state:(int)state
|
||||
trigger:(int)trigger
|
||||
move:(int)move;
|
||||
- (instancetype)initWithType:(int)type
|
||||
yaw:(int)yaw
|
||||
state:(int)state
|
||||
trigger:(int)trigger
|
||||
move:(int)move;
|
||||
- (void)incurDamage:(int)damage fromEntity:(__kindof DynamicEntity *)d;
|
||||
@end
|
|
@ -1,26 +1,45 @@
|
|||
// monster.cpp: implements AI for single player monsters, currently client only
|
||||
|
||||
#import "Monster.h"
|
||||
|
||||
#include "cube.h"
|
||||
|
||||
#import "DynamicEntity.h"
|
||||
#import "Entity.h"
|
||||
|
||||
static OFMutableArray<DynamicEntity *> *monsters;
|
||||
static OFMutableArray<Monster *> *monsters;
|
||||
static int nextmonster, spawnremain, numkilled, monstertotal, mtimestart;
|
||||
|
||||
VARF(skill, 1, 3, 10, conoutf(@"skill is now %d", skill));
|
||||
@implementation Monster
|
||||
+ (void)initialize
|
||||
{
|
||||
monsters = [[OFMutableArray alloc] init];
|
||||
}
|
||||
|
||||
OFArray<DynamicEntity *> *
|
||||
getmonsters()
|
||||
+ (OFMutableArray<Monster *> *)monsters
|
||||
{
|
||||
return monsters;
|
||||
}
|
||||
|
||||
// for savegames
|
||||
void
|
||||
restoremonsterstate()
|
||||
+ (instancetype)monsterWithType:(int)type
|
||||
yaw:(int)yaw
|
||||
state:(int)state
|
||||
trigger:(int)trigger
|
||||
move:(int)move
|
||||
{
|
||||
for (DynamicEntity *monster in monsters)
|
||||
return [[self alloc] initWithType:type
|
||||
yaw:yaw
|
||||
state:state
|
||||
trigger:trigger
|
||||
move:move];
|
||||
}
|
||||
|
||||
VARF(skill, 1, 3, 10, conoutf(@"skill is now %d", skill));
|
||||
|
||||
// for savegames
|
||||
+ (void)restoreAll
|
||||
{
|
||||
for (Monster *monster in monsters)
|
||||
if (monster.state == CS_DEAD)
|
||||
numkilled++;
|
||||
}
|
||||
|
@ -55,47 +74,67 @@ monstertypes[NUMMONSTERTYPES] = {
|
|||
@"a goblin", @"monster/goblin" },
|
||||
};
|
||||
|
||||
DynamicEntity *
|
||||
basicmonster(int type, int yaw, int state, int trigger, int move)
|
||||
- (instancetype)initWithType:(int)type
|
||||
yaw:(int)yaw
|
||||
state:(int)state
|
||||
trigger:(int)trigger
|
||||
move:(int)move
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (type >= NUMMONSTERTYPES) {
|
||||
conoutf(@"warning: unknown monster in spawn: %d", type);
|
||||
type = 0;
|
||||
}
|
||||
DynamicEntity *m = [DynamicEntity entity];
|
||||
struct monstertype *t = &monstertypes[(m.monsterType = type)];
|
||||
m.eyeHeight = 2.0f;
|
||||
m.aboveEye = 1.9f;
|
||||
m.radius *= t->bscale / 10.0f;
|
||||
m.eyeHeight *= t->bscale / 10.0f;
|
||||
m.aboveEye *= t->bscale / 10.0f;
|
||||
m.monsterState = state;
|
||||
|
||||
struct monstertype *t = &monstertypes[(self.monsterType = type)];
|
||||
self.eyeHeight = 2.0f;
|
||||
self.aboveEye = 1.9f;
|
||||
self.radius *= t->bscale / 10.0f;
|
||||
self.eyeHeight *= t->bscale / 10.0f;
|
||||
self.aboveEye *= t->bscale / 10.0f;
|
||||
self.monsterState = state;
|
||||
|
||||
if (state != M_SLEEP)
|
||||
spawnplayer(m);
|
||||
m.trigger = lastmillis + trigger;
|
||||
m.targetYaw = m.yaw = (float)yaw;
|
||||
m.move = move;
|
||||
m.enemy = player1;
|
||||
m.gunSelect = t->gun;
|
||||
m.maxSpeed = (float)t->speed;
|
||||
m.health = t->health;
|
||||
m.armour = 0;
|
||||
loopi(NUMGUNS) m.ammo[i] = 10000;
|
||||
m.pitch = 0;
|
||||
m.roll = 0;
|
||||
m.state = CS_ALIVE;
|
||||
m.anger = 0;
|
||||
m.name = t->name;
|
||||
spawnplayer(self);
|
||||
|
||||
if (monsters == nil)
|
||||
monsters = [[OFMutableArray alloc] init];
|
||||
self.trigger = lastmillis + trigger;
|
||||
self.targetYaw = self.yaw = (float)yaw;
|
||||
self.move = move;
|
||||
self.enemy = player1;
|
||||
self.gunSelect = t->gun;
|
||||
self.maxSpeed = (float)t->speed;
|
||||
self.health = t->health;
|
||||
self.armour = 0;
|
||||
|
||||
[monsters addObject:m];
|
||||
for (size_t i = 0; i < NUMGUNS; i++)
|
||||
self.ammo[i] = 10000;
|
||||
|
||||
return m;
|
||||
self.pitch = 0;
|
||||
self.roll = 0;
|
||||
self.state = CS_ALIVE;
|
||||
self.anger = 0;
|
||||
self.name = t->name;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
- (id)copy
|
||||
{
|
||||
Monster *copy = [super copy];
|
||||
|
||||
copy->_monsterState = _monsterState;
|
||||
copy->_monsterType = _monsterType;
|
||||
copy->_enemy = _enemy;
|
||||
copy->_targetYaw = _targetYaw;
|
||||
copy->_trigger = _trigger;
|
||||
copy->_attackTarget = _attackTarget;
|
||||
copy->_anger = _anger;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
static void
|
||||
spawnmonster() // spawn a random monster according to freq distribution in DMSP
|
||||
{
|
||||
int n = rnd(TOTMFREQ), type;
|
||||
|
@ -105,18 +144,22 @@ spawnmonster() // spawn a random monster according to freq distribution in DMSP
|
|||
break;
|
||||
}
|
||||
}
|
||||
basicmonster(type, rnd(360), M_SEARCH, 1000, 1);
|
||||
|
||||
[monsters addObject:[Monster monsterWithType:type
|
||||
yaw:rnd(360)
|
||||
state:M_SEARCH
|
||||
trigger:1000
|
||||
move:1]];
|
||||
}
|
||||
|
||||
// called after map start of when toggling edit mode to reset/spawn all
|
||||
// monsters to initial state
|
||||
void
|
||||
monsterclear()
|
||||
+ (void)resetAll
|
||||
{
|
||||
[monsters removeAllObjects];
|
||||
|
||||
numkilled = 0;
|
||||
monstertotal = 0;
|
||||
spawnremain = 0;
|
||||
|
||||
if (m_dmsp) {
|
||||
nextmonster = mtimestart = lastmillis + 10000;
|
||||
monstertotal = spawnremain = gamemode < 0 ? skill * 10 : 0;
|
||||
|
@ -127,9 +170,13 @@ monsterclear()
|
|||
if (e.type != MONSTER)
|
||||
continue;
|
||||
|
||||
DynamicEntity *m =
|
||||
basicmonster(e.attr2, e.attr1, M_SLEEP, 100, 0);
|
||||
Monster *m = [Monster monsterWithType:e.attr2
|
||||
yaw:e.attr1
|
||||
state:M_SLEEP
|
||||
trigger:100
|
||||
move:0];
|
||||
m.origin = OFMakeVector3D(e.x, e.y, e.z);
|
||||
[monsters addObject:m];
|
||||
entinmap(m);
|
||||
monstertotal++;
|
||||
}
|
||||
|
@ -137,7 +184,7 @@ monsterclear()
|
|||
}
|
||||
|
||||
// height-correct line of sight for monster shooting/seeing
|
||||
bool
|
||||
static bool
|
||||
los(float lx, float ly, float lz, float bx, float by, float bz, OFVector3D *v)
|
||||
{
|
||||
if (OUTBORD((int)lx, (int)ly) || OUTBORD((int)bx, (int)by))
|
||||
|
@ -173,8 +220,8 @@ los(float lx, float ly, float lz, float bx, float by, float bz, OFVector3D *v)
|
|||
return i >= steps;
|
||||
}
|
||||
|
||||
bool
|
||||
enemylos(DynamicEntity *m, OFVector3D *v)
|
||||
static bool
|
||||
enemylos(Monster *m, OFVector3D *v)
|
||||
{
|
||||
*v = m.origin;
|
||||
return los(m.origin.x, m.origin.y, m.origin.z, m.enemy.origin.x,
|
||||
|
@ -189,90 +236,91 @@ enemylos(DynamicEntity *m, OFVector3D *v)
|
|||
// decision making means tougher AI.
|
||||
|
||||
// n = at skill 0, n/2 = at skill 10, r = added random factor
|
||||
void
|
||||
transition(DynamicEntity *m, int state, int moving, int n, int r)
|
||||
- (void)transitionWithState:(int)state moving:(int)moving n:(int)n r:(int)r
|
||||
{
|
||||
m.monsterState = state;
|
||||
m.move = moving;
|
||||
self.monsterState = state;
|
||||
self.move = moving;
|
||||
n = n * 130 / 100;
|
||||
m.trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1);
|
||||
self.trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1);
|
||||
}
|
||||
|
||||
void
|
||||
normalise(DynamicEntity *m, float angle)
|
||||
- (void)normalizeWithAngle:(float)angle
|
||||
{
|
||||
while (m.yaw < angle - 180.0f)
|
||||
m.yaw += 360.0f;
|
||||
while (m.yaw > angle + 180.0f)
|
||||
m.yaw -= 360.0f;
|
||||
while (self.yaw < angle - 180.0f)
|
||||
self.yaw += 360.0f;
|
||||
while (self.yaw > angle + 180.0f)
|
||||
self.yaw -= 360.0f;
|
||||
}
|
||||
|
||||
// main AI thinking routine, called every frame for every monster
|
||||
void
|
||||
monsteraction(DynamicEntity *m)
|
||||
- (void)performAction
|
||||
{
|
||||
if (m.enemy.state == CS_DEAD) {
|
||||
m.enemy = player1;
|
||||
m.anger = 0;
|
||||
if (self.enemy.state == CS_DEAD) {
|
||||
self.enemy = player1;
|
||||
self.anger = 0;
|
||||
}
|
||||
normalise(m, m.targetYaw);
|
||||
[self normalizeWithAngle:self.targetYaw];
|
||||
// slowly turn monster towards his target
|
||||
if (m.targetYaw > m.yaw) {
|
||||
m.yaw += curtime * 0.5f;
|
||||
if (m.targetYaw < m.yaw)
|
||||
m.yaw = m.targetYaw;
|
||||
if (self.targetYaw > self.yaw) {
|
||||
self.yaw += curtime * 0.5f;
|
||||
if (self.targetYaw < self.yaw)
|
||||
self.yaw = self.targetYaw;
|
||||
} else {
|
||||
m.yaw -= curtime * 0.5f;
|
||||
if (m.targetYaw > m.yaw)
|
||||
m.yaw = m.targetYaw;
|
||||
self.yaw -= curtime * 0.5f;
|
||||
if (self.targetYaw > self.yaw)
|
||||
self.yaw = self.targetYaw;
|
||||
}
|
||||
|
||||
vdist(disttoenemy, vectoenemy, m.origin, m.enemy.origin);
|
||||
m.pitch = atan2(m.enemy.origin.z - m.origin.z, disttoenemy) * 180 / PI;
|
||||
vdist(disttoenemy, vectoenemy, self.origin, self.enemy.origin);
|
||||
self.pitch =
|
||||
atan2(self.enemy.origin.z - self.origin.z, disttoenemy) * 180 / PI;
|
||||
|
||||
// special case: if we run into scenery
|
||||
if (m.blocked) {
|
||||
m.blocked = false;
|
||||
if (self.blocked) {
|
||||
self.blocked = false;
|
||||
// try to jump over obstackle (rare)
|
||||
if (!rnd(20000 / monstertypes[m.monsterType].speed))
|
||||
m.jumpNext = true;
|
||||
if (!rnd(20000 / monstertypes[self.monsterType].speed))
|
||||
self.jumpNext = true;
|
||||
// search for a way around (common)
|
||||
else if (m.trigger < lastmillis &&
|
||||
(m.monsterState != M_HOME || !rnd(5))) {
|
||||
else if (self.trigger < lastmillis &&
|
||||
(self.monsterState != M_HOME || !rnd(5))) {
|
||||
// patented "random walk" AI pathfinding (tm) ;)
|
||||
m.targetYaw += 180 + rnd(180);
|
||||
transition(m, M_SEARCH, 1, 400, 1000);
|
||||
self.targetYaw += 180 + rnd(180);
|
||||
[self transitionWithState:M_SEARCH
|
||||
moving:1
|
||||
n:400
|
||||
r:1000];
|
||||
}
|
||||
}
|
||||
|
||||
float enemyYaw = -(float)atan2(m.enemy.origin.x - m.origin.x,
|
||||
m.enemy.origin.y - m.origin.y) /
|
||||
float enemyYaw = -(float)atan2(self.enemy.origin.x - self.origin.x,
|
||||
self.enemy.origin.y - self.origin.y) /
|
||||
PI * 180 +
|
||||
180;
|
||||
|
||||
switch (m.monsterState) {
|
||||
switch (self.monsterState) {
|
||||
case M_PAIN:
|
||||
case M_ATTACKING:
|
||||
case M_SEARCH:
|
||||
if (m.trigger < lastmillis)
|
||||
transition(m, M_HOME, 1, 100, 200);
|
||||
if (self.trigger < lastmillis)
|
||||
[self transitionWithState:M_HOME moving:1 n:100 r:200];
|
||||
break;
|
||||
|
||||
case M_SLEEP: // state classic sp monster start in, wait for visual
|
||||
// contact
|
||||
{
|
||||
OFVector3D target;
|
||||
if (editmode || !enemylos(m, &target))
|
||||
if (editmode || !enemylos(self, &target))
|
||||
return; // skip running physics
|
||||
normalise(m, enemyYaw);
|
||||
float angle = (float)fabs(enemyYaw - m.yaw);
|
||||
[self normalizeWithAngle:enemyYaw];
|
||||
float angle = (float)fabs(enemyYaw - self.yaw);
|
||||
if (disttoenemy < 8 // the better the angle to the player, the
|
||||
// further the monster can see/hear
|
||||
|| (disttoenemy < 16 && angle < 135) ||
|
||||
(disttoenemy < 32 && angle < 90) ||
|
||||
(disttoenemy < 64 && angle < 45) || angle < 10) {
|
||||
transition(m, M_HOME, 1, 500, 200);
|
||||
OFVector3D loc = m.origin;
|
||||
[self transitionWithState:M_HOME moving:1 n:500 r:200];
|
||||
OFVector3D loc = self.origin;
|
||||
playsound(S_GRUNT1 + rnd(2), &loc);
|
||||
}
|
||||
break;
|
||||
|
@ -281,90 +329,109 @@ monsteraction(DynamicEntity *m)
|
|||
case M_AIMING:
|
||||
// this state is the delay between wanting to shoot and actually
|
||||
// firing
|
||||
if (m.trigger < lastmillis) {
|
||||
m.lastAction = 0;
|
||||
m.attacking = true;
|
||||
OFVector3D attackTarget = m.attackTarget;
|
||||
shoot(m, &attackTarget);
|
||||
transition(m, M_ATTACKING, 0, 600, 0);
|
||||
if (self.trigger < lastmillis) {
|
||||
self.lastAction = 0;
|
||||
self.attacking = true;
|
||||
OFVector3D attackTarget = self.attackTarget;
|
||||
shoot(self, &attackTarget);
|
||||
[self transitionWithState:M_ATTACKING
|
||||
moving:0
|
||||
n:600
|
||||
r:0];
|
||||
}
|
||||
break;
|
||||
|
||||
case M_HOME:
|
||||
// monster has visual contact, heads straight for player and
|
||||
// may want to shoot at any time
|
||||
m.targetYaw = enemyYaw;
|
||||
if (m.trigger < lastmillis) {
|
||||
self.targetYaw = enemyYaw;
|
||||
if (self.trigger < lastmillis) {
|
||||
OFVector3D target;
|
||||
if (!enemylos(m, &target)) {
|
||||
if (!enemylos(self, &target)) {
|
||||
// no visual contact anymore, let monster get
|
||||
// as close as possible then search for player
|
||||
transition(m, M_HOME, 1, 800, 500);
|
||||
[self transitionWithState:M_HOME
|
||||
moving:1
|
||||
n:800
|
||||
r:500];
|
||||
} else {
|
||||
// the closer the monster is the more likely he
|
||||
// wants to shoot
|
||||
if (!rnd((int)disttoenemy / 3 + 1) &&
|
||||
m.enemy.state == CS_ALIVE) {
|
||||
self.enemy.state == CS_ALIVE) {
|
||||
// get ready to fire
|
||||
m.attackTarget = target;
|
||||
transition(m, M_AIMING, 0,
|
||||
monstertypes[m.monsterType].lag,
|
||||
10);
|
||||
} else
|
||||
self.attackTarget = target;
|
||||
int n =
|
||||
monstertypes[self.monsterType].lag;
|
||||
[self transitionWithState:M_AIMING
|
||||
moving:0
|
||||
n:n
|
||||
r:10];
|
||||
} else {
|
||||
// track player some more
|
||||
transition(m, M_HOME, 1,
|
||||
monstertypes[m.monsterType].rate,
|
||||
0);
|
||||
int n =
|
||||
monstertypes[self.monsterType].rate;
|
||||
[self transitionWithState:M_HOME
|
||||
moving:1
|
||||
n:n
|
||||
r:0];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
moveplayer(m, 1, false); // use physics to move monster
|
||||
moveplayer(self, 1, false); // use physics to move monster
|
||||
}
|
||||
|
||||
void
|
||||
monsterpain(DynamicEntity *m, int damage, DynamicEntity *d)
|
||||
- (void)incurDamage:(int)damage fromEntity:(__kindof DynamicEntity *)d
|
||||
{
|
||||
// a monster hit us
|
||||
if (d.monsterState) {
|
||||
if ([d isKindOfClass:Monster.class]) {
|
||||
Monster *m = (Monster *)d;
|
||||
|
||||
// guard for RL guys shooting themselves :)
|
||||
if (m != d) {
|
||||
if (self != m) {
|
||||
// don't attack straight away, first get angry
|
||||
m.anger++;
|
||||
int anger = m.monsterType == d.monsterType ? m.anger / 2
|
||||
: m.anger;
|
||||
if (anger >= monstertypes[m.monsterType].loyalty)
|
||||
self.anger++;
|
||||
int anger =
|
||||
(self.monsterType == m.monsterType ? self.anger / 2
|
||||
: self.anger);
|
||||
if (anger >= monstertypes[self.monsterType].loyalty)
|
||||
// monster infight if very angry
|
||||
m.enemy = d;
|
||||
self.enemy = m;
|
||||
}
|
||||
} else {
|
||||
// player hit us
|
||||
m.anger = 0;
|
||||
m.enemy = d;
|
||||
self.anger = 0;
|
||||
self.enemy = d;
|
||||
}
|
||||
|
||||
// in this state monster won't attack
|
||||
transition(m, M_PAIN, 0, monstertypes[m.monsterType].pain, 200);
|
||||
if ((m.health -= damage) <= 0) {
|
||||
m.state = CS_DEAD;
|
||||
m.lastAction = lastmillis;
|
||||
[self transitionWithState:M_PAIN
|
||||
moving:0
|
||||
n:monstertypes[self.monsterType].pain
|
||||
r:200];
|
||||
|
||||
if ((self.health -= damage) <= 0) {
|
||||
self.state = CS_DEAD;
|
||||
self.lastAction = lastmillis;
|
||||
numkilled++;
|
||||
player1.frags = numkilled;
|
||||
OFVector3D loc = m.origin;
|
||||
playsound(monstertypes[m.monsterType].diesound, &loc);
|
||||
OFVector3D loc = self.origin;
|
||||
playsound(monstertypes[self.monsterType].diesound, &loc);
|
||||
int remain = monstertotal - numkilled;
|
||||
if (remain > 0 && remain <= 5)
|
||||
conoutf(@"only %d monster(s) remaining", remain);
|
||||
} else {
|
||||
OFVector3D loc = m.origin;
|
||||
playsound(monstertypes[m.monsterType].painsound, &loc);
|
||||
OFVector3D loc = self.origin;
|
||||
playsound(monstertypes[self.monsterType].painsound, &loc);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
endsp(bool allkilled)
|
||||
+ (void)endSinglePlayerWithAllKilled:(bool)allKilled
|
||||
{
|
||||
conoutf(allkilled ? @"you have cleared the map!"
|
||||
conoutf(allKilled ? @"you have cleared the map!"
|
||||
: @"you reached the exit!");
|
||||
conoutf(@"score: %d kills in %d seconds", numkilled,
|
||||
(lastmillis - mtimestart) / 1000);
|
||||
|
@ -372,8 +439,7 @@ endsp(bool allkilled)
|
|||
startintermission();
|
||||
}
|
||||
|
||||
void
|
||||
monsterthink()
|
||||
+ (void)thinkAll
|
||||
{
|
||||
if (m_dmsp && spawnremain && lastmillis > nextmonster) {
|
||||
if (spawnremain-- == monstertotal)
|
||||
|
@ -383,7 +449,7 @@ monsterthink()
|
|||
}
|
||||
|
||||
if (monstertotal && !spawnremain && numkilled == monstertotal)
|
||||
endsp(true);
|
||||
[self endSinglePlayerWithAllKilled:true];
|
||||
|
||||
// equivalent of player entity touch, but only teleports are used
|
||||
[ents enumerateObjectsUsingBlock:^(Entity *e, size_t i, bool *stop) {
|
||||
|
@ -395,7 +461,7 @@ monsterthink()
|
|||
|
||||
OFVector3D v =
|
||||
OFMakeVector3D(e.x, e.y, (float)S(e.x, e.y)->floor);
|
||||
for (DynamicEntity *monster in monsters) {
|
||||
for (Monster *monster in monsters) {
|
||||
if (monster.state == CS_DEAD) {
|
||||
if (lastmillis - monster.lastAction < 2000) {
|
||||
monster.move = 0;
|
||||
|
@ -412,17 +478,17 @@ monsterthink()
|
|||
}
|
||||
}];
|
||||
|
||||
for (DynamicEntity *monster in monsters)
|
||||
for (Monster *monster in monsters)
|
||||
if (monster.state == CS_ALIVE)
|
||||
monsteraction(monster);
|
||||
[monster performAction];
|
||||
}
|
||||
|
||||
void
|
||||
monsterrender()
|
||||
+ (void)renderAll
|
||||
{
|
||||
for (DynamicEntity *monster in monsters)
|
||||
for (Monster *monster in monsters)
|
||||
renderclient(monster, false,
|
||||
monstertypes[monster.monsterType].mdlname,
|
||||
monster.monsterType == 5,
|
||||
monstertypes[monster.monsterType].mscale / 10.0f);
|
||||
}
|
||||
@end
|
|
@ -3,6 +3,7 @@
|
|||
#include "cube.h"
|
||||
|
||||
#import "DynamicEntity.h"
|
||||
#import "Monster.h"
|
||||
|
||||
// render players & monsters
|
||||
// very messy ad-hoc handling of animation frames, should be made more
|
||||
|
@ -53,9 +54,11 @@ renderclient(
|
|||
n = 16;
|
||||
} else if (d.state == CS_LAGGED) {
|
||||
n = 17;
|
||||
} else if (d.monsterState == M_ATTACKING) {
|
||||
} else if ([d isKindOfClass:Monster.class] &&
|
||||
((Monster *)d).monsterState == M_ATTACKING) {
|
||||
n = 8;
|
||||
} else if (d.monsterState == M_PAIN) {
|
||||
} else if ([d isKindOfClass:Monster.class] &&
|
||||
((Monster *)d).monsterState == M_PAIN) {
|
||||
n = 10;
|
||||
} else if ((!d.move && !d.strafe) || !d.moving) {
|
||||
n = 12;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#import "DynamicEntity.h"
|
||||
#import "Entity.h"
|
||||
#import "Monster.h"
|
||||
#import "OFString+Cube.h"
|
||||
|
||||
int nextmode = 0; // nextmode becomes gamemode after next map load
|
||||
|
@ -174,7 +175,7 @@ updateworld(int millis) // main game update loop
|
|||
}
|
||||
otherplayers();
|
||||
if (!demoplayback) {
|
||||
monsterthink();
|
||||
[Monster thinkAll];
|
||||
if (player1.state == CS_DEAD) {
|
||||
if (lastmillis - player1.lastAction < 2000) {
|
||||
player1.move = player1.strafe = 0;
|
||||
|
@ -417,7 +418,7 @@ startmap(OFString *name) // called just after a map load
|
|||
conoutf(@"coop sp not supported yet");
|
||||
}
|
||||
sleepwait = 0;
|
||||
monsterclear();
|
||||
[Monster resetAll];
|
||||
projreset();
|
||||
spawncycle = -1;
|
||||
spawnplayer(player1);
|
||||
|
|
|
@ -126,7 +126,6 @@ enum {
|
|||
|
||||
enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off
|
||||
enum {
|
||||
M_NONE = 0,
|
||||
M_SEARCH,
|
||||
M_HOME,
|
||||
M_ATTACKING,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "cube.h"
|
||||
|
||||
#import "DynamicEntity.h"
|
||||
#import "Monster.h"
|
||||
#import "OFString+Cube.h"
|
||||
|
||||
bool editmode = false;
|
||||
|
@ -68,8 +69,8 @@ toggleedit()
|
|||
// edited
|
||||
player1.health = 100;
|
||||
if (m_classicsp)
|
||||
monsterclear(); // all monsters back at their spawns for
|
||||
// editing
|
||||
// all monsters back at their spawns for editing
|
||||
[Monster resetAll];
|
||||
projreset();
|
||||
}
|
||||
Cube.sharedInstance.repeatsKeys = editmode;
|
||||
|
|
|
@ -13,6 +13,7 @@ executable('client',
|
|||
'MapModelInfo.m',
|
||||
'Menu.m',
|
||||
'MenuItem.m',
|
||||
'Monster.m',
|
||||
'OFString+Cube.m',
|
||||
'Projectile.m',
|
||||
'ResolverResult.m',
|
||||
|
@ -30,7 +31,6 @@ executable('client',
|
|||
'entities.m',
|
||||
'init.m',
|
||||
'menus.m',
|
||||
'monster.m',
|
||||
'physics.m',
|
||||
'rendercubes.m',
|
||||
'renderextras.m',
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#import "DynamicEntity.h"
|
||||
#import "Entity.h"
|
||||
#import "MapModelInfo.h"
|
||||
#import "Monster.h"
|
||||
|
||||
// collide with player or monster
|
||||
static bool
|
||||
|
@ -29,9 +30,9 @@ plcollide(
|
|||
|
||||
if (fabs(o.origin.z - d.origin.z) < o.aboveEye + d.eyeHeight)
|
||||
return false;
|
||||
if (d.monsterState)
|
||||
if ([d isKindOfClass:Monster.class])
|
||||
return false; // hack
|
||||
//
|
||||
|
||||
*headspace = d.origin.z - o.origin.z - o.aboveEye - d.eyeHeight;
|
||||
if (*headspace < 0)
|
||||
*headspace = 10;
|
||||
|
@ -105,9 +106,10 @@ collide(DynamicEntity *d, bool spawn, float drop, float rise)
|
|||
const int y2 = fast_f2nat(fy2);
|
||||
float hi = 127, lo = -128;
|
||||
// big monsters are afraid of heights, unless angry :)
|
||||
float minfloor = (d.monsterState && !spawn && d.health > 100)
|
||||
float minfloor =
|
||||
([d isKindOfClass:Monster.class] && !spawn && d.health > 100
|
||||
? d.origin.z - d.eyeHeight - 4.5f
|
||||
: -1000.0f;
|
||||
: -1000.0f);
|
||||
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
|
@ -183,7 +185,7 @@ collide(DynamicEntity *d, bool spawn, float drop, float rise)
|
|||
|
||||
// this loop can be a performance bottleneck with many monster on a slow
|
||||
// cpu, should replace with a blockmap but seems mostly fast enough
|
||||
for (DynamicEntity *monster in getmonsters())
|
||||
for (Monster *monster in Monster.monsters)
|
||||
if (!vreject(d.origin, monster.origin, 7.0f) && d != monster &&
|
||||
!plcollide(d, monster, &headspace, &hi, &lo))
|
||||
return false;
|
||||
|
@ -323,7 +325,7 @@ moveplayer4(DynamicEntity *pl, int moveres, bool local, int curtime)
|
|||
pl.velocity.y / 8, pl.velocity.z);
|
||||
if (local)
|
||||
playsoundc(S_JUMP);
|
||||
else if (pl.monsterState) {
|
||||
else if ([pl isKindOfClass:Monster.class]) {
|
||||
OFVector3D loc = pl.origin;
|
||||
playsound(S_JUMP, &loc);
|
||||
}
|
||||
|
@ -332,7 +334,7 @@ moveplayer4(DynamicEntity *pl, int moveres, bool local, int curtime)
|
|||
// high jump, make thud sound
|
||||
if (local)
|
||||
playsoundc(S_LAND);
|
||||
else if (pl.monsterState) {
|
||||
else if ([pl isKindOfClass:Monster.class]) {
|
||||
OFVector3D loc = pl.origin;
|
||||
playsound(S_LAND, &loc);
|
||||
}
|
||||
|
|
|
@ -250,15 +250,6 @@ extern void projreset();
|
|||
extern OFString *playerincrosshair();
|
||||
extern int reloadtime(int gun);
|
||||
|
||||
// monster
|
||||
extern void monsterclear();
|
||||
extern void restoremonsterstate();
|
||||
extern void monsterthink();
|
||||
extern void monsterrender();
|
||||
extern OFArray<DynamicEntity *> *getmonsters();
|
||||
extern void monsterpain(DynamicEntity *m, int damage, DynamicEntity *d);
|
||||
extern void endsp(bool allkilled);
|
||||
|
||||
// entities
|
||||
extern void initEntities();
|
||||
extern void renderents();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "cube.h"
|
||||
|
||||
#import "DynamicEntity.h"
|
||||
#import "Monster.h"
|
||||
#import "OFString+Cube.h"
|
||||
|
||||
#ifdef DARWIN
|
||||
|
@ -478,7 +479,7 @@ gl_drawframe(int w, int h, float curfps)
|
|||
xtraverts = 0;
|
||||
|
||||
renderclients();
|
||||
monsterrender();
|
||||
[Monster renderAll];
|
||||
|
||||
renderentities();
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#import "DynamicEntity.h"
|
||||
#import "Entity.h"
|
||||
#import "Monster.h"
|
||||
|
||||
#ifdef OF_BIG_ENDIAN
|
||||
static const int islittleendian = 0;
|
||||
|
@ -114,9 +115,9 @@ savestate(OFIRI *IRI)
|
|||
for (Entity *e in ents)
|
||||
gzputc(f, e.spawned);
|
||||
gzwrite(f, data.items, data.count);
|
||||
OFArray<DynamicEntity *> *monsters = getmonsters();
|
||||
OFArray<Monster *> *monsters = Monster.monsters;
|
||||
gzputi(monsters.count);
|
||||
for (DynamicEntity *monster in monsters) {
|
||||
for (Monster *monster in monsters) {
|
||||
data = [monster dataBySerializing];
|
||||
gzwrite(f, data.items, data.count);
|
||||
}
|
||||
|
@ -227,11 +228,11 @@ loadgamerest()
|
|||
player1.lastAction = lastmillis;
|
||||
|
||||
int nmonsters = gzgeti();
|
||||
OFArray<DynamicEntity *> *monsters = getmonsters();
|
||||
OFArray<Monster *> *monsters = Monster.monsters;
|
||||
if (nmonsters != monsters.count)
|
||||
return loadgameout();
|
||||
|
||||
for (DynamicEntity *monster in monsters) {
|
||||
for (Monster *monster in monsters) {
|
||||
gzread(f, data.mutableItems, data.count);
|
||||
[monster setFromSerializedData:data];
|
||||
// lazy, could save id of enemy instead
|
||||
|
@ -241,7 +242,7 @@ loadgamerest()
|
|||
if (monster.state == CS_DEAD)
|
||||
monster.lastAction = 0;
|
||||
}
|
||||
restoremonsterstate();
|
||||
[Monster restoreAll];
|
||||
|
||||
int nplayers = gzgeti();
|
||||
loopi(nplayers) if (!gzget())
|
||||
|
|
25
src/weapon.m
25
src/weapon.m
|
@ -3,6 +3,7 @@
|
|||
#include "cube.h"
|
||||
|
||||
#import "DynamicEntity.h"
|
||||
#import "Monster.h"
|
||||
#import "OFString+Cube.h"
|
||||
#import "Projectile.h"
|
||||
|
||||
|
@ -168,13 +169,13 @@ newprojectile(const OFVector3D *from, const OFVector3D *to, float speed,
|
|||
}
|
||||
|
||||
void
|
||||
hit(int target, int damage, DynamicEntity *d, DynamicEntity *at)
|
||||
hit(int target, int damage, __kindof DynamicEntity *d, DynamicEntity *at)
|
||||
{
|
||||
OFVector3D o = d.origin;
|
||||
if (d == player1)
|
||||
selfdamage(damage, at == player1 ? -1 : -2, at);
|
||||
else if (d.monsterState)
|
||||
monsterpain(d, damage, at);
|
||||
else if ([d isKindOfClass:Monster.class])
|
||||
[d incurDamage:damage fromEntity:at];
|
||||
else {
|
||||
addmsg(1, 4, SV_DAMAGE, target, damage, d.lifeSequence);
|
||||
playsound(S_PAIN1 + rnd(5), &o);
|
||||
|
@ -235,8 +236,8 @@ splash(Projectile *p, const OFVector3D *v, const OFVector3D *vold,
|
|||
radialeffect(player, v, i, qdam, p.owner);
|
||||
}];
|
||||
|
||||
[getmonsters() enumerateObjectsUsingBlock:^(
|
||||
DynamicEntity *monster, size_t i, bool *stop) {
|
||||
[Monster.monsters enumerateObjectsUsingBlock:^(
|
||||
Monster *monster, size_t i, bool *stop) {
|
||||
if (i != notthismonster)
|
||||
radialeffect(monster, v, i, qdam, p.owner);
|
||||
}];
|
||||
|
@ -267,7 +268,7 @@ moveprojectiles(float time)
|
|||
continue;
|
||||
|
||||
int qdam = guns[p.gun].damage * (p.owner.quadMillis ? 4 : 1);
|
||||
if (p.owner.monsterState)
|
||||
if ([p.owner isKindOfClass:Monster.class])
|
||||
qdam /= MONSTERDAMAGEFACTOR;
|
||||
vdist(dist, v, p.o, p.to);
|
||||
float dtime = dist * 1000 / p.speed;
|
||||
|
@ -283,7 +284,7 @@ moveprojectiles(float time)
|
|||
if (p.owner != player1)
|
||||
projdamage(player1, p, &v, -1, -1, qdam);
|
||||
|
||||
for (DynamicEntity *monster in getmonsters())
|
||||
for (Monster *monster in Monster.monsters)
|
||||
if (!vreject(monster.origin, v, 10.0f) &&
|
||||
monster != p.owner)
|
||||
projdamage(monster, p, &v, -1, i, qdam);
|
||||
|
@ -335,7 +336,7 @@ shootv(int gun, const OFVector3D *from, const OFVector3D *to, DynamicEntity *d,
|
|||
case GUN_ICEBALL:
|
||||
case GUN_SLIMEBALL:
|
||||
pspeed = guns[gun].projspeed;
|
||||
if (d.monsterState)
|
||||
if ([d isKindOfClass:Monster.class])
|
||||
pspeed /= 2;
|
||||
newprojectile(from, to, (float)pspeed, local, d, gun);
|
||||
break;
|
||||
|
@ -366,7 +367,7 @@ raydamage(DynamicEntity *o, const OFVector3D *from, const OFVector3D *to,
|
|||
int qdam = guns[d.gunSelect].damage;
|
||||
if (d.quadMillis)
|
||||
qdam *= 4;
|
||||
if (d.monsterState)
|
||||
if ([d isKindOfClass:Monster.class])
|
||||
qdam /= MONSTERDAMAGEFACTOR;
|
||||
if (d.gunSelect == GUN_SG) {
|
||||
int damage = 0;
|
||||
|
@ -419,7 +420,7 @@ shoot(DynamicEntity *d, const OFVector3D *targ)
|
|||
if (d.quadMillis && attacktime > 200)
|
||||
playsoundc(S_ITEMPUP);
|
||||
shootv(d.gunSelect, &from, &to, d, true);
|
||||
if (!d.monsterState)
|
||||
if (![d isKindOfClass:Monster.class])
|
||||
addmsg(1, 8, SV_SHOT, d.gunSelect, (int)(from.x * DMF),
|
||||
(int)(from.y * DMF), (int)(from.z * DMF), (int)(to.x * DMF),
|
||||
(int)(to.y * DMF), (int)(to.z * DMF));
|
||||
|
@ -433,10 +434,10 @@ shoot(DynamicEntity *d, const OFVector3D *targ)
|
|||
raydamage(player, &from, &to, d, i);
|
||||
}];
|
||||
|
||||
for (DynamicEntity *monster in getmonsters())
|
||||
for (Monster *monster in Monster.monsters)
|
||||
if (monster != d)
|
||||
raydamage(monster, &from, &to, d, -2);
|
||||
|
||||
if (d.monsterState)
|
||||
if ([d isKindOfClass:Monster.class])
|
||||
raydamage(player1, &from, &to, d, -1);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#import "DynamicEntity.h"
|
||||
#import "Entity.h"
|
||||
#import "Monster.h"
|
||||
|
||||
extern OFString *entnames[]; // lookup from map entities above to strings
|
||||
|
||||
|
@ -79,7 +80,7 @@ trigger(int tag, int type, bool savegame)
|
|||
execute(aliasname, true);
|
||||
|
||||
if (type == 2)
|
||||
endsp(false);
|
||||
[Monster endSinglePlayerWithAllKilled:false];
|
||||
}
|
||||
COMMAND(trigger, ARG_2INT)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#import "DynamicEntity.h"
|
||||
#import "Entity.h"
|
||||
#import "Monster.h"
|
||||
|
||||
extern bool hasoverbright;
|
||||
|
||||
|
@ -205,7 +206,7 @@ dodynlight(const OFVector3D *vold, const OFVector3D *v, int reach, int strength,
|
|||
{
|
||||
if (!reach)
|
||||
reach = dynlight;
|
||||
if (owner.monsterState)
|
||||
if ([owner isKindOfClass:Monster.class])
|
||||
reach = reach / 2;
|
||||
if (!reach)
|
||||
return;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue