heretic2-sdk/Toolkit/Programming/GameCode/game/g_monster.c

1987 lines
54 KiB
C
Raw Normal View History

1998-11-24 00:00:00 +00:00
#include "g_monster.h"
#include "m_stats.h"
#include "g_local.h"
#include "FX.h"
#include "g_DefaultMessageHandler.h"
#include "vector.h"
#include "g_playstats.h"
#include "random.h"
//JWEIER START INCLUDES
#include "g_misc.h"
#include "utilities.h"
//JWEIER END INCLUDES
#include "g_HitLocation.h"
#include "mg_guide.h"
#define FALLDAMAGE_MODIFIER 8
qboolean defaultMonsterAlerted (edict_t *self, alertent_t *alerter, edict_t *enemy);
// ************************************************************************************************
// MonsterHealth
// -------------
// Returns the modified health for a monster based on the number of players who will be in a game.
// ************************************************************************************************
int MonsterHealth(int health)
{
1999-03-18 00:00:00 +00:00
if (skill->value > 1.0)
health += (int)((float)health * (skill->value - 1.0) / 2.0); // 150% on Armageddon (skill 3)
1998-11-24 00:00:00 +00:00
return(health+(health*((game.maxclients-1)*0.25))); // 175% with 4 players.
}
/*-------------------------------------------------------------------------
DeadMsgHandler
-------------------------------------------------------------------------*/
void DeadMsgHandler(edict_t *self, G_Message_t *msg)
{
G_MsgReceiver_t receiver;
if(msg->ID!=MSG_DEATH_PAIN)
return;
receiver = classStatics[self->classID].msgReceivers[msg->ID];
if(receiver)
{
receiver(self, msg);
}
else
{
// if and when there are a good number of defaults, change the NULL to be an Empty
// function, overall that should be faster to just always call the function then
// do the check
receiver = DefaultMessageReceivers[msg->ID];
if(receiver)
{
DefaultMessageReceivers[msg->ID](self, msg);
}
}
}
/*-------------------------------------------------------------------------
DyingMsgHandler
Allows only dimemberment to still be called
-------------------------------------------------------------------------*/
void DyingMsgHandler(edict_t *self, G_Message_t *msg)
{
G_MsgReceiver_t receiver;
if(msg->ID!=MSG_DISMEMBER)
return;
receiver = classStatics[self->classID].msgReceivers[msg->ID];
if(receiver)
{
receiver(self, msg);
}
else
{
// if and when there are a good number of defaults, change the NULL to be an Empty
// function, overall that should be faster to just always call the function then
// do the check
receiver = DefaultMessageReceivers[msg->ID];
if(receiver)
{
DefaultMessageReceivers[msg->ID](self, msg);
}
}
}
/*-------------------------------------------------------------------------
PauseTime
-------------------------------------------------------------------------*/
void PauseTime(edict_t *self, float time)
{
self->monsterinfo.pausetime = level.time + time;
}
/*-------------------------------------------------------------------------
AttackFinished
-------------------------------------------------------------------------*/
void AttackFinished (edict_t *self, float time)
{
self->monsterinfo.attack_finished = level.time + time;
}
/*-------------------------------------------------------------------------
M_CheckGround
-------------------------------------------------------------------------*/
void M_CheckGround (edict_t *ent)
{
vec3_t point;
trace_t trace;
if (ent->flags & (FL_SWIM|FL_FLY))
return;
if (ent->velocity[2] >= 50)
{
// 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;
gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID,&trace);
// check steepness
if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
{
// ent->groundentity = NULL;
return;
}
// ent->groundentity = trace.ent;
// ent->groundentity_linkcount = trace.ent->linkcount;
// if (!trace.startsolid && !trace.allsolid)
// VectorCopy (trace.endpos, ent->s.origin);
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;
}
}
/*-------------------------------------------------------------------------
M_CatagorizePosition
-------------------------------------------------------------------------*/
void M_MonsterCatPos (edict_t *ent)
{
vec3_t point;
int cont;
//
// get waterlevel
//
point[0] = ent->s.origin[0];
point[1] = ent->s.origin[1];
point[2] = ent->s.origin[2] + ent->mins[2] + 1;
cont = gi.pointcontents (point);
if (!(cont & MASK_WATER))
{
ent->waterlevel = 0;
ent->watertype = 0;
return;
}
point[2] -= 1;
ent->watertype = cont;
ent->waterlevel = 1;//below knees
point[2] += ent->size[2] * 0.25;// quarter of way up
cont = gi.pointcontents (point);
if (!(cont & MASK_WATER))
return;
ent->waterlevel = 2;//between knees and head
point[2] = ent->absmax[2];//over head
cont = gi.pointcontents (point);
if (cont & MASK_WATER)
ent->waterlevel = 3;//all the way in
}
void M_CatagorizePosition (edict_t *ent)
{
vec3_t point;
int cont;
if(!ent->client)
{
M_MonsterCatPos(ent);
return;
}
//
// get waterlevel
//
point[0] = ent->s.origin[0];
point[1] = ent->s.origin[1];
point[2] = ent->s.origin[2] + ent->mins[2] + 1;
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;
}
/*-------------------------------------------------------------------------
M_WorldEffects
-------------------------------------------------------------------------*/
void M_WorldEffects (edict_t *ent)
{
int dmg;
if (ent->health > 0)
{
if (!(ent->flags & FL_SWIM))
{
if (ent->waterlevel < 3 || (ent->monsterinfo.aiflags&AI_SWIM_OK))
{
ent->air_finished = level.time + M_HOLD_BREATH_TIME;
}
else if (ent->air_finished < level.time && !(ent->flags & FL_AMPHIBIAN))
{ // 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_SUFFOCATION,MOD_WATER);
ent->pain_debounce_time = level.time + 1;
}
}
}
else
{
if (ent->waterlevel > 0 || (ent->monsterinfo.aiflags&AI_SWIM_OK))
{
ent->air_finished = level.time + 9;
}
else if (ent->air_finished < level.time && !(ent->flags & FL_AMPHIBIAN))
{ // 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_SUFFOCATION,MOD_WATER);
ent->pain_debounce_time = level.time + 1;
}
}
}
}
if (ent->waterlevel == 0)
{
// INWATER is set whether in lava, slime or water.
if (ent->flags & FL_INWATER)
{
if (ent->flags & FL_INLAVA)
{
gi.sound (ent, CHAN_BODY, gi.soundindex("player/inlava.wav"), 1, ATTN_NORM, 0);
ent->flags &= ~FL_INLAVA;
}
else if (ent->flags & FL_INSLIME)
{
gi.sound (ent, CHAN_BODY, gi.soundindex("player/muckexit.wav"), 1, ATTN_NORM, 0);
ent->flags &= ~FL_INSLIME;
}
else
{
gi.sound (ent, CHAN_BODY, gi.soundindex("player/Water Exit.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, DAMAGE_LAVA,MOD_LAVA);
}
}
if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
{
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, DAMAGE_SLIME,MOD_SLIME);
}
}
if ( !(ent->flags & FL_INWATER) )
{
if (ent->watertype & CONTENTS_LAVA)
{
gi.sound (ent, CHAN_BODY, gi.soundindex("player/inlava.wav"), 1, ATTN_NORM, 0);
ent->flags |= FL_INLAVA;
}
else
if (ent->watertype & CONTENTS_SLIME)
{
gi.sound (ent, CHAN_BODY, gi.soundindex("player/muckin.wav"), 1, ATTN_NORM, 0);
ent->flags |= FL_INSLIME;
}
else
{
gi.sound (ent, CHAN_BODY, gi.soundindex("player/Water Enter.wav"), 1, ATTN_NORM, 0);
}
// INWATER is set whether in lava, slime or water.
ent->flags |= FL_INWATER;
ent->damage_debounce_time = 0;
}
}
/*-------------------------------------------------------------------------
M_droptofloor
-------------------------------------------------------------------------*/
void M_droptofloor (edict_t *ent)
{
vec3_t end;
trace_t trace;
if(Vec3IsZero(ent->mins)&&Vec3IsZero(ent->maxs))
{
gi.dprintf("ERROR : %s at %s called drop to floor before having size set\n", ent->classname, vtos(ent->s.origin));
if(ent->think == M_droptofloor)
ent->think = NULL;//don't try again
return;
}
ent->nextthink = level.time + FRAMETIME;
ent->s.origin[2] += 1.0;
VectorCopy (ent->s.origin, end);
end[2] -= 256;
gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID,&trace);
if(trace.allsolid||trace.startsolid)
{
gi.dprintf("ERROR : Object (%s) at %s started in solid\n", ent->classname, vtos(ent->s.origin));
if(ent->think == M_droptofloor)
{
ent->think = NULL;//don't try again
gi.linkentity (ent);
M_CatagorizePosition (ent);
}
return;
}
if(trace.fraction == 1.0)
{
gi.dprintf("ERROR : Object (%s) more than 256 off ground, waiting to fall\n", ent->classname);
return;
}
if(MGAI_DEBUG)
gi.dprintf("%s at %s dropped to floor at %s\n", ent->classname, vtos(ent->s.origin), vtos(trace.endpos));
VectorCopy (trace.endpos, ent->s.origin);
gi.linkentity (ent);
M_CheckGround (ent);
M_CatagorizePosition (ent);
// No need to think anymore if on the ground
ent->think = NULL;
}
/* ------------------------------------------------------------------------------
M_MoveFrame - unless a nextframe is specified, advance to the next frame listed in
the Animation Frame Array. Execute any aifunction or think function specified
with the given frame.
--------------------------------------------------------------------------------*/
extern qboolean MonsterAdvanceFrame;
void M_MoveFrame (edict_t *self)
{
animmove_t *move;
int index;
qboolean wasnewphys = false;
if(self->movetype < NUM_PHYSICSTYPES)
wasnewphys = true;
move = self->monsterinfo.currentmove;
if (move == NULL)
{ // if move is NULL, then this monster needs to have an anim set on it or all is lost.
#ifdef _DEVEL
gi.dprintf("MONSTER: '%s', at %s has no move pointer. Setting to move zero.\n", self->classname, self->s.origin);
#endif
self->think = NULL;
self->nextthink = -1;
return;
}
self->nextthink = level.time + self->monsterinfo.thinkinc;
//There is a voice sound waiting to play
if (self->monsterinfo.sound_pending && self->monsterinfo.sound_start <= level.time)
{
//Post a message and make the monster speak
QPostMessage(self, MSG_VOICE_PUPPET, PRI_DIRECTIVE, "i", self->monsterinfo.sound_pending);
//Sound queue is free
self->monsterinfo.sound_pending = 0;
}
// If this is set, the monster runs absolutely no animations or ai
if (sv_freezemonsters->value != 0 && !MonsterAdvanceFrame)
return;
// Forcing the next frame index - usually the start of an animation
if (self->monsterinfo.nextframeindex > -1)
{
self->monsterinfo.currframeindex = self->monsterinfo.nextframeindex;
self->monsterinfo.nextframeindex = -1;
}
else
{
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
{
++self->monsterinfo.currframeindex;
if (self->monsterinfo.currframeindex >= move->numframes)
self->monsterinfo.currframeindex = 0;
}
//
if (self->monsterinfo.currframeindex == (move->numframes - 1))
{
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;
}
}
}
index = self->monsterinfo.currframeindex;
self->s.frame = move->frame[index].framenum;
//this is consistent with the animmove_t in the monster anims.
//currently all of the *real* movement happens in the
//"actionfunc" instead of the move func
if(!(self->monsterinfo.aiflags & AI_DONT_THINK))
{
if(move->frame[index].movefunc)
{
move->frame[index].movefunc(self, move->frame[index].var1, move->frame[index].var2,
move->frame[index].var3);
}
if (move->frame[index].actionfunc)
{
if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
{//Put scaling into SV_Movestep since this isn't ALWAYS the movement function
move->frame[index].actionfunc (self, move->frame[index].var4);//* self->monsterinfo.scale);
}
else
move->frame[index].actionfunc (self, 0);
}
if (move->frame[index].thinkfunc)
move->frame[index].thinkfunc (self);
}
if(wasnewphys)
{
assert(self->movetype < NUM_PHYSICSTYPES);
}
}
/*-------------------------------------------------------------------------
monster_think
-------------------------------------------------------------------------*/
void monster_think (edict_t *self)
{
M_MoveFrame (self);
/*
if (self->linkcount != self->monsterinfo.linkcount)
{
self->monsterinfo.linkcount = self->linkcount;
M_CheckGround (self);
}
*/
M_CatagorizePosition (self);
M_WorldEffects (self);
}
/*
================
monster_use
Using a monster makes it angry at the current activator
================
*/
void monster_use (edict_t *self, edict_t *other, edict_t *activator)
{
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->targetname = "";//so can only be used once...???
self->enemy = activator;
FoundTarget (self, true);
}
void monster_start_go (edict_t *self);
/*-------------------------------------------------------------------------
monster_triggered_spawn
-------------------------------------------------------------------------*/
void monster_triggered_spawn (edict_t *self)
{
vec3_t pos;
self->s.origin[2] += 1;
KillBox (self);
self->solid = SOLID_BBOX;
self->movetype = PHYSICSTYPE_STEP;
self->svflags &= ~SVF_NOCLIENT;
self->air_finished = level.time + M_HOLD_BREATH_TIME;
gi.linkentity (self);
monster_start_go (self);
if((self->classID==CID_ASSASSIN) && (self->spawnflags & MSF_ASS_TPORTAMBUSH))
{
FoundTarget (self, true);
VectorCopy(self->s.origin, pos);
pos[2]+=self->mins[2];
gi.CreateEffect(NULL, FX_TPORTSMOKE, 0, pos, "");
}
else if (self->enemy && !(self->spawnflags & MSF_AMBUSH) && !(self->enemy->flags & FL_NOTARGET))
{
FoundTarget (self, true);
}
else
{
self->enemy = NULL;
}
}
/*-------------------------------------------------------------------------
monster_triggered_spawn_use
-------------------------------------------------------------------------*/
void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
{
// we have a one frame delay here so we don't telefrag the guy who activated us
self->spawnflags &= ~MSF_ASLEEP;
self->think = monster_triggered_spawn;
self->nextthink = level.time + FRAMETIME;
if (activator->client)
self->enemy = activator;
self->use = monster_use;
}
/*-------------------------------------------------------------------------
monster_triggered_start
-------------------------------------------------------------------------*/
void monster_triggered_start (edict_t *self)
{
self->solid = SOLID_NOT;
self->movetype = PHYSICSTYPE_NONE;
self->svflags |= SVF_NOCLIENT;
self->nextthink = 0;
self->use = monster_triggered_spawn_use;
}
/*
================
monster_death_use
When a monster dies, it fires all of its targets with the current
enemy as activator.
================
*/
void monster_death_use (edict_t *self)
{
self->flags &= ~(FL_FLY|FL_SWIM);
// self->monsterinfo.aiflags &= AI_GOOD_GUY;//WHY mask out everything above this flag???
if (self->item)
{
Drop_Item (self, self->item);
self->item = NULL;
}
if (self->target)
G_UseTargets (self, self->enemy);
if (self->deathtarget)
{
self->target = self->deathtarget;
G_UseTargets (self, self->enemy);
}
}
//============================================================================
void MG_CheckInGround (edict_t *self)
{
if(gi.pointcontents(self->s.origin)&CONTENTS_SOLID)
{
1999-03-18 00:00:00 +00:00
gi.dprintf("%s's origin at %s in solid!!!\n", self->classname, vtos(self->s.origin));
1998-11-24 00:00:00 +00:00
}
else
{//check down against world- does not check against entities! Does not check up against cieling (why would they put one close to a cieling???)
vec3_t top, bottom, mins, maxs;
trace_t trace;
VectorCopy(self->s.origin, top);
VectorCopy(self->s.origin, bottom);
top[2] += self->maxs[2] - 1;
bottom[2] += self->mins[2];
VectorSet(mins, self->mins[0], self->mins[1], 0);
VectorSet(maxs, self->maxs[0], self->maxs[1], 1);
gi.trace(top, mins, maxs, bottom, self, MASK_SOLID,&trace);
if(trace.allsolid || trace.startsolid)//monster in solid, can't be fixed
{
1999-03-18 00:00:00 +00:00
gi.dprintf("top of %s at %s in solid architecture(%s)!!!\n", self->classname, vtos(self->s.origin), trace.ent->classname);
1998-11-24 00:00:00 +00:00
}
else if(trace.fraction < 1.0f)
{//buoy is in the ground
VectorCopy(trace.endpos, bottom);
bottom[2] -= self->mins[2];
if((int)(trace.endpos[2]) != (int)(self->s.origin[2]))
1999-03-18 00:00:00 +00:00
gi.dprintf("%s at %s was in ground(%s), moved to %s...!!!\n", self->classname, vtos(self->s.origin), trace.ent->classname, vtos(bottom));
1998-11-24 00:00:00 +00:00
VectorCopy(bottom, self->s.origin);
}
//fixme- check against other ents too? same trace or second one?
}
}
/*-------------------------------------------------------------------------
monster_start
-------------------------------------------------------------------------*/
qboolean monster_start (edict_t *self)
{
1999-03-18 00:00:00 +00:00
if ((deathmatch->value == 1) && !((int)sv_cheats->value & self_spawn))
1998-11-24 00:00:00 +00:00
{
G_FreeEdict (self);
return false;
}
if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
level.total_monsters++;
self->monsterinfo.awake = false;
self->nextthink = level.time + FRAMETIME;
self->svflags |= SVF_MONSTER;
self->s.renderfx |= RF_FRAMELERP;
self->takedamage = DAMAGE_AIM;
self->air_finished = level.time + M_HOLD_BREATH_TIME;
self->use = monster_use;
self->touch = M_Touch;
self->monsterinfo.alert = defaultMonsterAlerted;//I don't understand why I get a warning here...
self->max_health = self->health;
self->clipmask = MASK_MONSTERSOLID;
if(!self->materialtype)
self->materialtype = MAT_FLESH;
// Stop the camera clipping with monsters, except the trial beast.
if(self->classID!=CID_TBEAST)
self->s.effects|=EF_CAMERA_NO_CLIP;
if (G_MonsterShadow[self->classID].useShadow)
{
gi.CreateEffect(&self->s,
FX_SHADOW,
CEF_OWNERS_ORIGIN,
self->s.origin,
"f",
G_MonsterShadow[self->classID].scale);
}
self->s.skinnum = 0;
self->deadflag = DEAD_NO;
self->svflags &= ~SVF_DEADMONSTER;
self->monsterinfo.thinkinc = MONSTER_THINK_INC;//FRAMETIME;
self->monsterinfo.nextframeindex = -1;
if (!self->monsterinfo.checkattack)
self->monsterinfo.checkattack = M_CheckAttack;
VectorCopy (self->s.origin, self->s.old_origin);
if (st.item)
{
1999-03-18 00:00:00 +00:00
self->item = P_FindItemByClassname (st.item);
1998-11-24 00:00:00 +00:00
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 + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
if (!self->mass)
self->mass = 200;
self->s.frame = 1;
self->oldenemy_debounce_time = -1;
return true;
}
void MG_BBoxAndOriginAdjustForScale (edict_t *self)
{
float o_mins2;
if(!self->s.scale)
{
if(!self->monsterinfo.scale)
self->s.scale = self->monsterinfo.scale = 1.0f;
}
else if(!self->monsterinfo.scale)
self->monsterinfo.scale = self->s.scale;
o_mins2 = self->mins[2];
Vec3ScaleAssign(self->s.scale, self->mins);
Vec3ScaleAssign(self->s.scale, self->maxs);
self->s.origin[2] += o_mins2 - self->mins[2];
gi.linkentity(self);
}
/*-------------------------------------------------------------------------
monster_start_go
-------------------------------------------------------------------------*/
void monster_start_go (edict_t *self)
{
vec3_t v;
float volume;
self->nextthink = level.time + FRAMETIME;
if (self->health <= 0)
return;
MG_BBoxAndOriginAdjustForScale(self);
MG_CheckInGround(self);
if(!self->mass)
self->mass = 100;
if(self->s.scale)
self->mass *= self->s.scale;
if(self->spawnflags & MSF_COWARD)//start off running away- FIXME: let them specify a flee_time and use AI_FLEE if one is set? Would anyone ever use this?!?!?
self->monsterinfo.aiflags |= AI_COWARD;
if(self->spawnflags&MSF_STALK)//stalks enemies- only approaches and attacks from behind
self->ai_mood_flags |= AI_MOOD_FLAG_BACKSTAB;
if(self->spawnflags&MSF_MELEE_LEAD)//lead enemies in melee and tries to cut them off
self->ai_mood_flags |= AI_MOOD_FLAG_PREDICT;
if(!self->wakeup_distance)
self->wakeup_distance = MAX_SIGHT_PLAYER_DIST;
volume = VectorLength(self->size);
if(volume < 32)
self->svflags |= SVF_DO_NO_IMPACT_DMG;
self->jump_time = level.time + 2;//so they don't take damage from the fall after spawning...
// check for target to combat_point and change to combattarget
self->monsterinfo.coop_check_debounce_time = 0;
self->monsterinfo.pausetime = -1;
if(self->enemy)
{//spawned mad
FoundTarget(self, false);
}
else
{
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;
if (!self->monsterinfo.c_mode) // Not in cinematic mode
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
else
QPostMessage(self, MSG_C_IDLE1, PRI_DIRECTIVE, "iiige",0,0,0,NULL,NULL);
}
else if (strcmp (self->movetarget->classname, "path_corner") == 0)
{
if(self->classID != CID_SERAPH_OVERLORD)
{
VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
gi.dprintf("Monster start go to walk\n");
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
self->monsterinfo.pausetime = 0;
}
else
{
self->goalentity = self->movetarget = NULL;
self->monsterinfo.pausetime = 100000000;
if (!self->monsterinfo.c_mode) // Not in cinematic mode
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
else
QPostMessage(self, MSG_C_IDLE1, PRI_DIRECTIVE, "iiige",0,0,0,NULL,NULL);
}
self->target = NULL;
}
else
{
self->goalentity = self->movetarget = NULL;
self->monsterinfo.pausetime = 100000000;
if (!self->monsterinfo.c_mode) // Not in cinematic mode
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
else
QPostMessage(self, MSG_C_IDLE1, PRI_DIRECTIVE, "iiige",0,0,0,NULL,NULL);
}
}
else
{
self->monsterinfo.pausetime = 100000000;
if (self->monsterinfo.aiflags & AI_EATING)
{
QPostMessage(self, MSG_EAT, PRI_DIRECTIVE, NULL);
}
else
{
if (!self->monsterinfo.c_mode) // Not in cinematic mode
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
else
QPostMessage(self, MSG_C_IDLE1, PRI_DIRECTIVE, "iiige",0,0,0,NULL,NULL);
}
}
}
self->think = monster_think;
}
/*-------------------------------------------------------------------------
walkmonster_start_go
-------------------------------------------------------------------------*/
void walkmonster_start_go (edict_t *self)
{
if (!(self->spawnflags & MSF_ASLEEP) && 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 & MSF_ASLEEP)
monster_triggered_start (self);
}
/*-------------------------------------------------------------------------
walkmonster_start
-------------------------------------------------------------------------*/
qboolean walkmonster_start (edict_t *self)
{
self->think = walkmonster_start_go;
if (!monster_start(self))
return false; // Failed initialization
else
return true;
}
/*-------------------------------------------------------------------------
flymonster_start_go
-------------------------------------------------------------------------*/
void flymonster_start_go (edict_t *self)
{
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;
if(!self->viewheight)
self->viewheight = 25;
monster_start_go (self);
if (self->spawnflags & MSF_ASLEEP)
monster_triggered_start (self);
}
/*-------------------------------------------------------------------------
flymonster_start
-------------------------------------------------------------------------*/
qboolean flymonster_start (edict_t *self)
{
self->flags |= FL_FLY;
self->think = flymonster_start_go;
if (!monster_start(self))
return false; // Failed initialization
else
return true;
}
/*-------------------------------------------------------------------------
swimmonster_start_go
-------------------------------------------------------------------------*/
void swimmonster_start_go (edict_t *self)
{
if (!self->yaw_speed)
self->yaw_speed = 10;
self->viewheight = 10;
monster_start_go (self);
if (self->spawnflags & MSF_ASLEEP)
monster_triggered_start (self);
}
/*-------------------------------------------------------------------------
swimmonster_start
-------------------------------------------------------------------------*/
qboolean swimmonster_start (edict_t *self)
{
self->flags |= FL_SWIM;
self->think = swimmonster_start_go;
M_CatagorizePosition (self);
if (!monster_start(self))
return false; // Failed initialization
else
return true;
}
/*
====================================================================
void pitch_roll_for_slope (edict_t *forwhom, vec3_t *slope)
MG
This will adjust the pitch and roll of a monster to match
a given slope - if a non-'0 0 0' slope is passed, it will
use that value, otherwise it will use the ground underneath
the monster. If it doesn't find a surface, it does nothinh\g
and returns.
====================================================================
*/
void pitch_roll_for_slope (edict_t *forwhom, vec3_t pass_slope)
{
vec3_t slope;
vec3_t nvf, ovf, ovr, startspot, endspot, new_angles = { 0, 0, 0 };
float pitch, mod, dot;
if(!pass_slope)
{
trace_t trace;
VectorCopy(forwhom->s.origin, startspot);
startspot[2] += forwhom->mins[2];
VectorCopy(startspot, endspot);
endspot[2] -= 300;
gi.trace(forwhom->s.origin, vec3_origin, vec3_origin, endspot, forwhom, MASK_SOLID,&trace);
// if(trace_fraction>0.05&&forwhom.movetype==MOVETYPE_STEP)
// forwhom.flags(-)FL_ONGROUND;
if(trace.fraction==1.0)
return;
if(!(&trace.plane))
return;
if(Vec3IsZero(trace.plane.normal))
return;
VectorCopy(trace.plane.normal, slope);
}
else
VectorCopy(pass_slope, slope);
//C stuff
AngleVectors(forwhom->s.angles, ovf, ovr, NULL);
vectoangles(slope, new_angles);
pitch = new_angles[PITCH] - 90;
new_angles[ROLL] = new_angles[PITCH] = 0;
AngleVectors(new_angles, nvf, NULL, NULL);
mod = DotProduct(nvf, ovr);
if(mod<0)
mod = -1;
else
mod = 1;
dot = DotProduct(nvf, ovf);
forwhom->s.angles[PITCH] = dot * pitch;
forwhom->s.angles[ROLL] = (1-Q_fabs(dot)) * pitch * mod;
}
//JWEIER START HELPER BLOCK
/*----------------------------------------------------------------------------------------------------------------
Monster Helper Functions
----------------------------------------------------------------------------------------------------------------*/
/*====================================================================================================================
void M_Touch
Tests to see whether the thing touching it is on its head, and if so, it tries to correct that situation.
Returns:
All parameters are passed to the function from the touch callback
======================================================================================================================*/
void M_Touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
vec3_t pos1, pos2, dir;
float zdiff, dropmag;
if ((other->svflags & SVF_MONSTER) || (!stricmp(other->classname, "player")))
{
VectorCopy(other->s.origin, pos1);
pos1[2] += other->mins[2];
VectorCopy(self->s.origin, pos2);
pos2[2] += self->maxs[2];
zdiff = pos1[2] - pos2[2];
// On top
if (zdiff >= 0 )
{
//We want the full magnitude of the vector, not just drop magnitude
VectorCopy(other->velocity, dir);
dropmag = VectorNormalize(dir);
//Do damage to the thing getting hit based on how hard the collision was
// T_Damage (self, other, other, dir, pos2, vec3_origin, 1 + (dropmag/FALLDAMAGE_MODIFIER), 0, DAMAGE_NO_BLOOD | DAMAGE_NO_KNOCKBACK);
if(!irand(0, 9))//10% chance
{
int damage;
damage = irand(1, 3);
#ifdef _DEVEL
gi.dprintf("%s doing %d damage to %s by standing on it\n", other->classname, damage, self->classname);
#endif
T_Damage (self, other, other, dir, pos2, vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK|DAMAGE_AVOID_ARMOR,MOD_DIED);
}
//Setup a random velocity for the first entity
other->velocity[0] = flrand(100.0, 150.0);
other->velocity[1] = flrand(100.0, 150.0);
other->velocity[2] += 110;
//Randomly reverse those random numbers
if (irand(0,1))
VectorScale(other->velocity, -1, other->velocity);
//Let the other entity move at its velocity
// other->groundentity = NULL;
}
}
}
/*====================================================================================================================
edict_t *M_CheckMeleeHit
Test a melee strike to see if it has hit its target.
Returns: "trace.ent" if a valid entity is struck (may not be intended target)
"NULL" if nothing hit
"attacker" if hit a wall, but no entity (used for spark effects)
attacker - the entity attacking
max_dist - the distance it checks forward
trace - passed parameter filled with the trace information (can be overkill, or very useful)
======================================================================================================================*/
edict_t *M_CheckMeleeHit( edict_t *attacker, float max_dist, trace_t *trace )
{
vec3_t targPos, vf;
//Trace forward the maximum amount of the melee distance
AngleVectors(attacker->s.angles, vf, NULL, NULL);
VectorMA(attacker->s.origin, max_dist, vf, targPos);
gi.trace(attacker->s.origin, attacker->mins, attacker->maxs, targPos, attacker, MASK_MONSTERSOLID,trace);
//Check to see if the trace was successful (miss)
if (trace->fraction < 1)
{
//Check an entity collision
if (trace->ent)
{
//Can take damage, so pass it back
if (trace->ent->takedamage)
{
//VectorCopy(trace.endpos, hitPos);
return trace->ent;
}
}
//Wasn't an entity, but we were blocked (world brush)
return attacker;
}
//Nothing found (missed)
return NULL;
}
/*====================================================================================================================
edict_t *M_CheckMeleeLineHit
Test a melee attack along a directed line.
Returns: "trace.ent" if a valid entity is struck (may not be intended target)
"NULL" if nothing hit
"attacker" if hit a wall, but no entity (used for spark effects)
attacker - what's attacking
start - starting position of the attack (offsets from the character (f,r,u)
end - ending position of the attack (offsets from the character (f,r,u)
mins, maxs - the size of the box to trace by
trace - passed parameter filled with the trace information (can be overkill, or very useful)
======================================================================================================================*/
edict_t *M_CheckMeleeLineHit( edict_t *attacker, vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, trace_t *trace, vec3_t direction)
{
vec3_t startv, endv, midv, swipedir,
vf, vr, vu;
//Apply the offsets to the positions passed
AngleVectors(attacker->s.angles, vf, vr, vu);
VectorMA(attacker->s.origin, start[0], vf, startv);
VectorMA(startv, start[1], vr, startv);
VectorMA(startv, start[2], vu, startv);
VectorMA(attacker->s.origin, end[0], vf, endv);
VectorMA(endv, end[1], vr, endv);
VectorMA(endv, end[2], vu, endv);
VectorSubtract(endv, startv, swipedir);
//make sure line to start of swipe is clear
gi.trace(attacker->s.origin, mins, maxs, startv, attacker, MASK_SHOT,trace);
if(trace->fraction == 1.0)//line to start of trace not blocked
gi.trace(startv, mins, maxs, endv, attacker, MASK_SHOT,trace);//MASK_MONSTERSOLID);
if(trace->fraction == 1.0)
{//hit nothing, trace to middle of line from origin to see if reached too far
VectorMA(startv, 0.5, swipedir, midv);
gi.trace(attacker->s.origin, mins, maxs, midv, attacker, MASK_SHOT,trace);
}
if(trace->fraction == 1.0)
{//Last Chance: trace to end of swipe from origin to see if reached too far
gi.trace(attacker->s.origin, mins, maxs, endv, attacker, MASK_SHOT,trace);
}
VectorNormalize(swipedir);
if(direction)
VectorCopy(swipedir, direction);
//Check to see if the trace was successful (miss)
if (trace->fraction < 1 || trace->startsolid || trace->allsolid)
{
//Check an entity collision
if (trace->ent)
{
//Can take damage, so pass it back
if (trace->ent->takedamage)
return trace->ent;
}
//Wasn't an entity, but we were blocked (world brush)
return attacker;
}
//Nothing found (missed)
return NULL;
}
/*====================================================================================================================
float M_DistanceToTarget
Make sure we have a live enemy, and then return a distance to him.
Returns: distance to target
self - what's attacking
target - what were looking for
======================================================================================================================*/
float M_DistanceToTarget ( edict_t *self, edict_t *target )
{
vec3_t vec;
assert (target);
VectorSubtract(target->s.origin, self->s.origin, vec);
return VectorLength(vec);
}
/*====================================================================================================================
qboolean M_ValidTarget
Make sure we have a live enemy, and then return a distance to it.
Returns: true if the enemy is valid, false if it is dead.
self - what's attacking
target - what were looking for
======================================================================================================================*/
qboolean M_ValidOldEnemy (edict_t *self)
{
if (!self->oldenemy)
return false;
if (self->oldenemy->health <= 0 || self->oldenemy == self)
return false;
if(self->monsterinfo.last_successful_enemy_tracking_time + MONSTER_SEARCH_TIME < level.time)
if(!visible(self, self->oldenemy))
return false;
self->monsterinfo.aiflags &= ~AI_STRAIGHT_TO_ENEMY;
self->enemy = self->goalentity = self->oldenemy;
self->oldenemy = NULL;
return true;
}
qboolean M_ValidTarget( edict_t *self, edict_t *target )
{
qboolean checkold = false;
if(self->oldenemy_debounce_time > 0)
{
if(self->oldenemy_debounce_time < level.time)
{
self->oldenemy_debounce_time = -1;
if(M_ValidOldEnemy(self))
return true;
}
}
if(target == self->enemy)
checkold = true;
if (!target)
{
self->monsterinfo.aiflags &= ~AI_STRAIGHT_TO_ENEMY;
if(checkold)
if(M_ValidOldEnemy(self))
return true;
if (!FindTarget(self))
return false;
target = self->enemy;
}
if(!target)
return false;
//See if the target has died
if (target->health <= 0 || target == self)
{
self->monsterinfo.aiflags &= ~AI_STRAIGHT_TO_ENEMY;
//See if there is another valid target to go after
if(checkold)
if(M_ValidOldEnemy(self))
return true;
if (!FindTarget(self))
{
if(self->enemy)
self->oldenemy = self->enemy;
self->enemy = NULL;
return false;
}
}
if(coop->value)
{
if(self->monsterinfo.awake)
{
if(self->enemy && self->monsterinfo.coop_check_debounce_time < level.time)
{
int c_dist[MAX_CLIENTS];
float e_dist;
int i;
edict_t *newenemy = NULL;
edict_t *client = NULL;
//only do this check once a second per monster
self->monsterinfo.coop_check_debounce_time = level.time + 1;
e_dist = M_DistanceToTarget(self, self->enemy);
for(i = 0; i <= game.maxclients; i++)
{
c_dist[i] = 9999999999;
client = &g_edicts[i];
if(client->client && client->health > 0)
{
c_dist[i] = M_DistanceToTarget(self, client);
}
}
for(i = 0; i <= game.maxclients; i++)
{
if(c_dist[i] < e_dist)
{
client = &g_edicts[i];
if(visible(self, client))
{
newenemy = client;
e_dist = c_dist[i];
}
}
}
if(newenemy)
{
if(self->enemy->client && self->enemy->health > 0)
self->oldenemy = self->enemy;
self->enemy = newenemy;
FoundTarget(self, false);
self->monsterinfo.searchType = SEARCH_COMMON;
}
}
}
}
return true;
}
/*====================================================================================================================
int M_PredictTargetEvasion
Predicts where the target will be a few frames later based on current velocity and facing, and predicts where
the attacker will be at that same time. It then decides whether or not it will be able to melee from there.
This is necessary for melee striking creatures who tend to run up to the player, swing, then stand for a few
frames while the player backs up.
NOTE: Does not detect whether or not a target and attacker will collide during the course of movement, but ai_run will
find this for us.
Returns: 0 - target will be out of range at end of movements (suggest: run after)
1 - target will be within range at the end of the movements at current velocities (suggest: continue motion)
attacker - the entity pursuing the target
target - what's being pursued
pursue_vel - attacker's desired movement velocity (passed as parameter so an average velocity for frames can be used)
evade_vel - target's estimated evade velocity (again, passed as parameter in case you have special knowledge of a movement)
strike_dist - maximum distance a melee attack can occur at, this is the range checked at the end of prediction
pred_frames - number of frames (1/10th second) to predict over (prediction accuracy decreases over large amounts of time)
======================================================================================================================*/
int M_PredictTargetEvasion( edict_t *attacker, edict_t *target, vec3_t pursue_vel, vec3_t evade_vel, float strike_dist, float pred_frames )
{
vec3_t pTargetPos, pAttackPos, targetMove, attackMove, vec;
float dist, targetDist, attackDist;
//Setup the movement directions
VectorCopy(pursue_vel, attackMove);
VectorCopy(evade_vel, targetMove);
//Setup the distances of attack
attackDist = VectorNormalize(attackMove);
targetDist = VectorNormalize(targetMove);
//Obtain movement per frame, then apply it over the number of predicted frames
attackDist = pred_frames * (attackDist * FRAMETIME);
targetDist = pred_frames * (targetDist * FRAMETIME);
VectorMA(attacker->s.origin, attackDist, attackMove, pAttackPos);
VectorMA(target->s.origin, targetDist, targetMove, pTargetPos);
//Find the distance between them
VectorSubtract(pAttackPos, pTargetPos, vec);
dist = VectorLength(vec);
//If dist is too far, we won't hit
if (dist > strike_dist)
return 0;
return 1;
}
/*====================================================================================================================
vec3_t M_PredictTargetPosition
Predicts where the target will be a few frames later based on current velocity and facing.
NOTE: Does not detect whether or not a target and attacker will collide during the course of movement, but ai_run will
find this for us.
Returns: Position the target may be at in the predicted period
target - what's being pursued
evade_vel - target's estimated evade velocity (again, passed as parameter in case you have special knowledge of a movement)
pred_frames - number of frames (1/10th second) to predict over (prediction accuracy decreases over large amounts of time)
pTargetPos - where the enemy will be
======================================================================================================================*/
void M_PredictTargetPosition( edict_t *target, vec3_t evade_vel, float pred_frames, vec3_t pTargetPos)
{
vec3_t targetMove;
float targetDist;
//Setup the movement directions
VectorCopy(evade_vel, targetMove);
//Setup the distances of attack
targetDist = VectorNormalize(targetMove);
//Obtain movement per frame, then apply it over the number of predicted frames
targetDist = pred_frames * (targetDist * FRAMETIME);
VectorMA(target->s.origin, targetDist, targetMove, pTargetPos);
}
/*====================================================================================================================
void M_StartDeath
Sets various states and sets up the monster to play his death frames. Passing -1 in the sound field will
skip the sound playing
Returns:
self - the entity dying
sound - the sound ID to play, -1 if no sound
======================================================================================================================*/
void M_StartDeath( edict_t *self, int animID)
{
self->msgHandler = DeadMsgHandler;
/*if(self->monsterinfo.aiflags & AI_DONT_THINK)
{
SetAnim(self, animID);
return;
}*/
//Dead but still being hit
if(self->deadflag == DEAD_DEAD)
return;
self->deadflag = DEAD_DEAD;
//gib death
if(self->health <= -80)
{
//The monster much check and play its own sound if a gib occured
self->think = BecomeDebris;
self->nextthink = level.time + FRAMETIME;
return;
}
}
/*====================================================================================================================
void M_EndDeath
The monster is dead completely. Set all information to reflect this.
Returns:
self - the entity that is dead
======================================================================================================================*/
void M_EndDeath( edict_t *self)
{
self->mood_nextthink = -1;//never mood_think again
self->maxs[2] = self->mins[2] + 16;
1999-03-18 00:00:00 +00:00
if (self->s.effects & EF_ON_FIRE)
{
self->think = M_EndDeath;
self->nextthink = level.time + 1.0;
self->s.effects &= ~EF_ON_FIRE;
}
else
{
self->think = NULL;
self->nextthink = -1;//stop thinkin'
gi.RemoveEffects(&self->s, 0);
}
self->s.effects |= EF_DISABLE_EXTRA_FX;
1998-11-24 00:00:00 +00:00
gi.linkentity (self);
}
/*====================================================================================================================
int M_FindSupport
Look for monsters of a similar race around the current position of this monster.
Returns: Number of monsters around the current monster
range - The radius to check inside
======================================================================================================================*/
int M_FindSupport( edict_t *self, int range )
{
edict_t *ent = NULL;
int numSupport = 0;
while((ent = findradius(ent, self->s.origin, range)) != NULL)
{
if (ent==self)
continue;
if (ent->classID != self->classID)
continue;
if (ent->health <= 0)
continue;
numSupport++;
}
return numSupport;
}
/*====================================================================================================================
qboolean M_FindSupport
Look for monsters of a similar race and if they are already trying to alert others
Returns: Whether or not to alert other monsters
range - The radius to check inside
======================================================================================================================*/
qboolean M_CheckAlert( edict_t *self, int range )
{
edict_t *ent = NULL;
int numSupport = 0;
while((ent = findradius(ent, self->s.origin, range)) != NULL)
{
if (ent==self)
continue;
if (ent->classID != self->classID)
continue;
if (ent->enemy != self->enemy)
continue;
if (ent->health <= 0)
continue;
if (ent->monsterinfo.sound_finished < level.time || ent->monsterinfo.sound_pending)
continue;
return false;
}
return true;
}
/*----------------------------------------------------------------------
Generic Jump
-----------------------------------------------------------------------*/
void M_jump(edict_t *self, G_Message_t *msg)
{
vec3_t jvec, fwd;
float dist;
if (!self->goalentity)
return;
if(self->spawnflags&MSF_FIXED)
return;
dist = M_DistanceToTarget(self, self->goalentity);
if (dist > 256)
return;
self->jump_time = level.time + 0.5;
AngleVectors(self->s.angles, fwd, NULL, NULL);
VectorScale(fwd, 256, jvec);
jvec[2] += 101;
if(classStatics[self->classID].msgReceivers[MSG_CHECK_MOOD])
{
VectorCopy(jvec, self->movedir);
self->ai_mood = AI_MOOD_JUMP;//don't technically need this line
self->mood_nextthink = level.time + 0.5;
//as an alternative, call self->forced_jump(self);
QPostMessage(self, MSG_CHECK_MOOD, PRI_DIRECTIVE, "i", AI_MOOD_JUMP);
}
else
VectorCopy(jvec, self->velocity);
}
// get the dismember message and send it to my dismember code
void MG_parse_dismember_msg(edict_t *self, G_Message_t *msg)
{
HitLocation_t HitLocation;
int damage;
if(!self->monsterinfo.dismember)
{
#ifdef _DEVEL
gi.dprintf("ERROR: %s with dismember message handler but no dismember function\n", self->classname);
#endif
return;
}
ParseMsgParms(msg, "ii", &damage, &HitLocation);
self->monsterinfo.dismember(self, damage, HitLocation);
}
/*----------------------------------------------------------------------
Generic Monster Reaction to being alerted
-----------------------------------------------------------------------*/
qboolean defaultMonsterAlerted (edict_t *self, alertent_t *alerter, edict_t *enemy)//I don't understand why I get a warning here...
{
if(self->alert_time < level.time)
{//not already alerted
if(!(alerter->alert_svflags&SVF_ALERT_NO_SHADE) && skill->value < 3.0 && !(self->monsterinfo.aiflags & AI_NIGHTVISION))
{
if(enemy->light_level < flrand(6, 77))
{
return false;
}
}
}
if(alerter->lifetime < level.time + 2)
self->alert_time = level.time + 2;//be ready for 2 seconds to wake up if alerted again
else
self->alert_time = alerter->lifetime;//be alert as long as the alert sticks around
if(enemy->svflags&SVF_MONSTER)
self->enemy = alerter->enemy;
else
self->enemy = enemy;
FoundTarget(self, true);
return true;
}
/*
===============
MG_ChangePitchForZVel
===============
*/
float MG_ChangePitch(edict_t *self, float ideal, float speed)
{
float current;
float move;
current = anglemod(self->s.angles[PITCH]);
ideal = anglemod(ideal);
if (current == ideal)
return false;
move = ideal - current;
if (ideal > current)
{
if (move >= 180)
move = move - 360;
}
else
{
if (move <= -180)
move = move + 360;
}
if (move > 0)
{
if (move > speed)
move = speed;
}
else
{
if (move < -speed)
move = -speed;
}
self->s.angles[PITCH] = anglemod (current + move);
return move;
}
float MG_ChangePitchForZVel(edict_t *self, float speed, float cap_vel, float max_angle)
{
float ideal;
ideal = self->velocity[PITCH];
if(fabs(ideal) > cap_vel)
{
if(ideal > 0)
ideal = max_angle;
else
ideal = -max_angle;
}
else
ideal = ideal/cap_vel * max_angle;
return MG_ChangePitch(self, ideal, 10);
}
/*
===============
MG_SetNormalizeVelToGoal
===============
*/
void MG_SetNormalizeVelToGoal(edict_t *self, vec3_t vec)
{
vec3_t targVec;
qboolean charge_enemy = false;
if(self->monsterinfo.aiflags&AI_STRAIGHT_TO_ENEMY && self->enemy)
charge_enemy = true;
if (self->monsterinfo.searchType == SEARCH_BUOY && !charge_enemy)
{
#ifdef _DEVEL
if(MGAI_DEBUG)
gi.dprintf("Vec to navgoal!\n");
#endif
if(self->buoy_index < 0 || self->buoy_index > level.active_buoys)
{
#ifdef _DEVEL
gi.dprintf("Error: SEARCH_BUOY but invalid index!!!\n");
#endif
VectorClear(vec);
return;
}
VectorCopy(level.buoy_list[self->buoy_index].origin, self->monsterinfo.nav_goal);
VectorSubtract(self->monsterinfo.nav_goal, self->s.origin, vec);
}
else if(self->goalentity && !charge_enemy)
{
#ifdef _DEVEL
if(MGAI_DEBUG)
gi.dprintf("Vec to goalentity!\n");
#endif
if(self->goalentity == self->enemy && self->ai_mood_flags & AI_MOOD_FLAG_PREDICT)
{//predict where he's goin
M_PredictTargetPosition( self->enemy, self->enemy->velocity, 8, targVec);
}
else
{
VectorCopy(self->goalentity->s.origin, targVec);
}
VectorSubtract(targVec, self->s.origin, vec);
}
else if(self->enemy)
{
#ifdef _DEVEL
if(MGAI_DEBUG)
gi.dprintf("Vec to enemy!\n");
#endif
if (self->ai_mood_flags & AI_MOOD_FLAG_PREDICT)
{//predict where he's goin
M_PredictTargetPosition( self->enemy, self->enemy->velocity, 8, targVec);
}
else
{
VectorCopy(self->enemy->s.origin, targVec);
}
VectorSubtract(targVec, self->s.origin, vec);
}
else
{
#ifdef _DEVEL
if(MGAI_DEBUG)
gi.dprintf("No goal to face!\n");
#endif
VectorClear(vec);
return;
}
VectorNormalize(vec);
}
/*====================================================================================================================
void M_ShowLifeMeter
Overides the lung meter and displays the creature's life meter to all clients
Returns: Nothing
======================================================================================================================*/
#define LIFEBAR_SCALE 16
void M_ShowLifeMeter( edict_t *self, int value, int max_value )
{
player_state_t *ps;
int i;
//Update all clients
for (i = 0 ; i < maxclients->value ; i++)
{
ps = &game.clients[i].ps;
1999-03-18 00:00:00 +00:00
ps->stats[STAT_LIFEBAR_XSIZE] = (max_value / LIFEBAR_SCALE);
ps->stats[STAT_LIFEBAR_YSIZE] = 16;
ps->stats[STAT_LIFEBAR_ICON] = gi.imageindex("icons/powerup.m8");
1998-11-24 00:00:00 +00:00
ps->stats[STAT_LIFEBAR_BACK] = gi.imageindex("icons/lifebar_back.m8");
1999-03-18 00:00:00 +00:00
if (max_value)
ps->stats[STAT_LIFEBAR_VALUE] = (100 * value) / max_value;
else
ps->stats[STAT_LIFEBAR_VALUE] = 0;
1998-11-24 00:00:00 +00:00
}
}