Convert dynent to a class
FossilOrigin-Name: d2b3ff790fee10ef4150cdb17f1c979e7001a420b2629b153b4dbf4c0b489704
This commit is contained in:
parent
410e244ed6
commit
90fc249052
26 changed files with 1447 additions and 1061 deletions
299
src/monster.mm
299
src/monster.mm
|
@ -2,22 +2,27 @@
|
|||
|
||||
#include "cube.h"
|
||||
|
||||
dvector monsters;
|
||||
int nextmonster, spawnremain, numkilled, monstertotal, mtimestart;
|
||||
#import "DynamicEntity.h"
|
||||
|
||||
static OFMutableArray<DynamicEntity *> *monsters;
|
||||
static int nextmonster, spawnremain, numkilled, monstertotal, mtimestart;
|
||||
|
||||
VARF(skill, 1, 3, 10, conoutf(@"skill is now %d", skill));
|
||||
|
||||
dvector &
|
||||
OFArray<DynamicEntity *> *
|
||||
getmonsters()
|
||||
{
|
||||
return monsters;
|
||||
}
|
||||
|
||||
// for savegames
|
||||
void
|
||||
restoremonsterstate()
|
||||
{
|
||||
loopv(monsters) if (monsters[i]->state == CS_DEAD) numkilled++;
|
||||
} // for savegames
|
||||
for (DynamicEntity *monster in monsters)
|
||||
if (monster.state == CS_DEAD)
|
||||
numkilled++;
|
||||
}
|
||||
|
||||
#define TOTMFREQ 13
|
||||
#define NUMMONSTERTYPES 8
|
||||
|
@ -49,40 +54,43 @@ monstertypes[NUMMONSTERTYPES] = {
|
|||
@"a goblin", @"monster/goblin" },
|
||||
};
|
||||
|
||||
dynent *
|
||||
DynamicEntity *
|
||||
basicmonster(int type, int yaw, int state, int trigger, int move)
|
||||
{
|
||||
if (type >= NUMMONSTERTYPES) {
|
||||
conoutf(@"warning: unknown monster in spawn: %d", type);
|
||||
type = 0;
|
||||
}
|
||||
dynent *m = newdynent();
|
||||
monstertype *t = &monstertypes[m->mtype = 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;
|
||||
DynamicEntity *m = newdynent();
|
||||
monstertype *t = &monstertypes[(m.mtype = 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;
|
||||
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;
|
||||
@autoreleasepool {
|
||||
strcpy_s(m->name, t->name.UTF8String);
|
||||
}
|
||||
monsters.add(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;
|
||||
|
||||
if (monsters == nil)
|
||||
monsters = [[OFMutableArray alloc] init];
|
||||
|
||||
[monsters addObject:m];
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
|
@ -99,12 +107,12 @@ spawnmonster() // spawn a random monster according to freq distribution in DMSP
|
|||
basicmonster(type, rnd(360), M_SEARCH, 1000, 1);
|
||||
}
|
||||
|
||||
// called after map start of when toggling edit mode to reset/spawn all
|
||||
// monsters to initial state
|
||||
void
|
||||
monsterclear() // called after map start of when toggling edit mode to
|
||||
// reset/spawn all monsters to initial state
|
||||
monsterclear()
|
||||
{
|
||||
loopv(monsters) free(monsters[i]);
|
||||
monsters.setsize(0);
|
||||
[monsters removeAllObjects];
|
||||
numkilled = 0;
|
||||
monstertotal = 0;
|
||||
spawnremain = 0;
|
||||
|
@ -115,20 +123,18 @@ monsterclear() // called after map start of when toggling edit mode to
|
|||
mtimestart = lastmillis;
|
||||
loopv(ents) if (ents[i].type == MONSTER)
|
||||
{
|
||||
dynent *m = basicmonster(
|
||||
DynamicEntity *m = basicmonster(
|
||||
ents[i].attr2, ents[i].attr1, M_SLEEP, 100, 0);
|
||||
m->o.x = ents[i].x;
|
||||
m->o.y = ents[i].y;
|
||||
m->o.z = ents[i].z;
|
||||
m.o = OFMakeVector3D(ents[i].x, ents[i].y, ents[i].z);
|
||||
entinmap(m);
|
||||
monstertotal++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// height-correct line of sight for monster shooting/seeing
|
||||
bool
|
||||
los(float lx, float ly, float lz, float bx, float by, float bz,
|
||||
OFVector3D &v) // height-correct line of sight for monster shooting/seeing
|
||||
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))
|
||||
return false;
|
||||
|
@ -164,11 +170,11 @@ los(float lx, float ly, float lz, float bx, float by, float bz,
|
|||
}
|
||||
|
||||
bool
|
||||
enemylos(dynent *m, OFVector3D &v)
|
||||
enemylos(DynamicEntity *m, OFVector3D &v)
|
||||
{
|
||||
v = m->o;
|
||||
return los(m->o.x, m->o.y, m->o.z, m->enemy->o.x, m->enemy->o.y,
|
||||
m->enemy->o.z, v);
|
||||
v = m.o;
|
||||
return los(
|
||||
m.o.x, m.o.y, m.o.z, m.enemy.o.x, m.enemy.o.y, m.enemy.o.z, v);
|
||||
}
|
||||
|
||||
// monster AI is sequenced using transitions: they are in a particular state
|
||||
|
@ -178,73 +184,72 @@ enemylos(dynent *m, OFVector3D &v)
|
|||
// parametrized by difficulty level (skill), faster transitions means quicker
|
||||
// decision making means tougher AI.
|
||||
|
||||
// n = at skill 0, n/2 = at skill 10, r = added random factor
|
||||
void
|
||||
transition(dynent *m, int state, int moving, int n,
|
||||
int r) // n = at skill 0, n/2 = at skill 10, r = added random factor
|
||||
transition(DynamicEntity *m, int state, int moving, int n, int r)
|
||||
{
|
||||
m->monsterstate = state;
|
||||
m->move = moving;
|
||||
m.monsterstate = state;
|
||||
m.move = moving;
|
||||
n = n * 130 / 100;
|
||||
m->trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1);
|
||||
m.trigger = lastmillis + n - skill * (n / 16) + rnd(r + 1);
|
||||
}
|
||||
|
||||
void
|
||||
normalise(dynent *m, float angle)
|
||||
normalise(DynamicEntity *m, float angle)
|
||||
{
|
||||
while (m->yaw < angle - 180.0f)
|
||||
m->yaw += 360.0f;
|
||||
while (m->yaw > angle + 180.0f)
|
||||
m->yaw -= 360.0f;
|
||||
while (m.yaw < angle - 180.0f)
|
||||
m.yaw += 360.0f;
|
||||
while (m.yaw > angle + 180.0f)
|
||||
m.yaw -= 360.0f;
|
||||
}
|
||||
|
||||
// main AI thinking routine, called every frame for every monster
|
||||
void
|
||||
monsteraction(
|
||||
dynent *m) // main AI thinking routine, called every frame for every monster
|
||||
monsteraction(DynamicEntity *m)
|
||||
{
|
||||
if (m->enemy->state == CS_DEAD) {
|
||||
m->enemy = player1;
|
||||
m->anger = 0;
|
||||
if (m.enemy.state == CS_DEAD) {
|
||||
m.enemy = player1;
|
||||
m.anger = 0;
|
||||
}
|
||||
normalise(m, m->targetyaw);
|
||||
if (m->targetyaw > m->yaw) // slowly turn monster towards his target
|
||||
{
|
||||
m->yaw += curtime * 0.5f;
|
||||
if (m->targetyaw < m->yaw)
|
||||
m->yaw = m->targetyaw;
|
||||
normalise(m, m.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;
|
||||
} else {
|
||||
m->yaw -= curtime * 0.5f;
|
||||
if (m->targetyaw > m->yaw)
|
||||
m->yaw = m->targetyaw;
|
||||
m.yaw -= curtime * 0.5f;
|
||||
if (m.targetyaw > m.yaw)
|
||||
m.yaw = m.targetyaw;
|
||||
}
|
||||
|
||||
vdist(disttoenemy, vectoenemy, m->o, m->enemy->o);
|
||||
m->pitch = atan2(m->enemy->o.z - m->o.z, disttoenemy) * 180 / PI;
|
||||
vdist(disttoenemy, vectoenemy, m.o, m.enemy.o);
|
||||
m.pitch = atan2(m.enemy.o.z - m.o.z, disttoenemy) * 180 / PI;
|
||||
|
||||
// special case: if we run into scenery
|
||||
if (m->blocked) {
|
||||
m->blocked = false;
|
||||
if (m.blocked) {
|
||||
m.blocked = false;
|
||||
// try to jump over obstackle (rare)
|
||||
if (!rnd(20000 / monstertypes[m->mtype].speed))
|
||||
m->jumpnext = true;
|
||||
if (!rnd(20000 / monstertypes[m.mtype].speed))
|
||||
m.jumpnext = true;
|
||||
// search for a way around (common)
|
||||
else if (m->trigger < lastmillis &&
|
||||
(m->monsterstate != M_HOME || !rnd(5))) {
|
||||
else if (m.trigger < lastmillis &&
|
||||
(m.monsterstate != M_HOME || !rnd(5))) {
|
||||
// patented "random walk" AI pathfinding (tm) ;)
|
||||
m->targetyaw += 180 + rnd(180);
|
||||
m.targetyaw += 180 + rnd(180);
|
||||
transition(m, M_SEARCH, 1, 400, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
float enemyyaw =
|
||||
-(float)atan2(m->enemy->o.x - m->o.x, m->enemy->o.y - m->o.y) / PI *
|
||||
180 +
|
||||
-(float)atan2(m.enemy.o.x - m.o.x, m.enemy.o.y - m.o.y) / PI * 180 +
|
||||
180;
|
||||
|
||||
switch (m->monsterstate) {
|
||||
switch (m.monsterstate) {
|
||||
case M_PAIN:
|
||||
case M_ATTACKING:
|
||||
case M_SEARCH:
|
||||
if (m->trigger < lastmillis)
|
||||
if (m.trigger < lastmillis)
|
||||
transition(m, M_HOME, 1, 100, 200);
|
||||
break;
|
||||
|
||||
|
@ -255,54 +260,53 @@ monsteraction(
|
|||
if (editmode || !enemylos(m, target))
|
||||
return; // skip running physics
|
||||
normalise(m, enemyyaw);
|
||||
float angle = (float)fabs(enemyyaw - m->yaw);
|
||||
float angle = (float)fabs(enemyyaw - m.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);
|
||||
playsound(S_GRUNT1 + rnd(2), &m->o);
|
||||
OFVector3D loc = m.o;
|
||||
playsound(S_GRUNT1 + rnd(2), &loc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
shoot(m, m->attacktarget);
|
||||
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;
|
||||
shoot(m, m.attacktarget);
|
||||
transition(m, M_ATTACKING, 0, 600, 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) {
|
||||
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) {
|
||||
OFVector3D target;
|
||||
if (!enemylos(
|
||||
m, target)) // no visual contact anymore, let
|
||||
// monster get as close as possible
|
||||
// then search for player
|
||||
{
|
||||
if (!enemylos(m, target)) {
|
||||
// no visual contact anymore, let monster get
|
||||
// as close as possible then search for player
|
||||
transition(m, M_HOME, 1, 800, 500);
|
||||
} else // the closer the monster is the more likely he
|
||||
// wants to shoot
|
||||
{
|
||||
} else {
|
||||
// the closer the monster is the more likely he
|
||||
// wants to shoot
|
||||
if (!rnd((int)disttoenemy / 3 + 1) &&
|
||||
m->enemy->state ==
|
||||
CS_ALIVE) // get ready to fire
|
||||
{
|
||||
m->attacktarget = target;
|
||||
m.enemy.state == CS_ALIVE) {
|
||||
// get ready to fire
|
||||
m.attacktarget = target;
|
||||
transition(m, M_AIMING, 0,
|
||||
monstertypes[m->mtype].lag, 10);
|
||||
} else // track player some more
|
||||
{
|
||||
monstertypes[m.mtype].lag, 10);
|
||||
} else
|
||||
// track player some more
|
||||
transition(m, M_HOME, 1,
|
||||
monstertypes[m->mtype].rate, 0);
|
||||
}
|
||||
monstertypes[m.mtype].rate, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -312,38 +316,40 @@ monsteraction(
|
|||
}
|
||||
|
||||
void
|
||||
monsterpain(dynent *m, int damage, dynent *d)
|
||||
monsterpain(DynamicEntity *m, int damage, DynamicEntity *d)
|
||||
{
|
||||
// a monster hit us
|
||||
if (d->monsterstate) {
|
||||
if (d.monsterstate) {
|
||||
// guard for RL guys shooting themselves :)
|
||||
if (m != d) {
|
||||
// don't attack straight away, first get angry
|
||||
m->anger++;
|
||||
int anger =
|
||||
m->mtype == d->mtype ? m->anger / 2 : m->anger;
|
||||
if (anger >= monstertypes[m->mtype].loyalty)
|
||||
m.anger++;
|
||||
int anger = m.mtype == d.mtype ? m.anger / 2 : m.anger;
|
||||
if (anger >= monstertypes[m.mtype].loyalty)
|
||||
// monster infight if very angry
|
||||
m->enemy = d;
|
||||
m.enemy = d;
|
||||
}
|
||||
} else {
|
||||
// player hit us
|
||||
m->anger = 0;
|
||||
m->enemy = d;
|
||||
m.anger = 0;
|
||||
m.enemy = d;
|
||||
}
|
||||
// in this state monster won't attack
|
||||
transition(m, M_PAIN, 0, monstertypes[m->mtype].pain, 200);
|
||||
if ((m->health -= damage) <= 0) {
|
||||
m->state = CS_DEAD;
|
||||
m->lastaction = lastmillis;
|
||||
transition(m, M_PAIN, 0, monstertypes[m.mtype].pain, 200);
|
||||
if ((m.health -= damage) <= 0) {
|
||||
m.state = CS_DEAD;
|
||||
m.lastaction = lastmillis;
|
||||
numkilled++;
|
||||
player1->frags = numkilled;
|
||||
playsound(monstertypes[m->mtype].diesound, &m->o);
|
||||
player1.frags = numkilled;
|
||||
OFVector3D loc = m.o;
|
||||
playsound(monstertypes[m.mtype].diesound, &loc);
|
||||
int remain = monstertotal - numkilled;
|
||||
if (remain > 0 && remain <= 5)
|
||||
conoutf(@"only %d monster(s) remaining", remain);
|
||||
} else
|
||||
playsound(monstertypes[m->mtype].painsound, &m->o);
|
||||
} else {
|
||||
OFVector3D loc = m.o;
|
||||
playsound(monstertypes[m.mtype].painsound, &loc);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -370,8 +376,8 @@ monsterthink()
|
|||
if (monstertotal && !spawnremain && numkilled == monstertotal)
|
||||
endsp(true);
|
||||
|
||||
loopv(ents) // equivalent of player entity touch, but only teleports are
|
||||
// used
|
||||
// equivalent of player entity touch, but only teleports are used
|
||||
loopv(ents)
|
||||
{
|
||||
entity &e = ents[i];
|
||||
if (e.type != TELEPORT)
|
||||
|
@ -380,33 +386,32 @@ monsterthink()
|
|||
continue;
|
||||
OFVector3D v =
|
||||
OFMakeVector3D(e.x, e.y, (float)S(e.x, e.y)->floor);
|
||||
loopv(monsters)
|
||||
{
|
||||
if (monsters[i]->state == CS_DEAD) {
|
||||
if (lastmillis - monsters[i]->lastaction <
|
||||
2000) {
|
||||
monsters[i]->move = 0;
|
||||
moveplayer(monsters[i], 1, false);
|
||||
for (DynamicEntity *monster in monsters) {
|
||||
if (monster.state == CS_DEAD) {
|
||||
if (lastmillis - monster.lastaction < 2000) {
|
||||
monster.move = 0;
|
||||
moveplayer(monster, 1, false);
|
||||
}
|
||||
} else {
|
||||
v.z += monsters[i]->eyeheight;
|
||||
vdist(dist, t, monsters[i]->o, v);
|
||||
v.z -= monsters[i]->eyeheight;
|
||||
v.z += monster.eyeheight;
|
||||
vdist(dist, t, monster.o, v);
|
||||
v.z -= monster.eyeheight;
|
||||
if (dist < 4)
|
||||
teleport(
|
||||
(int)(&e - &ents[0]), monsters[i]);
|
||||
teleport((int)(&e - &ents[0]), monster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loopv(monsters) if (monsters[i]->state == CS_ALIVE)
|
||||
monsteraction(monsters[i]);
|
||||
for (DynamicEntity *monster in monsters)
|
||||
if (monster.state == CS_ALIVE)
|
||||
monsteraction(monster);
|
||||
}
|
||||
|
||||
void
|
||||
monsterrender()
|
||||
{
|
||||
loopv(monsters) renderclient(monsters[i], false,
|
||||
monstertypes[monsters[i]->mtype].mdlname, monsters[i]->mtype == 5,
|
||||
monstertypes[monsters[i]->mtype].mscale / 10.0f);
|
||||
for (DynamicEntity *monster in monsters)
|
||||
renderclient(monster, false,
|
||||
monstertypes[monster.mtype].mdlname, monster.mtype == 5,
|
||||
monstertypes[monster.mtype].mscale / 10.0f);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue