From 2c5f2d03427302dedf014d455a7f623cdfa45a35 Mon Sep 17 00:00:00 2001 From: Jonathan Schleifer Date: Sun, 23 Mar 2025 02:03:32 +0000 Subject: [PATCH] Convert monster into a class FossilOrigin-Name: e8f80b0482846dfbd5bf97ed375b13687d325e9ef7e2a79176dac49722180124 --- src/DynamicEntity.h | 16 +- src/DynamicEntity.m | 44 ++--- src/Monster.h | 39 ++++ src/{monster.m => Monster.m} | 352 +++++++++++++++++++++-------------- src/clientextras.m | 7 +- src/clientgame.m | 5 +- src/cube.h | 1 - src/editing.m | 5 +- src/meson.build | 2 +- src/physics.m | 18 +- src/protos.h | 9 - src/rendergl.m | 3 +- src/savegamedemo.m | 11 +- src/weapon.m | 25 +-- src/world.m | 3 +- src/worldlight.m | 3 +- 16 files changed, 319 insertions(+), 224 deletions(-) create mode 100644 src/Monster.h rename src/{monster.m => Monster.m} (51%) diff --git a/src/DynamicEntity.h b/src/DynamicEntity.h index c8c8385..ab28f79 100644 --- a/src/DynamicEntity.h +++ b/src/DynamicEntity.h @@ -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; diff --git a/src/DynamicEntity.m b/src/DynamicEntity.m index 6efdb55..e335ba9 100644 --- a/src/DynamicEntity.m +++ b/src/DynamicEntity.m @@ -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]; diff --git a/src/Monster.h b/src/Monster.h new file mode 100644 index 0000000..1e4554d --- /dev/null +++ b/src/Monster.h @@ -0,0 +1,39 @@ +#import "DynamicEntity.h" + +@interface Monster: DynamicEntity +@property (class, readonly, nonatomic) OFMutableArray *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 diff --git a/src/monster.m b/src/Monster.m similarity index 51% rename from src/monster.m rename to src/Monster.m index 5f5c205..c971cfc 100644 --- a/src/monster.m +++ b/src/Monster.m @@ -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 *monsters; +static OFMutableArray *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 * -getmonsters() ++ (OFMutableArray *)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 diff --git a/src/clientextras.m b/src/clientextras.m index 1d2677d..00268b1 100644 --- a/src/clientextras.m +++ b/src/clientextras.m @@ -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; diff --git a/src/clientgame.m b/src/clientgame.m index e0dd5df..cbb4cca 100644 --- a/src/clientgame.m +++ b/src/clientgame.m @@ -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); diff --git a/src/cube.h b/src/cube.h index c4d4cee..1d9d8be 100644 --- a/src/cube.h +++ b/src/cube.h @@ -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, diff --git a/src/editing.m b/src/editing.m index bcec0da..bb5674a 100644 --- a/src/editing.m +++ b/src/editing.m @@ -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; diff --git a/src/meson.build b/src/meson.build index acab84e..bb4cc62 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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', diff --git a/src/physics.m b/src/physics.m index c28af01..8b7eff1 100644 --- a/src/physics.m +++ b/src/physics.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) - ? d.origin.z - d.eyeHeight - 4.5f - : -1000.0f; + float minfloor = + ([d isKindOfClass:Monster.class] && !spawn && d.health > 100 + ? d.origin.z - d.eyeHeight - 4.5f + : -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); } diff --git a/src/protos.h b/src/protos.h index 0de0192..5f906a3 100644 --- a/src/protos.h +++ b/src/protos.h @@ -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 *getmonsters(); -extern void monsterpain(DynamicEntity *m, int damage, DynamicEntity *d); -extern void endsp(bool allkilled); - // entities extern void initEntities(); extern void renderents(); diff --git a/src/rendergl.m b/src/rendergl.m index 56e61dc..68cb14d 100644 --- a/src/rendergl.m +++ b/src/rendergl.m @@ -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(); diff --git a/src/savegamedemo.m b/src/savegamedemo.m index 5d0f75b..d69037d 100644 --- a/src/savegamedemo.m +++ b/src/savegamedemo.m @@ -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 *monsters = getmonsters(); + OFArray *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 *monsters = getmonsters(); + OFArray *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()) diff --git a/src/weapon.m b/src/weapon.m index 5e6cc50..bd570ce 100644 --- a/src/weapon.m +++ b/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); } diff --git a/src/world.m b/src/world.m index 7668b7c..568dfc4 100644 --- a/src/world.m +++ b/src/world.m @@ -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) diff --git a/src/worldlight.m b/src/worldlight.m index a40870a..521e7b9 100644 --- a/src/worldlight.m +++ b/src/worldlight.m @@ -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;