mirror of
https://github.com/yquake2/yquake2remaster.git
synced 2025-02-20 10:53:22 +00:00
A long time ago in2b4f223
I introduced a small logic change to the handling of stacked entities. If two entities were standing on each other the original code set the movement speed of the upper entity to 0. It would be pushed or dragged by the lower entity. I changed that in way that the upper entity got the same speed as the lower entity. With that change it wasn't pushed or dragged but moving on it's own. I hoped to fix some of the 'elevator hurts player or monster' bugs. That hope was wrong, at a later time we quirked all elevators that hurt the player. Additionally the change lead to physics bugs if entities are standing on high speed elevators (more than 200 units per seconds). So revert it. The actual fix was already committed as part of69b6e5a
. This is just a little cleanup commit, mainly for documentation purposes. This closes #320.
1053 lines
18 KiB
C
1053 lines
18 KiB
C
/*
|
|
* Copyright (C) 1997-2001 Id Software, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*
|
|
* =======================================================================
|
|
*
|
|
* Monster utility functions.
|
|
*
|
|
* =======================================================================
|
|
*/
|
|
|
|
#include "header/local.h"
|
|
|
|
void monster_start_go(edict_t *self);
|
|
|
|
/* Monster weapons */
|
|
|
|
void
|
|
monster_fire_bullet(edict_t *self, vec3_t start, vec3_t dir, int damage,
|
|
int kick, int hspread, int vspread, int flashtype)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
fire_bullet(self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
|
|
|
|
gi.WriteByte(svc_muzzleflash2);
|
|
gi.WriteShort(self - g_edicts);
|
|
gi.WriteByte(flashtype);
|
|
gi.multicast(start, MULTICAST_PVS);
|
|
}
|
|
|
|
void
|
|
monster_fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
|
|
int kick, int hspread, int vspread, int count, int flashtype)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
fire_shotgun(self, start, aimdir, damage, kick, hspread,
|
|
vspread, count, MOD_UNKNOWN);
|
|
|
|
gi.WriteByte(svc_muzzleflash2);
|
|
gi.WriteShort(self - g_edicts);
|
|
gi.WriteByte(flashtype);
|
|
gi.multicast(start, MULTICAST_PVS);
|
|
}
|
|
|
|
void
|
|
monster_fire_blaster(edict_t *self, vec3_t start, vec3_t dir, int damage,
|
|
int speed, int flashtype, int effect)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
fire_blaster(self, start, dir, damage, speed, effect, false);
|
|
|
|
gi.WriteByte(svc_muzzleflash2);
|
|
gi.WriteShort(self - g_edicts);
|
|
gi.WriteByte(flashtype);
|
|
gi.multicast(start, MULTICAST_PVS);
|
|
}
|
|
|
|
void
|
|
monster_fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir,
|
|
int damage, int speed, int flashtype)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
fire_grenade(self, start, aimdir, damage, speed, 2.5, damage + 40);
|
|
|
|
gi.WriteByte(svc_muzzleflash2);
|
|
gi.WriteShort(self - g_edicts);
|
|
gi.WriteByte(flashtype);
|
|
gi.multicast(start, MULTICAST_PVS);
|
|
}
|
|
|
|
void
|
|
monster_fire_rocket(edict_t *self, vec3_t start, vec3_t dir,
|
|
int damage, int speed, int flashtype)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
fire_rocket(self, start, dir, damage, speed, damage + 20, damage);
|
|
|
|
gi.WriteByte(svc_muzzleflash2);
|
|
gi.WriteShort(self - g_edicts);
|
|
gi.WriteByte(flashtype);
|
|
gi.multicast(start, MULTICAST_PVS);
|
|
}
|
|
|
|
void
|
|
monster_fire_railgun(edict_t *self, vec3_t start, vec3_t aimdir,
|
|
int damage, int kick, int flashtype)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
fire_rail(self, start, aimdir, damage, kick);
|
|
|
|
gi.WriteByte(svc_muzzleflash2);
|
|
gi.WriteShort(self - g_edicts);
|
|
gi.WriteByte(flashtype);
|
|
gi.multicast(start, MULTICAST_PVS);
|
|
}
|
|
|
|
void
|
|
monster_fire_bfg(edict_t *self, vec3_t start, vec3_t aimdir,
|
|
int damage, int speed, int kick /* unused */, float damage_radius,
|
|
int flashtype)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
fire_bfg(self, start, aimdir, damage, speed, damage_radius);
|
|
|
|
gi.WriteByte(svc_muzzleflash2);
|
|
gi.WriteShort(self - g_edicts);
|
|
gi.WriteByte(flashtype);
|
|
gi.multicast(start, MULTICAST_PVS);
|
|
}
|
|
|
|
/* ================================================================== */
|
|
|
|
/* Monster utility functions */
|
|
|
|
void
|
|
M_FliesOff(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->s.effects &= ~EF_FLIES;
|
|
self->s.sound = 0;
|
|
}
|
|
|
|
void
|
|
M_FliesOn(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->waterlevel)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->s.effects |= EF_FLIES;
|
|
self->s.sound = gi.soundindex("infantry/inflies1.wav");
|
|
self->think = M_FliesOff;
|
|
self->nextthink = level.time + 60;
|
|
}
|
|
|
|
void
|
|
M_FlyCheck(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->waterlevel)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (random() > 0.5)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->think = M_FliesOn;
|
|
self->nextthink = level.time + 5 + 10 * random();
|
|
}
|
|
|
|
void
|
|
AttackFinished(edict_t *self, float time)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->monsterinfo.attack_finished = level.time + time;
|
|
}
|
|
|
|
void
|
|
M_CheckGround(edict_t *ent)
|
|
{
|
|
vec3_t point;
|
|
trace_t trace;
|
|
|
|
if (!ent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ent->flags & (FL_SWIM | FL_FLY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ent->velocity[2] > 100)
|
|
{
|
|
ent->groundentity = NULL;
|
|
return;
|
|
}
|
|
|
|
/* if the hull point one-quarter unit down
|
|
is solid the entity is on ground */
|
|
point[0] = ent->s.origin[0];
|
|
point[1] = ent->s.origin[1];
|
|
point[2] = ent->s.origin[2] - 0.25;
|
|
|
|
trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, point,
|
|
ent, MASK_MONSTERSOLID);
|
|
|
|
/* check steepness */
|
|
if ((trace.plane.normal[2] < 0.7) && !trace.startsolid)
|
|
{
|
|
ent->groundentity = NULL;
|
|
return;
|
|
}
|
|
|
|
if (!trace.startsolid && !trace.allsolid)
|
|
{
|
|
VectorCopy(trace.endpos, ent->s.origin);
|
|
ent->groundentity = trace.ent;
|
|
ent->groundentity_linkcount = trace.ent->linkcount;
|
|
ent->velocity[2] = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
M_CatagorizePosition(edict_t *ent)
|
|
{
|
|
vec3_t point;
|
|
int cont;
|
|
|
|
if (!ent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* get waterlevel */
|
|
point[0] = (ent->absmax[0] + ent->absmin[0])/2;
|
|
point[1] = (ent->absmax[1] + ent->absmin[1])/2;
|
|
point[2] = ent->absmin[2] + 2;
|
|
cont = gi.pointcontents(point);
|
|
|
|
if (!(cont & MASK_WATER))
|
|
{
|
|
ent->waterlevel = 0;
|
|
ent->watertype = 0;
|
|
return;
|
|
}
|
|
|
|
ent->watertype = cont;
|
|
ent->waterlevel = 1;
|
|
point[2] += 26;
|
|
cont = gi.pointcontents(point);
|
|
|
|
if (!(cont & MASK_WATER))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ent->waterlevel = 2;
|
|
point[2] += 22;
|
|
cont = gi.pointcontents(point);
|
|
|
|
if (cont & MASK_WATER)
|
|
{
|
|
ent->waterlevel = 3;
|
|
}
|
|
}
|
|
|
|
void
|
|
M_WorldEffects(edict_t *ent)
|
|
{
|
|
int dmg;
|
|
|
|
if (!ent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ent->health > 0)
|
|
{
|
|
if (!(ent->flags & FL_SWIM))
|
|
{
|
|
if (ent->waterlevel < 3)
|
|
{
|
|
ent->air_finished = level.time + 12;
|
|
}
|
|
else if (ent->air_finished < level.time)
|
|
{
|
|
/* drown! */
|
|
if (ent->pain_debounce_time < level.time)
|
|
{
|
|
dmg = 2 + 2 * floor(level.time - ent->air_finished);
|
|
|
|
if (dmg > 15)
|
|
{
|
|
dmg = 15;
|
|
}
|
|
|
|
T_Damage(ent, world, world, vec3_origin, ent->s.origin,
|
|
vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
|
|
ent->pain_debounce_time = level.time + 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ent->waterlevel > 0)
|
|
{
|
|
ent->air_finished = level.time + 9;
|
|
}
|
|
else if (ent->air_finished < level.time)
|
|
{
|
|
/* suffocate! */
|
|
if (ent->pain_debounce_time < level.time)
|
|
{
|
|
dmg = 2 + 2 * floor(level.time - ent->air_finished);
|
|
|
|
if (dmg > 15)
|
|
{
|
|
dmg = 15;
|
|
}
|
|
|
|
T_Damage(ent, world, world, vec3_origin, ent->s.origin,
|
|
vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
|
|
ent->pain_debounce_time = level.time + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ent->waterlevel == 0)
|
|
{
|
|
if (ent->flags & FL_INWATER)
|
|
{
|
|
gi.sound(ent, CHAN_BODY, gi.soundindex(
|
|
"player/watr_out.wav"), 1, ATTN_NORM, 0);
|
|
ent->flags &= ~FL_INWATER;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
|
|
{
|
|
if (ent->damage_debounce_time < level.time)
|
|
{
|
|
ent->damage_debounce_time = level.time + 0.2;
|
|
T_Damage(ent, world, world, vec3_origin, ent->s.origin,
|
|
vec3_origin, 10 * ent->waterlevel, 0, 0, MOD_LAVA);
|
|
}
|
|
}
|
|
|
|
if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME) && !(ent->svflags & SVF_DEADMONSTER))
|
|
{
|
|
if (ent->damage_debounce_time < level.time)
|
|
{
|
|
ent->damage_debounce_time = level.time + 1;
|
|
T_Damage(ent, world, world, vec3_origin, ent->s.origin,
|
|
vec3_origin, 4 * ent->waterlevel, 0, 0, MOD_SLIME);
|
|
}
|
|
}
|
|
|
|
if (!(ent->flags & FL_INWATER))
|
|
{
|
|
if (!(ent->svflags & SVF_DEADMONSTER))
|
|
{
|
|
if (ent->watertype & CONTENTS_LAVA)
|
|
{
|
|
if (random() <= 0.5)
|
|
{
|
|
gi.sound(ent, CHAN_BODY, gi.soundindex(
|
|
"player/lava1.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
else
|
|
{
|
|
gi.sound(ent, CHAN_BODY, gi.soundindex(
|
|
"player/lava2.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
else if (ent->watertype & CONTENTS_SLIME)
|
|
{
|
|
gi.sound(ent, CHAN_BODY, gi.soundindex(
|
|
"player/watr_in.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
else if (ent->watertype & CONTENTS_WATER)
|
|
{
|
|
gi.sound(ent, CHAN_BODY, gi.soundindex(
|
|
"player/watr_in.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
|
|
ent->flags |= FL_INWATER;
|
|
ent->damage_debounce_time = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
M_droptofloor(edict_t *ent)
|
|
{
|
|
vec3_t end;
|
|
trace_t trace;
|
|
|
|
if (!ent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ent->s.origin[2] += 1;
|
|
VectorCopy(ent->s.origin, end);
|
|
end[2] -= 256;
|
|
|
|
trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, end,
|
|
ent, MASK_MONSTERSOLID);
|
|
|
|
if ((trace.fraction == 1) || trace.allsolid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorCopy(trace.endpos, ent->s.origin);
|
|
|
|
gi.linkentity(ent);
|
|
M_CheckGround(ent);
|
|
M_CatagorizePosition(ent);
|
|
}
|
|
|
|
void
|
|
M_SetEffects(edict_t *ent)
|
|
{
|
|
if (!ent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ent->s.effects &= ~(EF_COLOR_SHELL | EF_POWERSCREEN);
|
|
ent->s.renderfx &= ~(RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE);
|
|
|
|
if (ent->monsterinfo.aiflags & AI_RESURRECTING)
|
|
{
|
|
ent->s.effects |= EF_COLOR_SHELL;
|
|
ent->s.renderfx |= RF_SHELL_RED;
|
|
}
|
|
|
|
if (ent->health <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ent->powerarmor_time > level.time)
|
|
{
|
|
if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
|
|
{
|
|
ent->s.effects |= EF_POWERSCREEN;
|
|
}
|
|
else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
|
|
{
|
|
ent->s.effects |= EF_COLOR_SHELL;
|
|
ent->s.renderfx |= RF_SHELL_GREEN;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
M_MoveFrame(edict_t *self)
|
|
{
|
|
mmove_t *move;
|
|
int index;
|
|
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
move = self->monsterinfo.currentmove;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
|
|
if ((self->monsterinfo.nextframe) &&
|
|
(self->monsterinfo.nextframe >= move->firstframe) &&
|
|
(self->monsterinfo.nextframe <= move->lastframe))
|
|
{
|
|
self->s.frame = self->monsterinfo.nextframe;
|
|
self->monsterinfo.nextframe = 0;
|
|
}
|
|
else
|
|
{
|
|
if (self->s.frame == move->lastframe)
|
|
{
|
|
if (move->endfunc)
|
|
{
|
|
move->endfunc(self);
|
|
|
|
/* regrab move, endfunc is very likely to change it */
|
|
move = self->monsterinfo.currentmove;
|
|
|
|
/* check for death */
|
|
if (self->svflags & SVF_DEADMONSTER)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((self->s.frame < move->firstframe) ||
|
|
(self->s.frame > move->lastframe))
|
|
{
|
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
|
self->s.frame = move->firstframe;
|
|
}
|
|
else
|
|
{
|
|
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
|
|
{
|
|
self->s.frame++;
|
|
|
|
if (self->s.frame > move->lastframe)
|
|
{
|
|
self->s.frame = move->firstframe;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
index = self->s.frame - move->firstframe;
|
|
|
|
if (move->frame[index].aifunc)
|
|
{
|
|
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
|
|
{
|
|
move->frame[index].aifunc(self,
|
|
move->frame[index].dist * self->monsterinfo.scale);
|
|
}
|
|
else
|
|
{
|
|
move->frame[index].aifunc(self, 0);
|
|
}
|
|
}
|
|
|
|
if (move->frame[index].thinkfunc)
|
|
{
|
|
move->frame[index].thinkfunc(self);
|
|
}
|
|
}
|
|
|
|
void
|
|
monster_think(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
M_MoveFrame(self);
|
|
|
|
if (self->linkcount != self->monsterinfo.linkcount)
|
|
{
|
|
self->monsterinfo.linkcount = self->linkcount;
|
|
M_CheckGround(self);
|
|
}
|
|
|
|
M_CatagorizePosition(self);
|
|
M_WorldEffects(self);
|
|
M_SetEffects(self);
|
|
}
|
|
|
|
/*
|
|
* Using a monster makes it angry
|
|
* at the current activator
|
|
*/
|
|
void
|
|
monster_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
|
|
{
|
|
if (!self || !activator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->enemy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->health <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (activator->flags & FL_NOTARGET)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* delay reaction so if the monster is
|
|
teleported, its sound is still heard */
|
|
self->enemy = activator;
|
|
FoundTarget(self);
|
|
}
|
|
|
|
void
|
|
monster_triggered_spawn(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->s.origin[2] += 1;
|
|
KillBox(self);
|
|
|
|
self->solid = SOLID_BBOX;
|
|
self->movetype = MOVETYPE_STEP;
|
|
self->svflags &= ~SVF_NOCLIENT;
|
|
self->air_finished = level.time + 12;
|
|
gi.linkentity(self);
|
|
|
|
monster_start_go(self);
|
|
|
|
if (self->enemy && !(self->spawnflags & 1) &&
|
|
!(self->enemy->flags & FL_NOTARGET))
|
|
{
|
|
FoundTarget(self);
|
|
}
|
|
else
|
|
{
|
|
self->enemy = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
monster_triggered_spawn_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
|
|
{
|
|
if (!self || !activator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* we have a one frame delay here so we
|
|
don't telefrag the guy who activated us */
|
|
self->think = monster_triggered_spawn;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
|
|
if (activator->client)
|
|
{
|
|
self->enemy = activator;
|
|
}
|
|
|
|
self->use = monster_use;
|
|
}
|
|
|
|
void
|
|
monster_triggered_start(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->solid = SOLID_NOT;
|
|
self->movetype = MOVETYPE_NONE;
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->nextthink = 0;
|
|
self->use = monster_triggered_spawn_use;
|
|
}
|
|
|
|
/*
|
|
* When a monster dies, it fires all of its targets
|
|
* with the current enemy as activator.
|
|
*/
|
|
void
|
|
monster_death_use(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->flags &= ~(FL_FLY | FL_SWIM);
|
|
self->monsterinfo.aiflags &= AI_GOOD_GUY;
|
|
|
|
if (self->item)
|
|
{
|
|
Drop_Item(self, self->item);
|
|
self->item = NULL;
|
|
}
|
|
|
|
if (self->deathtarget)
|
|
{
|
|
self->target = self->deathtarget;
|
|
}
|
|
|
|
if (!self->target)
|
|
{
|
|
return;
|
|
}
|
|
|
|
G_UseTargets(self, self->enemy);
|
|
}
|
|
|
|
/* ================================================================== */
|
|
|
|
qboolean
|
|
monster_start(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (deathmatch->value)
|
|
{
|
|
G_FreeEdict(self);
|
|
return false;
|
|
}
|
|
|
|
if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
|
|
{
|
|
self->spawnflags &= ~4;
|
|
self->spawnflags |= 1;
|
|
}
|
|
|
|
if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
|
|
{
|
|
level.total_monsters++;
|
|
}
|
|
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->svflags |= SVF_MONSTER;
|
|
self->s.renderfx |= RF_FRAMELERP;
|
|
self->takedamage = DAMAGE_AIM;
|
|
self->air_finished = level.time + 12;
|
|
self->use = monster_use;
|
|
|
|
if(!self->max_health)
|
|
{
|
|
self->max_health = self->health;
|
|
}
|
|
|
|
self->clipmask = MASK_MONSTERSOLID;
|
|
|
|
self->s.skinnum = 0;
|
|
self->deadflag = DEAD_NO;
|
|
self->svflags &= ~SVF_DEADMONSTER;
|
|
|
|
if (!self->monsterinfo.checkattack)
|
|
{
|
|
self->monsterinfo.checkattack = M_CheckAttack;
|
|
}
|
|
|
|
VectorCopy(self->s.origin, self->s.old_origin);
|
|
|
|
if (st.item)
|
|
{
|
|
self->item = FindItemByClassname(st.item);
|
|
|
|
if (!self->item)
|
|
{
|
|
gi.dprintf("%s at %s has bad item: %s\n", self->classname,
|
|
vtos(self->s.origin), st.item);
|
|
}
|
|
}
|
|
|
|
/* randomize what frame they start on */
|
|
if (self->monsterinfo.currentmove)
|
|
{
|
|
self->s.frame = self->monsterinfo.currentmove->firstframe +
|
|
(randk() % (self->monsterinfo.currentmove->lastframe -
|
|
self->monsterinfo.currentmove->firstframe + 1));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
monster_start_go(edict_t *self)
|
|
{
|
|
vec3_t v;
|
|
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->health <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* check for target to combat_point and change to combattarget */
|
|
if (self->target)
|
|
{
|
|
qboolean notcombat;
|
|
qboolean fixup;
|
|
edict_t *target;
|
|
|
|
target = NULL;
|
|
notcombat = false;
|
|
fixup = false;
|
|
|
|
while ((target = G_Find(target, FOFS(targetname), self->target)) != NULL)
|
|
{
|
|
if (strcmp(target->classname, "point_combat") == 0)
|
|
{
|
|
self->combattarget = self->target;
|
|
fixup = true;
|
|
}
|
|
else
|
|
{
|
|
notcombat = true;
|
|
}
|
|
}
|
|
|
|
if (notcombat && self->combattarget)
|
|
{
|
|
gi.dprintf("%s at %s has target with mixed types\n",
|
|
self->classname, vtos(self->s.origin));
|
|
}
|
|
|
|
if (fixup)
|
|
{
|
|
self->target = NULL;
|
|
}
|
|
}
|
|
|
|
/* validate combattarget */
|
|
if (self->combattarget)
|
|
{
|
|
edict_t *target;
|
|
|
|
target = NULL;
|
|
|
|
while ((target = G_Find(target, FOFS(targetname),
|
|
self->combattarget)) != NULL)
|
|
{
|
|
if (strcmp(target->classname, "point_combat") != 0)
|
|
{
|
|
gi.dprintf( "%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
|
|
self->classname, (int)self->s.origin[0], (int)self->s.origin[1],
|
|
(int)self->s.origin[2], self->combattarget, target->classname,
|
|
(int)target->s.origin[0], (int)target->s.origin[1],
|
|
(int)target->s.origin[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self->target)
|
|
{
|
|
self->goalentity = self->movetarget = G_PickTarget(self->target);
|
|
|
|
if (!self->movetarget)
|
|
{
|
|
gi.dprintf("%s can't find target %s at %s\n", self->classname,
|
|
self->target, vtos(self->s.origin));
|
|
self->target = NULL;
|
|
self->monsterinfo.pausetime = 100000000;
|
|
self->monsterinfo.stand(self);
|
|
}
|
|
else if (strcmp(self->movetarget->classname, "path_corner") == 0)
|
|
{
|
|
VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
|
|
self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
|
|
self->monsterinfo.walk(self);
|
|
self->target = NULL;
|
|
}
|
|
else
|
|
{
|
|
self->goalentity = self->movetarget = NULL;
|
|
self->monsterinfo.pausetime = 100000000;
|
|
self->monsterinfo.stand(self);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->monsterinfo.pausetime = 100000000;
|
|
self->monsterinfo.stand(self);
|
|
}
|
|
|
|
self->think = monster_think;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void
|
|
walkmonster_start_go(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!(self->spawnflags & 2) && (level.time < 1))
|
|
{
|
|
M_droptofloor(self);
|
|
|
|
if (self->groundentity)
|
|
{
|
|
if (!M_walkmove(self, 0, 0))
|
|
{
|
|
gi.dprintf("%s in solid at %s\n", self->classname,
|
|
vtos(self->s.origin));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!self->yaw_speed)
|
|
{
|
|
self->yaw_speed = 20;
|
|
}
|
|
|
|
self->viewheight = 25;
|
|
|
|
monster_start_go(self);
|
|
|
|
if (self->spawnflags & 2)
|
|
{
|
|
monster_triggered_start(self);
|
|
}
|
|
}
|
|
|
|
void
|
|
walkmonster_start(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->think = walkmonster_start_go;
|
|
monster_start(self);
|
|
}
|
|
|
|
void
|
|
flymonster_start_go(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!M_walkmove(self, 0, 0))
|
|
{
|
|
gi.dprintf("%s in solid at %s\n", self->classname, vtos(self->s.origin));
|
|
}
|
|
|
|
if (!self->yaw_speed)
|
|
{
|
|
self->yaw_speed = 10;
|
|
}
|
|
|
|
self->viewheight = 25;
|
|
|
|
monster_start_go(self);
|
|
|
|
if (self->spawnflags & 2)
|
|
{
|
|
monster_triggered_start(self);
|
|
}
|
|
}
|
|
|
|
void
|
|
flymonster_start(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->flags |= FL_FLY;
|
|
self->think = flymonster_start_go;
|
|
monster_start(self);
|
|
}
|
|
|
|
void
|
|
swimmonster_start_go(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!self->yaw_speed)
|
|
{
|
|
self->yaw_speed = 10;
|
|
}
|
|
|
|
self->viewheight = 10;
|
|
|
|
monster_start_go(self);
|
|
|
|
if (self->spawnflags & 2)
|
|
{
|
|
monster_triggered_start(self);
|
|
}
|
|
}
|
|
|
|
void
|
|
swimmonster_start(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->flags |= FL_SWIM;
|
|
self->think = swimmonster_start_go;
|
|
monster_start(self);
|
|
}
|