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