sof-sdk/Source/Game/gamecpp/ai_actions.cpp
2000-06-15 00:00:00 +00:00

3355 lines
87 KiB
C++

#include "g_local.h"
#include "ai_private.h"
#include "ds.h"
#include "..\qcommon\ef_flags.h"
//////////////////////////////////////////////////////////////////////////////
action_c *action_c::NewClassForCode(int code)
{
switch (code)
{
case STAND_ACTION:
return new stand_action();
case PAIN_ACTION:
return new pain_action();
case DEATH_ACTION:
return new death_action();
case DEATHCORPSE_ACTION:
return new deathcorpse_action();
case DEATHARMOR_ACTION:
return new deatharmor_action();
case ENDSCRIPT_ACTION:
return new endscript_action();
case WALK_ACTION:
return new walk_action();
case DOG_WALK_ACTION:
return new dog_walk_action();
case JUMP_ACTION:
return new jump_action();
case FALL_ACTION:
return new fall_action();
case SURRENDER_ACTION:
return new surrender_action();
case CAPTURE_ACTION:
return new capture_action();
case SHOOT_ATTACK_ACTION:
return new shoot_attack_action();
case RUNNING_MELEE_ATTACK_ACTION:
return new running_melee_attack_action();
case HELI_ACTION:
return new heli_action();
case HELIMOVE_ACTION:
return new helimove_action();
case HELIFACE_ACTION:
return new heliface_action();
case HELIATTACK_ACTION:
return new heliattack_action();
case HELIDEATH_ACTION:
return new helideath_action();
case SNOWCAT_ACTION:
return new snowcat_action();
case TANK_ACTION:
return new tank_action();
case ACTION:
gi.dprintf("ERROR: only allowed to use leaves of action class tree: %d\n",code);
break;
default:
gi.dprintf("ERROR: invalid action class code: %d\n",code);
break;
}
return new action_c();
}
action_c::action_c(decision_c *od, action_c *oa, mmove_t *newanim, vec3_t destination, vec3_t facing, edict_t *target, float timeout, qboolean fullAnimation)
{
anim = newanim;
VectorCopy(facing, face);
VectorCopy(destination, dest);
faceUpdatedTime = level.time;
interrupted = false;
abortAction = false;
owner_decision = od;
owner_action = oa;
if (newanim)
{
velScale = newanim->velScale;
default_turn = newanim->maxTurn * 2.0f;
}
else
{
velScale = 1.0f;
default_turn = 30;
}
move_mask = step_movemask;
start_time = level.time;
timeout_interval = timeout;
timeout_time = start_time + timeout_interval;
VectorClear(jump_intent);
jump_finish_time = level.time + 999;
waitForAnim = fullAnimation;
SignalEvent = NULL;
isLethal = false;
NullTarget = 0;
action_target = target;
}
action_c::action_c(void)
{
VectorClear(face);
VectorClear(dest);
VectorClear(jump_intent);
timeout_interval = 999999999999;
abortAction = false;
owner_decision = NULL;
owner_action = NULL;
velScale = 0;
default_turn = 0.0;
move_mask = step_movemask;
anim = &generic_move_stand;
action_target = NULL;
waitForAnim=false;
faceUpdatedTime=level.time;
interrupted = false;
jump_finish_time = level.time + 999;
SignalEvent = NULL;
isLethal = false;
NullTarget = 0;
}
void action_c::Init(decision_c *od, action_c *oa, mmove_t *newanim, vec3_t destination, vec3_t facing, edict_t *target, float timeout, qboolean fullAnimation)
{
anim = newanim;
VectorCopy(facing, face);
VectorCopy(destination, dest);
faceUpdatedTime = level.time;
interrupted = false;
abortAction = false;
owner_decision = od;
owner_action = oa;
if (newanim)
{
velScale = newanim->velScale;
default_turn = newanim->maxTurn * 2.0f;
}
else
{
velScale = 1.0f;
default_turn = 30;
}
move_mask = step_movemask;
start_time = level.time;
timeout_interval = timeout;
timeout_time = start_time + timeout_interval;
VectorClear(jump_intent);
jump_finish_time = level.time + 999;
waitForAnim = fullAnimation;
SignalEvent = NULL;
isLethal = false;
NullTarget = 0;
action_target = target;
}
void action_c::Init(void)
{
VectorClear(face);
VectorClear(dest);
VectorClear(jump_intent);
timeout_interval = 999999999999;
abortAction = false;
owner_decision = NULL;
owner_action = NULL;
velScale = 0;
default_turn = 0.0;
move_mask = step_movemask;
anim = &generic_move_stand;
action_target = NULL;
waitForAnim=false;
faceUpdatedTime=level.time;
interrupted = false;
jump_finish_time = level.time + 999;
SignalEvent = NULL;
isLethal = false;
NullTarget = 0;
}
void action_c::ResolveAction(ai_c &which_ai, edict_t &monster, float decision_success, float action_success)
{
if (owner_decision)
{
owner_decision->ActionCompleted(*this, which_ai, monster, decision_success);
}
if (owner_action)
{
owner_action->ActionCompleted(*this, which_ai, monster, action_success);
}
}
void action_c::SetTurnDestination(ai_c &which_ai, edict_t &monster)
{
vec3_t v;
if (face[0]||face[1]||face[2])
{
VectorSubtract (face, monster.s.origin, v);
// uhhh.... this makes me a bit sick to my stomach... guys shoot from a bit above their waist
// so, bump the spot up
v[2] -= MONSTER_SHOOT_HEIGHT;
}
//if no turn destination specified, turn toward movement destination
else if (((monster.s.origin[0]-dest[0])||(monster.s.origin[1]-dest[1]))&&!VectorCompare(dest,vec3_origin))
{
VectorSubtract (dest, monster.s.origin, v);
}
else
{
VectorCopy(monster.s.angles,which_ai.ideal_angles);
return;
}
//Enemy_Printf(&which_ai, "%f, %f, %f\n", v[0], v[1], v[2]);
vectoangles(v, which_ai.ideal_angles);
}
//fixme: should not do this in actions!
qboolean action_c::AdjustBBox(ai_c &which_ai, edict_t &monster)
{
if (which_ai.AttemptSetBBox(monster, anim->bbox))
{
return true;
}
return false;
}
//PerformBodilyFunctions:
//the boolean that gets returned tells caller whether to abort action (false == abort)
//the vector that gets passed in is set to reflect movement intention
qboolean action_c::PerformBodilyFunctions(ai_c &which_ai, edict_t &monster, vec3_t curDist)
{
turninfo_s turninfo;
// Com_Printf("action!--performing body functions!\n");
//abortAction will be set when child scripted action fails
if (abortAction || !which_ai.GetBody())
{
return false;
}
which_ai.GetBody()->UpdateFace(monster);
((bodyorganic_c*)which_ai.GetBody())->UpdateSoundsByWeapon(monster, which_ai);
InterpretFlags(NextFlags(which_ai, monster), which_ai, monster);
AdjustBBox(which_ai, monster);
//if i have a turn destination, turn toward it
SetTurnDestination(which_ai, monster);//45
NextTurn(1.0, which_ai, monster, turninfo);//70
Turn(which_ai, monster, turninfo);//bout 70
NextMovement(which_ai, monster, curDist);//bout 115
return true;
}
qboolean action_c::Think(ai_c &which_ai, edict_t &monster)
{
vec3_t mydist;
if (!PerformBodilyFunctions(which_ai, monster, mydist))
{
ResolveAction(which_ai, monster, 0.0, 0.0);
return true;
}
if (monster.movetype == MOVETYPE_STEP)
{
mydist[2]=0;
}
EvaluateMovement(which_ai, monster, mydist);
if ((waitForAnim && which_ai.GetBody() && !which_ai.GetBody()->IsAnimationFinished()
&& !which_ai.GetBody()->IsAnimationHeld(HOLDCODE_NO) && (level.time < timeout_time))
|| (!waitForAnim && (level.time < timeout_time)))
{
return false;
}
//finish action pronto, and let owners know about it
else
{
ResolveAction(which_ai, monster, 1.0, 1.0);
return true;
}
}
qboolean action_c::MatchAnimation(ai_c &which_ai, edict_t &monster)
{
qboolean success;
//just bein safe
if (!which_ai.GetBody())
{
gi.dprintf("action::MatchAnimation--ai has no body!\n");
return false;
}
success = which_ai.GetBody()->MatchAnimation(monster, anim);
//my anim should always be valid, lemme know if it's not
if (!success)
{
// gi.dprintf("action::MatchAnimation--sequence %s not found!\n", anim->ghoulSeqName);
}
return success;
}
qboolean action_c::SetAnimation(ai_c &which_ai, edict_t &monster)
{
qboolean success;
//just bein safe
if (!which_ai.GetBody())
{
gi.dprintf("action::SetAnimation--ai has no body!\n");
return false;
}
success = which_ai.GetBody()->SetAnimation(monster, anim);
//my anim should always be valid, lemme know if it's not
if (!success)
{
// gi.dprintf("action::SetAnimation--sequence %s not found!\n", anim->ghoulSeqName);
}
return success;
}
qboolean action_c::ForceAnimation(ai_c &which_ai, edict_t &monster)
{
qboolean success;
//just bein safe
if (!which_ai.GetBody())
{
gi.dprintf("action::ForceAnimation--ai has no body!\n");
return false;
}
success = which_ai.GetBody()->ForceAnimation(monster, anim);
//my anim should always be valid, lemme know if it's not
if (!success)
{
// gi.dprintf("action::ForceAnimation--sequence %s not found!\n", anim->ghoulSeqName);
}
return success;
}
void action_c::NextMovement(ai_c &which_ai, edict_t &monster, vec3_t curDist)
{
//just bein safe
if (!which_ai.GetBody())
{
gi.dprintf("action::NextMovement--ai has no body!\n");
VectorClear(curDist);
return;
}
which_ai.GetBody()->NextMovement(monster, curDist, velScale);
}
void action_c::NextTurn(float scale, ai_c &which_ai, edict_t &monster, turninfo_s &turninfo)
{
//just bein safe
if (!which_ai.GetBody())
{
gi.dprintf("action::NextTurn--ai has no body!\n");
turninfo.max_turn=0.0;
turninfo.min_turn=0.0;
return;
}
which_ai.GetBody()->NextTurn(scale, monster, default_turn, turninfo);
}
int action_c::NextFlags(ai_c &which_ai, edict_t &monster)
{
//just bein safe
if (!which_ai.GetBody())
{
gi.dprintf("action::NextFlags--ai has no body!\n");
return 0;
}
return which_ai.GetBody()->NextFlags(monster);
}
void action_c::InterpretFlags(int cur_flags, ai_c &which_ai, edict_t &monster)
{
//if frameflags specify jump & this action is primed for jumping, then jump
if (cur_flags & FRAMEFLAG_JUMP)
{
if (move_mask & jump_movemask)
{
Jump(which_ai, monster, jump_intent);
}
}
//this ok? no keeping track of whether i've played this sound recently?
if (cur_flags & FRAMEFLAG_LAND)
{
// if (monster.groundentity)
{
// gi.dprintf("<thud!>\n");
switch(rand()%3)
{
default:
case 0:
gi.sound (&monster, CHAN_BODY, gi.soundindex ("impact/gore/fall1.wav"), 1.0, ATTN_NORM, 0);
break;
case 1:
gi.sound (&monster, CHAN_BODY, gi.soundindex ("impact/gore/fall2.wav"), 1.0, ATTN_NORM, 0);
break;
case 2:
gi.sound (&monster, CHAN_BODY, gi.soundindex ("impact/gore/fall3.wav"), 1.0, ATTN_NORM, 0);
break;
}
}
}
//play footsteps
if (cur_flags & FRAMEFLAG_FOOTSTEP)
{
FX_SetEvent(&monster, EV_FOOTSTEPLEFT);
}
//do an attack--call some weapon class attack routine, eventually?
if (cur_flags & FRAMEFLAG_ATTACK)
{
if (which_ai.GetBody())
{
if (isLethal || NullTarget)
{
which_ai.setTarget(action_target);
}
which_ai.GetBody()->FirePrimaryWeapon(monster, isLethal, NullTarget);
isLethal = false;
}
}
if (cur_flags & FRAMEFLAG_THROW)
{
// need to call the actual attack function
body_c* body = which_ai.GetBody();
if (body)
{
body->ThrowProjectile(monster);
}
cur_flags &= ~FRAMEFLAG_THROW;
}
//frame holding--holds for random period of time, vey primitive.
if (cur_flags & FRAMEFLAG_HOLDFRAME)
{
}
}
//jumpvec is horizontal movement--change so jump accepts goal position?
void action_c::Jump (ai_c &which_ai, edict_t &monster, vec3_t jumpvec)
{
float frameboost;
float jump_time, vertical_vel;
float attempted_speed;
//only go through with jump if on ground
if (!monster.groundentity || monster.movetype == MOVETYPE_FLY)
{
return;
}
//abort jump if this action not set up for jumping
if ( !(move_mask & jump_movemask) )
{
return;
}
monster.groundentity = NULL;
//add a little extra velocity, just for this frame--minimum between gravity and jumpheight
if (which_ai.GetJumpHeight() < sv_gravity->value*FRAMETIME)
{
frameboost = which_ai.GetJumpHeight();
}
else
{
frameboost = sv_gravity->value*FRAMETIME;
}
vertical_vel = jumpvec[2];
jumpvec[2] = 0;
attempted_speed = VectorLength(jumpvec);
jump_time=((vertical_vel)/(sv_gravity->value*FRAMETIME))*2.0;
//cap horizontal velocity so i don't jump farther than i'm supposed to
if (jump_time * attempted_speed > which_ai.GetJumpDistance())
{
VectorNormalize(jumpvec);
VectorScale(jumpvec, which_ai.GetJumpDistance() / jump_time, jumpvec);
attempted_speed=which_ai.GetJumpDistance() / jump_time;
}
VectorCopy(jumpvec, monster.velocity);
//fixme: don't have to jump this high; also, store value as jump velocity
monster.velocity[2]=vertical_vel;//fixme? May be Possible to go higher than jumpheight, i don't think so tho
monster.groundentity = NULL;
//store away when i expect to finish the jump, for vel adjustment
jump_finish_time = level.time + (jump_time*FRAMETIME);
}
qboolean action_c::Move (ai_c &which_ai, edict_t &monster, vec3_t dist)//fixme?: do i want all movement here?
//i'd like to have movement inherited,
// with option for ai to replace it & do special kindsa movement
//this is an abbreviated version of what q2 monsters go thru when they move (no fly or swim)
//option to not relink was removed--add it in again, for mvmt tests?
{
float move_length;
vec3_t oldorg, neworg, end;
trace_t trace;
float stepsize;
vec3_t test;
int contents;
trace_t dbtrace;
// try the move
VectorCopy (monster.s.origin, oldorg);
VectorAdd (monster.s.origin, dist, neworg);
//hmmm...i don't especially like this, but to make ai stuff useful for non-humanoids it may have to stay. :(=
if ( monster.movetype != MOVETYPE_STEP )
{
int i;
move_length = VectorLengthSquared(dist);
//if not really moving, just touch triggers and call it a successful move
if (move_length<=0.01)
{
// G_TouchTriggers (&monster);
return true;
}
// try one move with vertical motion, then one without
for (i=0 ; i<2 ; i++)
{
VectorAdd (monster.s.origin, dist, neworg);
if (i == 1)
{
neworg[2] = monster.s.origin[2];
}
if (monster.movetype == MOVETYPE_NOCLIP)
{
gi.trace (monster.s.origin, monster.mins, monster.maxs, neworg, &monster, 0, &trace);
}
else
{
gi.trace (monster.s.origin, monster.mins, monster.maxs, neworg, &monster, MASK_MONSTERSOLID, &trace);
}
// fly monsters don't enter water voluntarily
if (monster.flags & FL_FLY)
{
if (!monster.waterlevel)
{
test[0] = trace.endpos[0];
test[1] = trace.endpos[1];
test[2] = trace.endpos[2] + monster.mins[2] + 1;
contents = gi.pointcontents(test);
if (contents & MASK_WATER)
return false;
}
}
// swim monsters don't exit water voluntarily
if (monster.flags & FL_SWIM)
{
if (monster.waterlevel < 2)
{
test[0] = trace.endpos[0];
test[1] = trace.endpos[1];
test[2] = trace.endpos[2] + monster.mins[2] + 1;
contents = gi.pointcontents(test);
if (!(contents & MASK_WATER))
return false;
}
}
if (trace.fraction == 1)
{
//FIXME!!!!!!!!!!!!!!! if physics weren't broken, wouldn't need this!
// { // actually covered some distance
//test only--did last trace take me into a solid?!!!!!!!!!
if (monster.movetype != MOVETYPE_NOCLIP)
{
gi.trace (trace.endpos, monster.mins, monster.maxs, trace.endpos, &monster, MASK_MONSTERSOLID, &dbtrace);
if (dbtrace.startsolid || dbtrace.allsolid)
{
return false;
}
}
// }
VectorCopy (trace.endpos, monster.s.origin);
gi.linkentity (&monster);
// G_TouchTriggers (&monster);
return true;
}
}
return false;
}
//don't allow movement if caught in midair
if (!monster.groundentity)
{
return false;
}
//sliding, don't move.
if (VectorLengthSquared(monster.velocity)>100)
{
return false;
}
move_length = VectorLengthSquared(dist);
//if not really moving, just touch triggers and call it a successful move
if (move_length<=0.01)
{
// G_TouchTriggers (&monster);
return true;
}
// push down from a step height above the wished position
if (move_mask & step_movemask)
{
stepsize = which_ai.GetStepHeight();
}
else
{
stepsize = 2;//turning off stepping not recommended (will jerk around on small irregularities in floor),
//but you can if you want.
}
neworg[2] += stepsize;
VectorCopy (neworg, end);
end[2] -= stepsize*2;
gi.trace (neworg, monster.mins, monster.maxs, end, &monster, MASK_MONSTERSOLID, &trace);
//if the trace started intersecting w/ solid, there's no headroom to step up--try to just step over & down
if (trace.allsolid||trace.startsolid)
{
neworg[2] -= stepsize*0.5;
gi.trace (neworg, monster.mins, monster.maxs, end, &monster, MASK_MONSTERSOLID, &trace);
if (trace.allsolid || trace.startsolid)//this intersects w/ solid too, forget it
{
neworg[2] -= stepsize*0.5;
gi.trace (neworg, monster.mins, monster.maxs, end, &monster, MASK_MONSTERSOLID, &trace);
if (trace.allsolid || trace.startsolid)//this intersects w/ solid too, forget it
{
// gi.dprintf("monster %s cannot move--in solid!\n",monster.targetname);
return false;
}
}
}
// don't go in to water
if (monster.waterlevel == 0)
{
test[0] = trace.endpos[0];
test[1] = trace.endpos[1];
test[2] = trace.endpos[2] + monster.mins[2] + 1;
contents = gi.pointcontents(test);
//fixme: this shouldn't be hardcoded to avoid h2o, some monsters shouldn't mind
if (contents & MASK_WATER)
{
return false;
}
}
//commented out, because i want the option to fall (using act_checkbottom) in all cases--
//this move rejection ignores possibility of being able to fall further than stepheight,
//even if it is a quick trivial rejection; could restore this, with a check for fall_movemask?
/* if (trace.fraction == 1)
{
// if monster had the ground pulled out, go ahead and fall
if ( monster.flags & FL_PARTIALGROUND )
{
VectorAdd (monster.s.origin, move, monster.s.origin);
gi.linkentity (&monster);
G_TouchTriggers (&monster);
monster.groundentity = NULL;
return true;
}
return false; // walked off an edge
}
*/
//DANGER!! ACHTUNG!!! just because i uncommented this doesn't mean it's guaranteed to work fine!
//FIXME!!!!!!!!!!!!!!! if physics weren't broken, wouldn't need this!
// { // actually covered some distance
//test only--did last trace take me into a solid?!!!!!!!!!
gi.trace (trace.endpos, monster.mins, monster.maxs, trace.endpos, &monster, MASK_MONSTERSOLID, &dbtrace);
if (dbtrace.startsolid || dbtrace.allsolid)
{
// VectorScale(trace.endpos,9,trace.endpos);
// VectorAdd(trace.endpos,neworg,trace.endpos);
// VectorScale(trace.endpos,0.1,trace.endpos);
//AAAAAAAAHHHHH!!! FIXME!!!!
// gi.trace (trace.endpos, monster.mins, monster.maxs, trace.endpos, &monster, MASK_MONSTERSOLID, &dbtrace);
// if (dbtrace.startsolid || dbtrace.allsolid)
// {
//#if _DEBUG
// gi.dprintf("qphysics ERROR!--moved into solid!\n");
//#endif
return false;
// }
}
// }
//if i landed on somebody, don't do the move!
if (trace.ent && trace.ent != monster.groundentity && (trace.ent->client || trace.ent->ai)
&& !(trace.ent->deadflag & DEAD_DEAD)//ok to walk on corpses(!?!)
&& !(move_mask & fall_movemask)//ok to fall on guys
&& trace.endpos[2]>trace.ent->s.origin[2]+trace.ent->maxs[2]+monster.mins[2]-1)
{
return false;
}
//move successful, unless i walked off an edge
VectorCopy (trace.endpos, monster.s.origin);
// check point traces down for dangling corners
//checkbottom checks stepheight or fallheight beneath new position
//(depending on move_mask); if there is room to land, the move is ok
if (!gmonster.CheckBottom (which_ai, &monster, monster.s.origin, move_mask))
{
if (move_mask&fall_movemask)
{
vec3_t newtestpos;
VectorCopy(monster.s.origin, newtestpos);
newtestpos[2]-=20;
gi.trace (monster.s.origin, monster.mins, monster.maxs, newtestpos, &monster, MASK_MONSTERSOLID, &dbtrace);
if (!dbtrace.startsolid && !dbtrace.allsolid && dbtrace.fraction>0.8)
{
monster.flags &= ~FL_PARTIALGROUND;
return false;
}
monster.flags |= FL_PARTIALGROUND;
return false;
}
if ( monster.flags & FL_PARTIALGROUND )
{ // entity had floor mostly pulled out from underneath it
// and is trying to correct
gi.linkentity (&monster);
// G_TouchTriggers (&monster);
return true;
}
if (gmonster.CheckBottom (which_ai, &monster, oldorg, move_mask))
{
VectorCopy (oldorg, monster.s.origin);
return false;
}
else
{
gi.linkentity (&monster);
// G_TouchTriggers (&monster);
return true;
}
}
//since move was successful, ground not being pulled out from under me
if ( monster.flags & FL_PARTIALGROUND )
{
monster.flags &= ~FL_PARTIALGROUND;
}
monster.groundentity = trace.ent;
monster.groundentity_linkcount = trace.ent->linkcount;
// the move is ok
gi.linkentity (&monster);
// G_TouchTriggers (&monster);
return true;
}
#define MAX_PLAYERSPEED 225
#define SPEED_DODGE_EFFECT .6
#define DUCK_DODGE_EFFECT .3
#define JUMP_DODGE_EFFECT .4
#define MISSBY 48
#define DODGE_UNKNOWN 0
#define DODGE_DOWN 1
#define DODGE_UP 2
#define DODGE_LEFT 3
#define DODGE_RIGHT 4
void CalcPlayerAim(vec3_t ang, ai_c &which_ai, edict_t &monster, edict_t &target)
{
float chance;
vec3_t dif, dir;
float dist;
float distMod;
float aimCone;
float playerHitValue = 1.0;
vec3_t targMoveDir;
float targSpeed;
int missDirection = DODGE_UNKNOWN;
float strafeValue;
float angAdd;
float missAng;
float awakeTimeValue;
VectorSubtract(target.s.origin, monster.s.origin, dif);
if(target.client->ps.pmove.pm_flags & PMF_DUCKED)
{ //aim lower for ducking folk
dif[2] -= 20;//?
}
dist = VectorNormalize(dif);
AngleVectors(monster.s.angles, dir, 0, 0);
awakeTimeValue = level.time - which_ai.getFirstTargetTime() - (which_ai.GetMySkills()->getHesitation()*game.playerSkills.getHesitation());
// calculate the effect of distance on the aiming
if(dist < 64)
{ // closer than this, and accuracy starts getting REALLY good
chance = 1.0;
}
else
{
if(dist < 512)
{
distMod = 1.0;
}
else
{ // steep curve fall off
distMod = 512 / dist;
distMod *= distMod;
}
// calculate the effect of player dodging on the shooting
playerHitValue = 1.0;
if(target.client->ps.pmove.pm_flags & PMF_DUCKED)
{
playerHitValue *= 1.0 - DUCK_DODGE_EFFECT;
missDirection = DODGE_DOWN;
}
if(!(target.client->ps.pmove.pm_flags & PMF_ON_GROUND))
{
playerHitValue *= 1.0 - JUMP_DODGE_EFFECT;
missDirection = DODGE_UP;
}
//conventional maximum speed is 300, it would appear
VectorCopy(target.velocity, targMoveDir);
targSpeed = VectorNormalize(targMoveDir);
//basically, moving perpendicular to someone targetting you at high speeds is a dodge
//moving towards or away doesn't help at all
strafeValue = 1.0 - fabs(DotProduct(targMoveDir, dir));
playerHitValue *= 1.0 - (targSpeed/MAX_PLAYERSPEED)*SPEED_DODGE_EFFECT*strafeValue;
if(strafeValue * (targSpeed/MAX_PLAYERSPEED) > .1)
{ // enough strafing that we should base our misfires off of it
vec3_t fakeSide;
//not a proper rotation, but good enough for now
fakeSide[0] = dif[1];
fakeSide[1] = -dif[0];
fakeSide[2] = dif[2];
if(DotProduct(fakeSide, targMoveDir) > 0)
{
missDirection = DODGE_LEFT;
}
else
{
missDirection = DODGE_RIGHT;//?
}
}
chance = which_ai.GetMySkills()->getAccuracy() * DotProduct(dif, dir) * distMod *
game.playerSkills.getAccuracy() * playerHitValue;
}
if(awakeTimeValue < which_ai.GetMySkills()->getAimTime()*game.playerSkills.getHesitation())
{
float extraChance;
extraChance = awakeTimeValue / (which_ai.GetMySkills()->getAimTime()*game.playerSkills.getHesitation());//not ready, I say
if(extraChance < 0)
{
extraChance = 0;
}
if(extraChance > 1.0)
{
extraChance = 1.0;
}
chance *= extraChance;
}
if(dist >= 64 && dist < 128)
{//secondary close range
chance = chance * ((dist - 64.0)/64.0) + (1.0 - ((dist - 64.0)/64.0));
}
vectoangles(dif, ang);
angAdd = atan2(MISSBY, dist) * 180/M_PI;
ang[YAW] += gi.flrand(-3, 3);
ang[PITCH] += gi.flrand(-3, 3);
if(gi.flrand(0,1) > chance)
{ // missed
switch(missDirection)
{
case DODGE_UNKNOWN:
missAng = gi.flrand(0, 3.14);
ang[YAW] += cos(missAng) * angAdd * 1.0;
ang[PITCH] -= sin(missAng) * angAdd * .5;
break;
case DODGE_UP:
ang[PITCH] += angAdd;
ang[YAW] += gi.flrand(-10, 10);
break;
case DODGE_DOWN:
ang[PITCH] -= angAdd;
ang[YAW] += gi.flrand(-10, 10);
break;
case DODGE_LEFT:
ang[YAW] += angAdd;//? I don't know what left and right mean here =/ I"ll just make 'em work, yeah.
ang[PITCH] += gi.flrand(-10, 10);
break;
case DODGE_RIGHT:
ang[YAW] -= angAdd;
ang[PITCH] += gi.flrand(-10, 10);
break;
}
}
else
{
ang[PITCH] += gi.flrand(angAdd * -.3, angAdd * .3);
ang[YAW] += gi.flrand(angAdd * -.3, angAdd * .3);
}
//keep it in range
if (which_ai.GetBody()&&which_ai.GetBody()->GetMove())
{
aimCone = which_ai.GetAimConeDegrees(monster, which_ai.GetBody()->GetMove()->bbox);
}
else
{
aimCone = 1.0;
}
while(ang[YAW] - monster.s.angles[YAW] > 180)ang[YAW] -= 360;
while(ang[YAW] - monster.s.angles[YAW] < -180)ang[YAW] += 360;
if(ang[YAW] - monster.s.angles[YAW] > aimCone)ang[YAW] = monster.s.angles[YAW] + aimCone;
if(ang[YAW] - monster.s.angles[YAW] < -aimCone)ang[YAW] = monster.s.angles[YAW] - aimCone;
}
#define AIM_DEVIATION_PITCHMOD 0.5
#define AIM_DEVIATION_INITIAL 0.75
#define AIM_DEVIATION_FINALSHAKE 0.25
#define AIMDIST_MIN 92
void getDistDeviate(vec3_t org1, vec3_t org2, int isPlayer, float *scale, float *distDeviate, CAISkills &skills)
{
vec3_t dif;
VectorSubtract(org2, org1, dif);
float dist = VectorLength(dif);
if(isPlayer)
{ // turn faster the closer a player is
if(dist < AIMDIST_MIN)
{
*scale = 2.0;
}
else if(dist > 512 * game.playerSkills.getAimMaxDist())
{
*scale = 1.0;
}
else
{ // the internal value runs from 0 up close to 1 far away
*scale = 2.0 - (dist - AIMDIST_MIN)/((512 * game.playerSkills.getAimMaxDist()) - AIMDIST_MIN);
}
}
else
{ // turn faster the closer the guy is - more severe than for regular player turning
if(dist < AIMDIST_MIN)
{
*scale = 10.0;
}
else if(dist > 512 * game.playerSkills.getAimMaxDist())
{
*scale = 1.0;
}
else
{ // the internal value runs from 0 up close to 1 far away
*scale = 10.0 - (dist - AIMDIST_MIN)/((512 * game.playerSkills.getAimMaxDist()) - AIMDIST_MIN) * 10.0;
}
}
if(dist > 512 * game.playerSkills.getAimMaxDist())
{
*distDeviate += (dist - (512 * game.playerSkills.getAimMaxDist()))/256.0;
}
else if(dist < AIMDIST_MIN)
{
*distDeviate = (dist / AIMDIST_MIN)*(dist / AIMDIST_MIN);
}
if(!isPlayer)
{ //more likely to hit non-players
*distDeviate *= .4;
}
}
void action_c::Aim(ai_c &which_ai, edict_t &monster)
{
//if i'm just standing around with nothing to do, don't bother aiming
if (!which_ai.HasHadTarget())
{
which_ai.SetAimAngles(monster.s.angles);
return;
}
assert((!which_ai.HasTarget())||(!which_ai.getTarget())||(which_ai.getTarget()->inuse < 10));
edict_t *ed = which_ai.getTarget();
if(which_ai.HasTarget() && ed && ed->client)
{ // players are a bit of a special case
vec3_t aimAngs;
CalcPlayerAim(aimAngs, which_ai, monster, *which_ai.getTarget());
which_ai.SetAimAngles(aimAngs);
return;
}
float ideal;
float current;
float move;
vec3_t my_aim;
vec3_t my_targ_pos;
float tempAng;
float aimCone;
float aimDeviation;
//adjust for distance to target
float distScale = 1.0;
float distDeviate = 1.0;
which_ai.getTargetPos(my_targ_pos);
if(which_ai.getTarget())
{
getDistDeviate(my_targ_pos, monster.s.origin, (which_ai.getTarget()->client != 0), &distScale, &distDeviate, *(which_ai.GetMySkills()));
}
which_ai.GetAimAngles(my_aim);
//yaw aim first
if (which_ai.GetBody()&&which_ai.GetBody()->GetMove())
{
aimCone = which_ai.GetAimConeDegrees(monster, which_ai.GetBody()->GetMove()->bbox);
}
else
{
aimCone = 1.0;
}
aimDeviation = which_ai.GetAimDeviationDegrees(monster) * distDeviate * (1.0 - which_ai.GetMySkills()->getAccuracy()) * game.playerSkills.getAccuracy();
tempAng = anglemod(which_ai.ideal_angles[YAW]-monster.s.angles[YAW]);
//if i'm scripted to do this, do it right!
if (owner_decision && ((owner_decision->GetClassCode() == SCRIPTED_DECISION) || (owner_decision->GetClassCode() == PATHCOMBAT_DECISION)
|| (owner_decision->GetClassCode() == PATHIDLE_DECISION)))
{
VectorCopy(which_ai.ideal_angles, my_aim);
aimDeviation = 0;
}
current = my_aim[YAW];
//make sure roll is cleared out
my_aim[ROLL]=0;
//i'm inside the aim cone...use ideal_angles for aiming
if (tempAng < aimCone || tempAng > 360.0-aimCone)
{
ideal = anglemod(which_ai.ideal_angles[YAW]+gi.flrand(-1.0,1.0)*aimDeviation*AIM_DEVIATION_INITIAL);
}
//i'm outside the cone--use the edge of the cone
else if (tempAng < 180.0)
{
ideal = anglemod(monster.s.angles[YAW]+aimCone+gi.flrand(-1.0,1.0)*aimDeviation*AIM_DEVIATION_INITIAL);
}
//outside on the other side
else
{
ideal = anglemod(monster.s.angles[YAW]-aimCone+gi.flrand(-1.0,1.0)*aimDeviation*AIM_DEVIATION_INITIAL);
}
move = ideal - current;
//make sure we take shortest route to desired yaw
if (ideal > current)
{
if (move >= 180)
{
move = move - 360;
}
}
else
{
if (move <= -180)
{
move = move + 360;
}
}
float moveScale = distScale;
//only make the aim adjustment if there's a significant difference between ideal &
if (move < 30 && move > -30)
{
moveScale *= 0.125;
}
else if (move < 60 && move > -60)
{
moveScale *= 0.25;
}
if(moveScale < 1.0)
{
move *= moveScale;
}
aimDeviation *= .4;//up and down should be a bit more accurate than left and right - just looks better
//adjust aiming yaw
my_aim[YAW] = anglemod (current + move + gi.flrand(-1.0,1.0)*aimDeviation*AIM_DEVIATION_FINALSHAKE);
//now pitch aim--don't bother with the cone here
current = my_aim[PITCH];
ideal = anglemod(which_ai.ideal_angles[PITCH]+gi.flrand(-1.0,1.0)*aimDeviation*AIM_DEVIATION_INITIAL*AIM_DEVIATION_PITCHMOD);
move = ideal - current;
//make sure we take shortest route to desired yaw
if (ideal > current)
{
if (move >= 180)
{
move = move - 360;
}
}
else
{
if (move <= -180)
{
move = move + 360;
}
}
moveScale = distScale;
//only make the aim adjustment if there's a significant difference between ideal &
if (move < 15 && move > -15)
{
moveScale *= 0.125;
}
else if (move < 30 && move > -30)
{
moveScale *= 0.25;
}
if(moveScale < 1.0)
{
move *= moveScale;
}
//adjust aiming pitch
my_aim[PITCH] = anglemod (current + move + gi.flrand(-1.0,1.0)*aimDeviation*AIM_DEVIATION_FINALSHAKE*AIM_DEVIATION_PITCHMOD);
which_ai.SetAimAngles(my_aim);
}
qboolean action_c::Turn(ai_c &which_ai, edict_t &monster, turninfo_s &turninfo)
{
float ideal;
float current;
float move;
if (which_ai.IsConcentratingOnPlayer()&&monster.health>0)
{
if (level.sight_client)
{
vec3_t toPlayer;
VectorSubtract(level.sight_client->s.origin, monster.s.origin, toPlayer);
toPlayer[2]=0;
VectorNormalize(toPlayer);
vectoangles(toPlayer, monster.s.angles);
return true;
}
}
current = anglemod(monster.s.angles[YAW]);
// if monster's anim is flagged as a backwards, left or right scripted action, add to the ideal_angles!
if (owner_decision && ((owner_decision->GetClassCode() == SCRIPTED_DECISION) || (owner_decision->GetClassCode() == PATHCOMBAT_DECISION)
|| (owner_decision->GetClassCode() == PATHIDLE_DECISION)))
{
if (anim->actionFlags & ACTFLAG_BACKUP)
{
ideal = anglemod(which_ai.ideal_angles[YAW] + 180);
}
else if (anim->actionFlags & ACTFLAG_LEFTSTRAFE)
{
ideal = anglemod(which_ai.ideal_angles[YAW] + 90);
}
else if (anim->actionFlags & ACTFLAG_RIGHTSTRAFE)
{
ideal = anglemod(which_ai.ideal_angles[YAW] - 90);
}
else
{
ideal = anglemod(which_ai.ideal_angles[YAW]);
}
}
else
{
ideal = anglemod(which_ai.ideal_angles[YAW]);
}
//already facing ideal direction; consider turn successful
if (current == ideal &&(turninfo.max_turn >= 0 && turninfo.min_turn <= 0))
{
Aim(which_ai, monster);
return true;
}
move = ideal - current;
//make sure we take shortest route to desired yaw
if (ideal > current)
{
if (move >= 180)
{
move = move - 360;
}
}
else
{
if (move <= -180)
{
move = move + 360;
}
}
//cap turning speed to yawspeed
// if (move > 0)
// {
// if (move > turninfo.max_turn)
// {
// move = turninfo.max_turn;
// }
// }
// else
// {
// if (move < turninfo.min_turn)
// {
// move = turninfo.min_turn;
// }
// }
if (move > turninfo.max_turn)
{
if (ai_pathtest->value && monster.health<=0)//debug breakpoint
{
gi.dprintf("dead monster forcing turn at %3.3f degrees.\n", turninfo.max_turn);
}
move = turninfo.max_turn;
}
else if (move < turninfo.min_turn)
{
if (ai_pathtest->value && monster.health<=0)//debug breakpoint
{
gi.dprintf("dead monster forcing turn at %3.3f degrees.\n", turninfo.min_turn);
}
move = turninfo.min_turn;
}
monster.s.angles[YAW] = anglemod (current + move);
Aim(which_ai, monster);
return true;
}
//EvaluateMovement no longer checks if we've reached goal position, just checks if we've been blocked
qboolean action_c::EvaluateMovement(ai_c &which_ai, edict_t &monster, vec3_t distance)
{
vec3_t vel_adjust,dir_to_dest,dist_dir;
VectorScale(which_ai.velocity, 0.9, which_ai.velocity);
VectorCopy(distance, dist_dir);
VectorNormalize(dist_dir);
VectorSubtract(dest, monster.s.origin, dir_to_dest);
VectorNormalize(dir_to_dest);
//going exactly the wrong way. hmp.
if (DotProduct(dist_dir, dir_to_dest)<0)
{
// return false;
// VectorScale(distance,-1.0,distance);
}
//move forward the amount current frame specifies
if (!Move (which_ai, monster, distance))//blocked--finish unsuccessful
{
//i'm stopped, dammit
VectorClear(which_ai.velocity);
return false;
}
//make sure i know if i'm stopped on purpose
if (VectorLengthSquared(distance)<=1)
{
VectorClear(which_ai.velocity);
}
else
{
VectorScale(distance, 0.1, vel_adjust);
VectorAdd(which_ai.velocity, vel_adjust, which_ai.velocity);
}
return true;//still walking--let ai know i'm not done yet
}
void action_c::BeginAction(ai_c &which_ai, edict_t &monster)
{
if (!abortAction)
{
if (which_ai.GetBody())
{
if (which_ai.GetBody()->IsAnimationFinished() && anim && !(anim->actionFlags&ACTFLAG_LOOPANIM))
{
ForceAnimation(which_ai, monster);
}
else
{
SetAnimation(which_ai, monster);
}
}
start_time = level.time;
timeout_time = start_time + timeout_interval;
}
}
void action_c::SetSignalEventState (qboolean done)
{
if (SignalEvent)
{
SignalEvent->SetEventState(done);
}
return;
}
action_c::action_c(action_c *orig)
{
*(int *)&anim = GetMmoveNum(orig->anim);
VectorCopy(orig->face, face);
VectorCopy(orig->dest, dest);
faceUpdatedTime = orig->faceUpdatedTime;
interrupted = orig->interrupted;
abortAction = orig->abortAction;
owner_decision.MakeIndex(orig->owner_decision);
owner_action.MakeIndex(orig->owner_action);
velScale = orig->velScale;
default_turn = orig->default_turn;
move_mask = orig->move_mask;
start_time = orig->start_time;
timeout_interval = orig->timeout_interval;
timeout_time = orig->timeout_time;
VectorCopy(orig->jump_intent, jump_intent);
jump_finish_time = orig->jump_finish_time;
waitForAnim = orig->waitForAnim;
*(int *)&SignalEvent= GetEventNum(orig->SignalEvent);
isLethal = orig->isLethal;
NullTarget = orig->NullTarget;
*(int *)&action_target = GetEdictNum(orig->action_target);
}
void action_c::Evaluate(action_c *orig)
{
anim = GetMmovePtr((int)orig->anim);
VectorCopy(orig->face, face);
VectorCopy(orig->dest, dest);
faceUpdatedTime = orig->faceUpdatedTime;
interrupted = orig->interrupted;
abortAction = orig->abortAction;
owner_decision.MakePtr(*(int *)&orig->owner_decision);
owner_action.MakePtr(*(int *)&orig->owner_action);
velScale = orig->velScale;
default_turn = orig->default_turn;
move_mask = orig->move_mask;
start_time = orig->start_time;
timeout_interval = orig->timeout_interval;
timeout_time = orig->timeout_time;
VectorCopy(orig->jump_intent, jump_intent);
jump_finish_time = orig->jump_finish_time;
waitForAnim = orig->waitForAnim;
SignalEvent = GetEventPtr((int)orig->SignalEvent);
isLethal = orig->isLethal;
NullTarget = orig->NullTarget;
action_target = GetEdictPtr((int)orig->action_target);
}
/**********************************************************************************
**********************************************************************************/
move_action::move_action(decision_c *od, action_c *oa, mmove_t *newanim, vec3_t destination, vec3_t facing, float timeout, qboolean fullAnimation)
:action_c(od, oa, newanim, destination, facing, NULL, timeout,fullAnimation)
{
VectorCopy(destination, dest);
}
void move_action::Init(decision_c *od, action_c *oa, mmove_t *newanim, vec3_t destination, vec3_t facing, float timeout, qboolean fullAnimation)
{
action_c::Init(od, oa, newanim, destination, facing, NULL, timeout,fullAnimation);
VectorCopy(destination, dest);
}
qboolean move_action::Think(ai_c &which_ai, edict_t &monster)
{
vec3_t mydist;
qboolean moveSuccess;
//stack of actions has been toppled--abort! abort!
//the only place abortAction is currently set true is when an owned action returns false--
//and the only time actions can now own each other is when scripting decisions stack them up
if (!PerformBodilyFunctions(which_ai, monster, mydist))
{
ResolveAction(which_ai, monster, 0.0, 0.0);
return true;
}
//fixme: z move should be ok!
mydist[2]=0;
bool bTest1 = !!(moveSuccess=EvaluateMovement(which_ai, monster, mydist)),
bTest2 = !!waitForAnim,
bTest3 = !!which_ai.GetBody(),
bTest4 = !!(bTest3?which_ai.GetBody()->IsAnimationFinished():false),
bTest5 = !!(bTest3?which_ai.GetBody()->IsAnimationHeld(HOLDCODE_NO):false),
bTest6 = (level.time > timeout_time),
bTest7 = !!(anim->actionFlags & ACTFLAG_FULLANIM);
// kef 10/12/99 -- here's the old way. it would stop an anim as soon as it finished its first
//cycle even if the anim was supposed to be held for a long period of time
//(e.g. "holding for 30 seconds" in a script wouldn't work)
// if( (!bTest1 || (bTest2 && bTest3 && (bTest4 || bTest5 || bTest6)) || (!bTest2 && bTest6)) &&
// (!bTest7 || bTest4) )
if( (!bTest1 || (bTest2 && bTest4 && (bTest5 || bTest6)) || (!bTest2 && bTest6)) &&
(!bTest7 || bTest4) )
{//movement failed, or action timed out--abort.
ResolveAction(which_ai, monster, 0.0, 0.0);
return true;
}
//movement successful
else
{
float dist_to_goal_sq;//only in xy here
//check to see if i'm close enough (only checking xy) to stop action
dist_to_goal_sq=(monster.s.origin[0]-dest[0])*(monster.s.origin[0]-dest[0])+
(monster.s.origin[1]-dest[1])*(monster.s.origin[1]-dest[1]);
if ((!waitForAnim && dist_to_goal_sq <= (VectorLengthSquared(mydist)+1)*9)
&&(!((anim->actionFlags & ACTFLAG_FULLANIM)&&(!which_ai.GetBody()->IsAnimationFinished()))))//fixme: should do a more accurate check,
//calculating my turning radius, and checking off of that so i don't circle a point
{//reached destination--return successful
ResolveAction(which_ai, monster, 1.0, 1.0);
return true;
}
//still going--let ai know to not get rid of me
return false;
}
}
move_action::move_action(move_action *orig)
: action_c(orig)
{
}
void move_action::Evaluate(move_action *orig)
{
action_c::Evaluate(orig);
}
/**********************************************************************************
**********************************************************************************/
pain_action::pain_action(decision_c *od, action_c *oa, mmove_t *newanim, edict_t &monster, edict_t *other, float kick, int damage, float timeout, qboolean fullAnimation):
action_c(od,oa,newanim,monster.s.origin,vec3_origin,NULL,timeout,fullAnimation){}
void pain_action::BeginAction(ai_c &which_ai, edict_t &monster)
{
monster.s.sound = 0;
monster.s.sound_data = 0;
start_time = level.time;
timeout_time = start_time + timeout_interval;
//don't start over if i've been interrupted...
if (!interrupted)
{
ForceAnimation(which_ai, monster);
}
}
void pain_action::SetTurnDestination(ai_c &which_ai, edict_t &monster)
{
VectorCopy(monster.s.angles, which_ai.ideal_angles);
}
qboolean pain_action::Think(ai_c &which_ai, edict_t &monster)
{
vec3_t mydist;
//allow aborting of pain action, so pain can be stacked in scripted events.
if (!PerformBodilyFunctions(which_ai, monster, mydist))
{
//let owners know i'm finished
ResolveAction(which_ai, monster, 0.0, 0.0);
return true;
}
EvaluateMovement(which_ai, monster, mydist);
if ((waitForAnim && which_ai.GetBody() && !which_ai.GetBody()->IsAnimationFinished()
&& !which_ai.GetBody()->IsAnimationHeld(HOLDCODE_NO) && (level.time < timeout_time))
|| (!waitForAnim && (level.time < timeout_time)))
{
return false;
}
//let owners know i'm finished
//abort previous action, tell decision that an action failed
ResolveAction(which_ai, monster, 0.0, 0.0);
return true;
}
pain_action::pain_action(pain_action *orig)
: action_c(orig)
{
}
void pain_action::Evaluate(pain_action *orig)
{
action_c::Evaluate(orig);
}
void pain_action::Read()
{
char loaded[sizeof(pain_action)];
gi.ReadFromSavegame('AIPA', loaded, sizeof(pain_action));
Evaluate((pain_action *)loaded);
}
void pain_action::Write()
{
pain_action *savable;
savable = new pain_action(this);
gi.AppendToSavegame('AIPA', savable, sizeof(*this));
delete savable;
}
/**********************************************************************************
**********************************************************************************/
death_action::death_action(decision_c *od, action_c *oa, mmove_t *newanim1, mmove_t *newanim2, edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
:action_c(od,oa, newanim1)
{
if (!newanim1)
{
anim=newanim2;
}
anim2 = newanim2;
time_of_death = level.time;
drip = false;
}
death_action::death_action(decision_c *od, action_c *oa, mmove_t *newanim, edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
:action_c(od,oa, newanim)
{
anim2 = NULL;
time_of_death = level.time;
drip = false;
}
void death_action::InterpretFlags(int cur_flags, ai_c &which_ai, edict_t &monster)
{
if (cur_flags & FRAMEFLAG_OOZE)
{
vec3_t curVec;
VectorCopy(monster.s.origin, curVec);
curVec[2] += 10;
FX_MakeDecalBelow(curVec, FXDECAL_BLOODPOOL, 0);
}
action_c::InterpretFlags(cur_flags&~FRAMEFLAG_OOZE,which_ai,monster);
}
#define MAX_SEEN_ENEMIES 5
#define MONSTER_CORPSE_FADE 3.0F
qboolean death_action::SafeToRemove(edict_t &monster)
{
int i;
edict_t *client;
vec3_t client_facing, client_to_body;
// If it's gibbed, then all parts are turned off anyway.
if (monster.gibbed)
{
return true;
}
// See if already fading
if (monster.s.effects & EF_FADE)
{
if (FXA_CheckFade(&monster))
{ // Remove that sucker
return true;
}
//I'm in the process of fading--don't bother doing anything else until I'm done disappearing
return false;
}
//if we are seeing too many guys, corpse is outta here no matter what
if((gmonster.GetSeenEnemyCount() >= ai_maxcorpses->value)&&(!game.cinematicfreeze)&&(!OnSameTeam(&monster, level.sight_client)))
{
if (level.time > time_of_death + 3)
{
if(monster.ghoulInst)
{
FXA_SetFadeOut(&monster, MONSTER_CORPSE_FADE); // Three second fade out time.
return false;//now we start fading
}
else
{
return true;
}
}
}
else
{//corpse count as seen enemies too
gmonster.AddSeenEnemy();
}
//don't consider removing until after a while
if (level.time-time_of_death < 15.0 * ai_maxcorpses->value)
{
return false;
}
//only remove if it won't look screwy to clients
for (i=0 ; i<game.maxclients ; i++)
{
client = &g_edicts[1+i];
if (!client->inuse)
{
continue;
}
VectorSubtract(monster.s.origin, client->s.origin, client_to_body);
//if e's greater than 800 units away, assume he can't see me
if (VectorLengthSquared(client_to_body)>640000)
{
continue;
}
if (!gi.inPVS(monster.s.origin, monster.s.origin))
{
continue;
}
AngleVectors(client->s.angles, client_facing, NULL, NULL);
VectorNormalize(client_to_body);
//if client is looking in my general dir, don't remove for sure
if (DotProduct(client_to_body, client_facing)>-.35) // this may seem excessive, but it since bodies are so big,
// we need to allow for some leeway.
{
time_of_death = level.time - 25;
return false;
}
}
//danger: if time_of_death gets used for anything other than in this func, replace this with something else!!!!
time_of_death = level.time - 25;
return true;
}
#define MAX_FLOOR_ALIGN_INCREMENT 2.5
qboolean death_action::AdjustBBox(ai_c &which_ai, edict_t &monster)
{
if (which_ai.AttemptSetBBox(monster, anim->bbox, true))
{
return true;
}
return false;
}
void death_action::TiltToFloor(ai_c &which_ai, edict_t &monster)
{
trace_t trace;
vec3_t up, down, forward, ftemp, right, tangles;
float rotateDist;
vec3_t normal, norm_accum, norm_temp, norm_compare;
//corpse-angling code: now in extra-balky!
VectorCopy(monster.s.origin, norm_compare);
norm_compare[2]+=monster.mins[2]+1;
VectorClear(norm_accum);
VectorCopy(monster.s.origin, down);
VectorAdd(down, monster.mins, down);
VectorCopy(down, up);
up[2]+=1;
down[2]-=20;
gi.trace(up, vec3_origin, vec3_origin, down, &monster, MASK_DEADSOLID, &trace);
if (trace.allsolid || trace.startsolid)
{
VectorSubtract(up, norm_compare, norm_temp);
}
else
{
VectorSubtract(trace.endpos, norm_compare, norm_temp);
}
VectorScale(norm_temp, -1.0, norm_temp);
norm_temp[2]*=16.0;
VectorNormalize(norm_temp);
VectorAdd(norm_temp, norm_accum, norm_accum);
down[0]+=monster.maxs[0]-monster.mins[0];
up[0]+=monster.maxs[0]-monster.mins[0];
gi.trace(up, vec3_origin, vec3_origin, down, &monster, MASK_DEADSOLID, &trace);
if (trace.allsolid || trace.startsolid)
{
VectorSubtract(up, norm_compare, norm_temp);
}
else
{
VectorSubtract(trace.endpos, norm_compare, norm_temp);
}
VectorScale(norm_temp, -1.0, norm_temp);
norm_temp[2]*=16.0;
VectorNormalize(norm_temp);
VectorAdd(norm_temp, norm_accum, norm_accum);
down[1]+=monster.maxs[1]-monster.mins[1];
up[1]+=monster.maxs[1]-monster.mins[1];
gi.trace(up, vec3_origin, vec3_origin, down, &monster, MASK_DEADSOLID, &trace);
if (trace.allsolid || trace.startsolid)
{
VectorSubtract(up, norm_compare, norm_temp);
}
else
{
VectorSubtract(trace.endpos, norm_compare, norm_temp);
}
VectorScale(norm_temp, -1.0, norm_temp);
norm_temp[2]*=16.0;
VectorNormalize(norm_temp);
VectorAdd(norm_temp, norm_accum, norm_accum);
down[0]-=monster.maxs[0]-monster.mins[0];
up[0]-=monster.maxs[0]-monster.mins[0];
gi.trace(up, vec3_origin, vec3_origin, down, &monster, MASK_DEADSOLID, &trace);
if (trace.allsolid || trace.startsolid)
{
VectorSubtract(up, norm_compare, norm_temp);
}
else
{
VectorSubtract(trace.endpos, norm_compare, norm_temp);
}
VectorScale(norm_temp, -1.0, norm_temp);
norm_temp[2]*=16.0;
VectorNormalize(norm_temp);
VectorAdd(norm_temp, norm_accum, norm_accum);
VectorNormalize(norm_accum);
VectorCopy(norm_accum, normal);
// if (trace.fraction < 1.0)
// {
// VectorCopy(trace.plane.normal, normal);
//get current orientation
AngleVectors(monster.s.angles,forward,right,up);
//find out how much current orientation deviates from ideal
rotateDist = -DotProduct(forward, normal)*90.0;
if (rotateDist > MAX_FLOOR_ALIGN_INCREMENT)
{
rotateDist = MAX_FLOOR_ALIGN_INCREMENT;
}
else if (rotateDist < -MAX_FLOOR_ALIGN_INCREMENT)
{
rotateDist = -MAX_FLOOR_ALIGN_INCREMENT;
}
//rotate me to face ideal
VectorCopy(forward, ftemp);
RotatePointAroundVector(forward, right, ftemp, rotateDist);
//convert to angular form
vectoangles(forward, monster.s.angles);
//...now do the same for roll...
//find out how much current orientation deviates from ideal
rotateDist = DotProduct(right, normal)*90.0;
if (rotateDist > MAX_FLOOR_ALIGN_INCREMENT)
{
rotateDist = MAX_FLOOR_ALIGN_INCREMENT;
}
else if (rotateDist < -MAX_FLOOR_ALIGN_INCREMENT)
{
rotateDist = -MAX_FLOOR_ALIGN_INCREMENT;
}
//rotate me to face ideal
VectorCopy(right, ftemp);
RotatePointAroundVector(right, forward, ftemp, rotateDist);
//convert to angular form
vectoangles(right, tangles);
//combine pitch & roll calcs
monster.s.angles[ROLL] = tangles[PITCH];
// }
//quick lil sliding test, to see how easy it would be to put slidy corpses in...looks good so far...
if (normal[2]>0)
{
normal[2]=0;
if (VectorLengthSquared(normal)>0.0025/*0.05*/)
{
VectorScale(normal, 100, normal);
if (fabs(monster.velocity[0])<fabs(normal[0]))
{
monster.velocity[0]=normal[0];
}
if (fabs(monster.velocity[1])<fabs(normal[1]))
{
monster.velocity[1]=normal[1];
}
if (!monster.velocity[2])
{
monster.velocity[2]=sv_gravity->value*FRAMETIME+10;
}
}
}
else
{
// gi.dprintf("Warning! corpse under ground!\n");
}
}
/*
BLAH this was started and never finished!
void death_action::AlternateTiltToFloor(ai_c &which_ai, edict_t &monster)
{
vec3_t forwardTest,middleTest,backTest;
vec3_t forwardGround, middleGround, backGround;
vec3_t forward,right,up,backward;
float halfHeight;
trace_t trace;
//set up three start points
halfHeight = monster.mins[2]+monster.maxs[2];
VectorCopy(monster.s.origin, middleTest);
middleTest[2]+=halfHeight;
AngleVectors(monster.s.angles, forward, right, up);
forward[2]=0;
VectorNormalize(forward);
VectorScale(forward,16,forward);
VectorAdd(forward,middleTest,forwardTest);
VectorScale(forward,-1,backward);
VectorAdd(backward,middleTest,backTest);
//trace down from three start points
}
*/
qboolean death_action::Think(ai_c &which_ai, edict_t &monster)
{ //go thru pain anim once, then nothing.
vec3_t mydist;
AdjustBBox(which_ai, monster);
// TiltToFloor(which_ai, monster);
// AlternateTiltToFloor(which_ai, monster);
if (monster.groundentity)
{
if (which_ai.GetBody()&&which_ai.GetBody()->IsAnimationHeld(HOLDCODE_INAIR))
{
which_ai.LandFrame(monster);
which_ai.GetBody()->ResumeAnimation(monster);
}
}
InterpretFlags(NextFlags(which_ai, monster), which_ai, monster);
// handle rotational motion tracks
turninfo_s turninfo;
SetTurnDestination(which_ai, monster);
NextTurn(1.0, which_ai, monster, turninfo);
Turn (which_ai, monster, turninfo);
NextMovement(which_ai, monster, mydist);
//special case--if
if (which_ai.GetBody() && which_ai.GetBody()->IsAnimationFinished())//animation done--drip
{
VectorClear(mydist);
if (!drip)
{
which_ai.GetBody()->Drip(monster);
drip = true;
}
}
EvaluateMovement(which_ai, monster, mydist);
/*
if(((level.time - which_ai.GetBody()->GetLastKilledTime()) > .5) && (which_ai.GetBody()->GetMove() == &generic_move_death_dance))
{
anim=anim2;
SetAnimation(which_ai, monster);
}
*/
if (which_ai.GetBody())
{
anim=which_ai.GetBody()->GetSequenceForDeath(monster, &monster, &monster, 0, monster.s.origin, which_ai.GetBody()->GetLastDFlags()|DAMAGE_NO_PROTECTION);
if (anim && anim->suggested_action != ACTCODE_DEATH)
{
gi.dprintf("Death action %s not really a death!\n", anim->ghoulSeqName);
}
which_ai.GetBody()->UpdateFace(monster);
if (anim)
{
SetAnimation(which_ai, monster);
}
which_ai.GetBody()->SetLastDFlags(0);
}
return false;
}
void death_action::BeginAction(ai_c &which_ai, edict_t &monster)
{
monster.s.sound = 0;
monster.s.sound_data = 0;
if (monster.health > 0)
{
monster.health = -1;
monster.deadflag = DEAD_DEAD;
monster.solid = SOLID_BBOX;
monster.clipmask = CONTENTS_DEADMONSTER;
monster.svflags |= SVF_DEADMONSTER;
monster.touch = NULL;//do we need this here?
}
//if i'm running the thrown back animation, and i'm not being thrown back, fall back to generic thrown
if (anim==&generic_move_deaththrown && monster.velocity[2]<10)
{
vec3_t to_dam;
AngleVectors(monster.s.angles, to_dam, NULL, NULL);
VectorScale(to_dam, -200, to_dam);
VectorAdd(monster.velocity, to_dam, monster.velocity);
monster.velocity[2]=100;//people have complained about the height of this at times...
monster.maxs[2]-=26;
monster.s.origin[2]+=26;
}
action_c::BeginAction(which_ai, monster);
}
death_action::death_action(death_action *orig)
: action_c(orig)
{
time_of_death = orig->time_of_death;
drip = orig->drip;
*(int *)&anim2 = GetMmoveNum(orig->anim2);
}
void death_action::Evaluate(death_action *orig)
{
time_of_death = orig->time_of_death;
drip = orig->drip;
anim2 = GetMmovePtr((int)orig->anim2);
action_c::Evaluate(orig);
}
void death_action::Read()
{
char loaded[sizeof(death_action)];
gi.ReadFromSavegame('AIDA', loaded, sizeof(death_action));
Evaluate((death_action *)loaded);
}
void death_action::Write()
{
death_action *savable;
savable = new death_action(this);
gi.AppendToSavegame('AIDA', savable, sizeof(*this));
delete savable;
}
/**********************************************************************************
**********************************************************************************/
deathcorpse_action::deathcorpse_action(decision_c *od, action_c *oa, mmove_t *newanim1, mmove_t *newanim2, edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
:death_action(od, oa, newanim1, newanim2, monster, inflictor, attacker, damage, point)
{
}
deathcorpse_action::deathcorpse_action(decision_c *od, action_c *oa, mmove_t *newanim, edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
:death_action(od, oa, newanim, monster, inflictor, attacker, damage, point)
{
}
void deathcorpse_action::BeginAction(ai_c &which_ai, edict_t &monster)
{
/*
monster.health = -1;
monster.deadflag = DEAD_DEAD;
monster.solid = SOLID_BBOX; // has to be BBOX or you can't shoot the body :{
monster.clipmask = CONTENTS_DEADMONSTER;
monster.svflags |= SVF_DEADMONSTER;
monster.touch = NULL;//do we need this here?
// need to play the last frame of a given death animation
if (monster.ghoulInst)
{
monster.ghoulInst->Play(which_ai.GetBody()->GetSequence(monster, &generic_move_death_fallforward),
level.time,
1.5, // how far into the anim to start. I'm pretty much guessing here.
true, // yes, restart
IGhoulInst::EndCondition::Hold,
false, // no, don't match current position
false // no, dont' reverse the anim
);
}
anim = &generic_move_death_fallforward;
//action_c::BeginAction(which_ai, monster);
*/
}
deathcorpse_action::deathcorpse_action(deathcorpse_action *orig)
:death_action(orig)
{
}
void deathcorpse_action::Evaluate(deathcorpse_action *orig)
{
time_of_death = orig->time_of_death;
drip = orig->drip;
anim2 = GetMmovePtr((int)orig->anim2);
action_c::Evaluate(orig);
}
void deathcorpse_action::Read()
{
char loaded[sizeof(deathcorpse_action)];
gi.ReadFromSavegame('AIXA', loaded, sizeof(deathcorpse_action));
Evaluate((deathcorpse_action *)loaded);
}
void deathcorpse_action::Write()
{
deathcorpse_action *savable;
savable = new deathcorpse_action(this);
gi.AppendToSavegame('AIXA', savable, sizeof(*this));
delete savable;
}
/**********************************************************************************
**********************************************************************************/
deatharmor_action::deatharmor_action(decision_c *od, action_c *oa, mmove_t *newanim1, mmove_t *newanim2, edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
:death_action(od, oa, newanim1, newanim2, monster, inflictor, attacker, damage, point)
{
}
deatharmor_action::deatharmor_action(decision_c *od, action_c *oa, mmove_t *newanim, edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
:death_action(od, oa, newanim, monster, inflictor, attacker, damage, point)
{
}
qboolean deatharmor_action::SafeToRemove(edict_t &monster)
{
// our target is the item_equip_armor we spawned over this guy's corpse. if we can't
//find that armor, the player got it so we can ditch this body
edict_t *armor = NULL;
if (monster.targetname)
{ // find the armor pickup in question
do
{
armor = G_Find (armor, FOFS(target), monster.targetname);
} while (armor && strcmp(armor->classname, "item_equip_armor"));
}
if (armor)
{ // our armor's still here so don't remove the corpse yet
return false;
}
else
{ // our armor's gone so treat us like a regular corpse
return death_action::SafeToRemove(monster);
}
}
qboolean deatharmor_action::Think(ai_c &which_ai, edict_t &monster)
{
AdjustBBox(which_ai, monster);
/*
// we don't want this joker's gore animating. he's dead, for heaven's sake.
ggOinstC *myInstance=game_ghoul.FindOInst(monster.ghoulInst);
if (myInstance)
{
myInstance->StopAnimatingAtAllBolts();
}
*/
//hey! i need a face!
if (which_ai.GetBody())
{
which_ai.GetBody()->UpdateFace(monster);
}
return false;
}
void deatharmor_action::BeginAction(ai_c &which_ai, edict_t &monster)
{
monster.health = -1;
monster.deadflag = DEAD_DEAD;
monster.solid = SOLID_BBOX; // has to be BBOX or you can't shoot the body :{
monster.clipmask = CONTENTS_DEADMONSTER;
monster.svflags |= SVF_DEADMONSTER;
monster.touch = NULL;//do we need this here?
// need to play the last frame of a given death animation
if (monster.ghoulInst)
{
monster.ghoulInst->Play(which_ai.GetBody()->GetSequence(monster, &generic_move_death_fallforward),
level.time,
1.5, // how far into the anim to start. I'm pretty much guessing here.
true, // yes, restart
IGhoulInst::EndCondition::Hold,
false, // no, don't match current position
false // no, dont' reverse the anim
);
}
if (which_ai.GetBody())
{
which_ai.GetBody()->SetVocalCordsWorking(false);
}
anim = &generic_move_death_fallforward;
//action_c::BeginAction(which_ai, monster);
}
deatharmor_action::deatharmor_action(deatharmor_action *orig)
:death_action(orig)
{
time_of_death = orig->time_of_death;
drip = orig->drip;
*(int *)&anim2 = GetMmoveNum(orig->anim2);
}
void deatharmor_action::Evaluate(deatharmor_action *orig)
{
time_of_death = orig->time_of_death;
drip = orig->drip;
anim2 = GetMmovePtr((int)orig->anim2);
action_c::Evaluate(orig);
}
void deatharmor_action::Read()
{
char loaded[sizeof(deatharmor_action)];
gi.ReadFromSavegame('AIXA', loaded, sizeof(deatharmor_action));
Evaluate((deatharmor_action *)loaded);
}
void deatharmor_action::Write()
{
deatharmor_action *savable;
savable = new deatharmor_action(this);
gi.AppendToSavegame('AIXA', savable, sizeof(*this));
delete savable;
}
/**********************************************************************************
**********************************************************************************/
int attack_action::NextFlags(ai_c &which_ai, edict_t &monster)
{
int myFlags=action_c::NextFlags(which_ai, monster);
if (anim && (anim->actionFlags & ACTFLAG_OPTATTACK) && which_ai.GetBody() && which_ai.GetBody()->RightHandWeaponReady(monster))
{
myFlags|=FRAMEFLAG_ATTACK;
}
return myFlags;
}
attack_action::attack_action(decision_c *od, action_c *oa, mmove_t *newanim, edict_t *attackTarget, vec3_t gohere, vec3_t facing, float timeout, qboolean fullAnimation, bool shouldKill, int theNullTarget)
:move_action(od,oa,newanim, gohere,facing,timeout,fullAnimation)
{
if (attackTarget)
{
if ((shouldKill == true) && (attackTarget->health > 1)) // kill oughta be initialized, but with the current setup I'm not sure of a convenient way to do it
{
attackTarget->health = 1;
// can we get rid of armor here?
}
init_health = attackTarget->health;
}
else
{
init_health = 1;
}
action_target = attackTarget;
next_attack_time = level.time;
isLethal = shouldKill;
NullTarget = theNullTarget;
}
void attack_action::BeginAction(ai_c &which_ai, edict_t &monster)
{
if (!abortAction)
{
if (anim && anim->actionFlags&ACTFLAG_LOOPANIM)
{
SetAnimation(which_ai, monster);
}
else
{
ForceAnimation(which_ai, monster);
}
move_action::BeginAction(which_ai,monster);
}
}
qboolean attack_action::Think(ai_c &which_ai, edict_t &monster)
{
// vec3_t v;
vec3_t mydist;
float mypercent;
bool bTest1 = false, bTest2 = false, bTest3 = false, bTest4 = false,
bTest5 = false, bTest6 = false, bTest7 = false, bTest8 = false,
bTest9 = false;
if (!PerformBodilyFunctions(which_ai, monster, mydist))
{
//let owners know i'm finished
ResolveAction(which_ai, monster, 0.0, 0.0);
return true;
}
//move, but i don't care if i'm blocked by default
// gi.dprintf("attack mvmt: %s\n",vtos(mydist));
if (EvaluateMovement(which_ai, monster, mydist))
{
mypercent = 1.0;
}
else
{
mypercent = 0.0;
}
// kef -- and this is _after_ it was cleaned up...
bTest1 = (!!action_target);
bTest2 = bTest1?(action_target->health <= 0):false;
bTest3 = bTest1?(!action_target->inuse):false;
bTest4 = bTest1?(action_target->health <= 0):false;
bTest5 = timeout_time < level.time;
bTest6 = (!!which_ai.GetBody());
bTest7 = (bTest6?(!!which_ai.GetBody()->IsAnimationFinished()):false);
bTest8 = (bTest6?(!!which_ai.GetBody()->IsAnimationHeld(HOLDCODE_NO)):false);
bTest9 = (!waitForAnim || (bTest6 && (bTest7 || bTest8)));
//is action over? if animation over, or target no longer valid, then it is
if ( (!isLethal || (bTest1 && bTest2)) &&
((bTest1 && (bTest3 || bTest4)) || ((bTest5 && bTest9))) )
{
float targetremains;
// kef -- i thought it was a little easier to read if i added this extra
//condition here rather than a dozen lines earlier. this ensures that anims
//are finished even if a scripted kill occurs before the end of the anim
if (bTest1 && bTest4 && !bTest7)
{
return false;
}
//was action successful? adjust percent_success accordingly
//fixme: might take a little time to damage player (rocket, etc)
if (!action_target || !action_target->inuse || action_target->health >= init_health)
{
// mypercent = 0.0;
}
else
{
// mypercent = 1.0;
}
//if target is destroyed, signal owner action to abort (hey, is this a good idea?).
if (!action_target || !action_target->inuse || (action_target->health <=0))
{
targetremains=0.0;
}
else
{
targetremains=1.0;
}
//let owners know i'm finished
ResolveAction(which_ai, monster, mypercent, targetremains);
return true;
}
else
{
return false;
}
}
attack_action::attack_action(attack_action *orig)
: move_action(orig)
{
init_health = orig->init_health;
next_attack_time = orig->next_attack_time;
}
void attack_action::Evaluate(attack_action *orig)
{
init_health = orig->init_health;
next_attack_time = orig->next_attack_time;
move_action::Evaluate(orig);
}
/**********************************************************************************
**********************************************************************************/
void jump_action::JumpToPosition(ai_c &which_ai, edict_t &monster, vec3_t jumpgoal)
{
vec3_t horiz_vel;
float dist_to_cover,vert_vel;
float jump_time, jump_vel;
float frameboost;
vec3_t tJumpGoal;
//no specific jump goal--just go 64 forward
if (VectorCompare(monster.s.origin, jumpgoal))
{
vec3_t forward;
AngleVectors(monster.s.angles, forward, NULL, NULL);
VectorMA(monster.s.origin, 64, forward, tJumpGoal);
VectorCopy(tJumpGoal, jumpgoal);
}
else
{
VectorCopy(jumpgoal, tJumpGoal);
}
//separate out direction and distance to goal
tJumpGoal[2]-=monster.mins[2];
VectorSubtract(tJumpGoal, monster.s.origin, horiz_vel);
horiz_vel[2]=0;
dist_to_cover = VectorNormalize(horiz_vel);
//cap velocity so i don't jump farther than i'm supposed to
if (dist_to_cover > which_ai.GetJumpDistance())
{
dist_to_cover = which_ai.GetJumpDistance();
}
//find out how long i'll be in the air
if (which_ai.GetJumpHeight() < sv_gravity->value*FRAMETIME)
{
frameboost = which_ai.GetJumpHeight();
}
else
{
frameboost = sv_gravity->value*FRAMETIME;
}
//fixme: don't have to jump this high; also, store value as jump velocity
//time going up
vert_vel=sqrt(which_ai.GetJumpHeight()*2.0*sv_gravity->value)+frameboost;
jump_time=vert_vel/(sv_gravity->value*FRAMETIME);
//time coming down--assuming goal is not higher than jumpheight!!!!!!
if ((monster.s.origin[2]+which_ai.GetJumpHeight()-tJumpGoal[2])>0)
{
if (tJumpGoal[2]>monster.s.origin[2])
{
vert_vel=sqrt((tJumpGoal[2]-monster.s.origin[2])*2.0*sv_gravity->value)+frameboost*2;
jump_time=vert_vel/(sv_gravity->value*FRAMETIME);
}
else
{
jump_time+=(sqrt((monster.s.origin[2]+which_ai.GetJumpHeight()-tJumpGoal[2])
*2.0*sv_gravity->value)+frameboost)/(sv_gravity->value*FRAMETIME);
}
}
//now set my jump velocity so i'll reach horizontal dest at right time
jump_vel = 10*dist_to_cover/jump_time;
VectorScale(horiz_vel, jump_vel, horiz_vel);
horiz_vel[2]=vert_vel;
VectorCopy(horiz_vel, jump_intent);
// Jump(which_ai, monster, horiz_vel);
}
void jump_action::BeginAction(ai_c &which_ai, edict_t &monster)
{
JumpToPosition(which_ai, monster, dest);
//store off horizontal velocity for restoration while i'm in air
VectorCopy(monster.velocity, jumpvel);
jumpvel[2]=0;
which_ai.GetBody()->ResumeAnimation(monster);
ForceAnimation(which_ai, monster);
}
qboolean jump_action::Think(ai_c &which_ai, edict_t &monster)
{
vec3_t mydist;
//achtung!! could still be in mid-air here, so be careful
if (!PerformBodilyFunctions(which_ai, monster, mydist))
{
ResolveAction(which_ai, monster, 0.0, 0.0);
return true;
}
EvaluateMovement(which_ai, monster, mydist);
//once i've landed, i'm done
if (((which_ai.GetBody() && which_ai.GetBody()->IsAnimationFinished()) || (jump_finish_time+3 < level.time))
&& monster.groundentity)
{
VectorClear(monster.velocity);
//let owners know i'm finished
VectorSubtract(monster.s.origin,dest,mydist);
if (VectorLengthSquared(mydist)<900)
{
ResolveAction(which_ai, monster, 1.0, 1.0);
}
else
{
ResolveAction(which_ai, monster, 0.0, 1.0);
}
return true;
}
else
{
if (monster.groundentity || monster.movetype == MOVETYPE_FLY)
{
which_ai.GetBody()->ResumeAnimation(monster);
}
//adjust velocity to slide up a wall to a step
//no adjust if i've come to a complete stop--be nice to physics, it zeroes out velocity when it needs to
//to prevent getting stuck on slopes, only adjust until action times out
//FIXME: this counts as a hack
if ((VectorLengthSquared(monster.velocity)>0.01) && (jump_finish_time > level.time) && monster.velocity[2]>0)
{
vec3_t jump_nudge;
jump_nudge[0]=(dest[0]-monster.s.origin[0])/(jump_finish_time - level.time);
jump_nudge[1]=(dest[1]-monster.s.origin[1])/(jump_finish_time - level.time);
jump_nudge[2]=0;
if (VectorLengthSquared(jump_nudge)>16000000)
{
VectorNormalize(jump_nudge);
VectorScale(jump_nudge,4000.0,jump_nudge);
}
monster.velocity[0]=jump_nudge[0];
monster.velocity[1]=jump_nudge[1];
}
return false;
}
}
jump_action::jump_action(jump_action *orig)
: move_action(orig)
{
VectorCopy(orig->jumpvel, jumpvel);
}
void jump_action::Evaluate(jump_action *orig)
{
VectorCopy(orig->jumpvel, jumpvel);
move_action::Evaluate(orig);
}
void jump_action::Read()
{
char loaded[sizeof(jump_action)];
gi.ReadFromSavegame('AIJA', loaded, sizeof(jump_action));
Evaluate((jump_action *)loaded);
}
void jump_action::Write()
{
jump_action *savable;
savable = new jump_action(this);
gi.AppendToSavegame('AIJA', savable, sizeof(*this));
delete savable;
}
/**********************************************************************************
**********************************************************************************/
qboolean fall_action::PerformBodilyFunctions(ai_c &which_ai, edict_t &monster, vec3_t curDist)
{
turninfo_s turninfo;
//abortAction will be set when child scripted action fails
if (abortAction)
{
return false;
}
which_ai.GetBody()->UpdateFace(monster);
InterpretFlags(NextFlags(which_ai, monster), which_ai, monster);
AdjustBBox(which_ai, monster);
//if i have a turn destination, turn toward it
SetTurnDestination(which_ai, monster);
NextTurn(1.0, which_ai, monster, turninfo);
Turn(which_ai, monster, turninfo);
NextMovement(which_ai, monster, curDist);
return true;
}
qboolean fall_action::Think(ai_c &which_ai, edict_t &monster)
{
vec3_t mydist,forward;
//achtung!! could still be in mid-air here, so be careful
if (!PerformBodilyFunctions(which_ai, monster, mydist))
{
ResolveAction(which_ai, monster, 0.0, 0.0);
return true;
}
//urk--this is an ugly way to make sure i'm moving forward.
if (!started_falling)
{
mydist[2]=0;
if (VectorLengthSquared(mydist)<25 && anim==anim2)
{
if (dest && !VectorCompare(dest, vec3_origin))
{
VectorSubtract(dest, monster.s.origin, forward);
forward[2]=0;
VectorNormalize(forward);
VectorScale(forward, 20.0, forward);
}
else
{
AngleVectors(monster.s.angles,forward,NULL,NULL);
VectorScale(forward,20.0,forward);
}
VectorAdd(mydist,forward,mydist);
}
}
//if i haven't started falling yet, and can't move, try to fall
if (!EvaluateMovement(which_ai, monster, mydist)/*&&!(move_mask&fall_movemask)*/)
{
move_mask |= fall_movemask;
if (EvaluateMovement(which_ai, monster, mydist))
{
started_falling = true;
anim=anim2;
}
//gads, this is not good--i'm trapped
else if ((!(monster.flags & FL_PARTIALGROUND) || monster.velocity[2]<-5)&&(!started_falling && !monster.groundentity))//if (level.time>timeout_time)
{
started_falling = true;
// started_falling = false;
anim=anim;
}
}
//using timeout for first half of action, while stepmovement keeps me on ground
if (/*timeout_time < level.time &&*/ !monster.groundentity && !started_falling)
{
// started_falling = true;
anim=anim2;
}
//once i've landed, i'm done
if (monster.groundentity&&started_falling)
{
float my_success=1.0;
which_ai.GetBody()->ResumeAnimation(monster);
if (which_ai.GetBody() && which_ai.GetBody()->IsAnimationFinished())
{
if (monster.s.origin[2]-dest[2]+monster.mins[2]>10)
{
my_success=0.0;
}
VectorClear(monster.velocity);
//let owners know i'm finished
ResolveAction(which_ai, monster, my_success, my_success);
return true;
}
return false;
}
else
{
return false;
}
}
fall_action::fall_action(fall_action *orig)
: move_action(orig)
{
started_falling = orig->started_falling;
*(int *)&anim2 = GetMmoveNum(orig->anim2);
}
void fall_action::Evaluate(fall_action *orig)
{
started_falling = orig->started_falling;
anim2 = GetMmovePtr((int)orig->anim2);
move_action::Evaluate(orig);
}
void fall_action::Read()
{
char loaded[sizeof(fall_action)];
gi.ReadFromSavegame('AIFA', loaded, sizeof(fall_action));
Evaluate((fall_action *)loaded);
}
void fall_action::Write()
{
fall_action *savable;
savable = new fall_action(this);
gi.AppendToSavegame('AIFA', savable, sizeof(*this));
delete savable;
}
/**********************************************************************************
**********************************************************************************/
surrender_action::surrender_action(decision_c *od, action_c *oa, mmove_t *newanim, edict_t *surrenderTo, vec3_t gohere, vec3_t facing, float timeout, qboolean fullAnimation)
:move_action(od,oa,newanim, gohere,facing, timeout,fullAnimation)
{
action_target = surrenderTo;
}
qboolean surrender_action::Think(ai_c &which_ai, edict_t &monster)
{
// vec3_t v;
vec3_t mydist;
if (!PerformBodilyFunctions(which_ai, monster, mydist))
{
//let owners know i'm finished
ResolveAction(which_ai, monster, 0.0, 0.0);
return true;
}
//move, but i don't care if i'm blocked by default
EvaluateMovement(which_ai, monster, mydist);
//finish when i timeout
if ( timeout_time < level.time)
{
//let owners know i'm finished--always successful
ResolveAction(which_ai, monster, 1.0, 1.0);
return true;
}
else
{
return false;
}
}
surrender_action::surrender_action(surrender_action *orig)
: move_action(orig)
{
}
void surrender_action::Evaluate(surrender_action *orig)
{
move_action::Evaluate(orig);
}
void surrender_action::Read()
{
char loaded[sizeof(surrender_action)];
gi.ReadFromSavegame('AISA', loaded, sizeof(surrender_action));
Evaluate((surrender_action *)loaded);
}
void surrender_action::Write()
{
surrender_action *savable;
savable = new surrender_action(this);
gi.AppendToSavegame('AISA', savable, sizeof(*this));
delete savable;
}
/**********************************************************************************
**********************************************************************************/
capture_action::capture_action(decision_c *od, action_c *oa,mmove_t *newanim, edict_t *attackTarget, vec3_t gohere, vec3_t facing, float timeout, qboolean fullAnimation)
:move_action(od,oa,newanim,gohere,facing,timeout,fullAnimation)
{
action_target = attackTarget;
}
qboolean capture_action::Think(ai_c &which_ai, edict_t &monster)
{
// vec3_t v;
vec3_t mydist;
if (!PerformBodilyFunctions(which_ai, monster, mydist))
{
//let owners know i'm finished
ResolveAction(which_ai, monster, 0.0, 0.0);
return true;
}
//move, but i don't care if i'm blocked by default
EvaluateMovement(which_ai, monster, mydist);
//finish when i timeout
if ( timeout_time < level.time)
{
//let owners know i'm finished--always successful??? shouldn't be.
ResolveAction(which_ai, monster, 1.0, 1.0);
return true;
}
else
{
return false;
}
}
capture_action::capture_action(capture_action *orig)
: move_action(orig)
{
}
void capture_action::Evaluate(capture_action *orig)
{
move_action::Evaluate(orig);
}
void capture_action::Read()
{
char loaded[sizeof(capture_action)];
gi.ReadFromSavegame('AICA', loaded, sizeof(capture_action));
Evaluate((capture_action *)loaded);
}
void capture_action::Write()
{
capture_action *savable;
savable = new capture_action(this);
gi.AppendToSavegame('AICA', savable, sizeof(*this));
delete savable;
}
/**********************************************************************************
**********************************************************************************/
endscript_action::endscript_action(decision_c *od) : action_c ()
{
od->FinishScript();
timeout_interval = 0;
}
qboolean endscript_action::Think(ai_c &which_ai, edict_t &monster)
{
//add sight & sound if i spawned deaf & blind
if (monster.spawnflags&SPAWNFLAG_BLIND)
{
which_ai.NewSense(new normalsight_sense(), &monster);
monster.spawnflags&=(~SPAWNFLAG_BLIND);
}
if (monster.spawnflags&SPAWNFLAG_DEAF)
{
which_ai.NewSense(new sound_sense(), &monster);
monster.spawnflags&=(~SPAWNFLAG_DEAF);
}
ResolveAction(which_ai, monster, 1.0, 1.0);
return true;
}
endscript_action::endscript_action(endscript_action *orig)
: action_c(orig)
{
}
void endscript_action::Evaluate(endscript_action *orig)
{
action_c::Evaluate(orig);
}
void endscript_action::Read()
{
char loaded[sizeof(endscript_action)];
gi.ReadFromSavegame('AIEA', loaded, sizeof(endscript_action));
Evaluate((endscript_action *)loaded);
}
void endscript_action::Write()
{
endscript_action *savable;
savable = new endscript_action(this);
gi.AppendToSavegame('AIEA', savable, sizeof(*this));
delete savable;
}
/**********************************************************************************
**********************************************************************************/
running_melee_attack_action::running_melee_attack_action(decision_c *od, action_c *oa, mmove_t *newanim, edict_t *attackTarget, vec3_t gohere, vec3_t facing, float timeout, qboolean fullAnimation)
:attack_action(od, oa, newanim, attackTarget, gohere, facing, timeout, fullAnimation)
{
m_nActionCode = MELEE_ACTION_NONE;
}
qboolean running_melee_attack_action::Turn(ai_c &which_ai, edict_t &monster, turninfo_s &turninfo)
{
float ideal;
float current;
float move;
current = anglemod(monster.s.angles[YAW]);
ideal = anglemod(which_ai.ideal_angles[YAW]);
//already facing ideal direction; consider turn successful
if (current == ideal)
{
Aim(which_ai, monster);
return true;
}
move = ideal - current;
//make sure we take shortest route to desired yaw
if (ideal > current)
{
if (move >= 180)
{
move = move - 360;
}
}
else
{
if (move <= -180)
{
move = move + 360;
}
}
//cap turning speed to yawspeed
if (move > 0)
{
if (move > turninfo.max_turn)
{
move = turninfo.max_turn;
}
}
else
{
if (move < turninfo.min_turn)
{
move = turninfo.min_turn;
}
}
monster.s.angles[YAW] = anglemod (current + move);
Aim(which_ai, monster);
return true;
}
void running_melee_attack_action::SetTurnDestination(ai_c &which_ai, edict_t &monster)
{
vec3_t v,temp_v;
sensedEntInfo_t sensed_client, sensed_monster;
which_ai.GetSensedClientInfo(smask_all, sensed_client);
which_ai.GetSensedMonsterInfo(smask_all, sensed_monster);
// once we jump we don't want to change our direction unless
//a) we hit someone
//b) we land
if (m_nActionCode == MELEE_ACTION_JUMPED)
{
// we just jumped so don't let anyone change our facing
}
else if (m_nActionCode == MELEE_ACTION_HIT)
{
// we hit someone during our attack. we changed our facing
//in running_melee_attack_action::InterpretFlags at the time
//of impact, so now just keep going straight through the end
//of this animation
}
else
{
// we aren't jumping yet and we haven't hit anyone yet so
//face wherever the heck
//keep in touch with the current position of my target, for greater accuracy
if (action_target && sensed_monster.ent == action_target)
{
VectorCopy(sensed_monster.pos, face);
VectorSubtract (face, monster.s.origin, v);
v[2]-=monster.viewheight;
VectorAdd(sensed_monster.ent->mins,sensed_monster.ent->maxs,temp_v);
VectorScale(temp_v,0.5,temp_v);
VectorAdd(v,temp_v,v);
}
else if (action_target && sensed_client.ent == action_target)
{
VectorCopy(sensed_client.pos, face);
VectorSubtract (face, monster.s.origin, v);
v[2]-=monster.viewheight;
VectorAdd(sensed_client.ent->mins,sensed_client.ent->maxs,temp_v);
VectorScale(temp_v,0.5,temp_v);
VectorAdd(v,temp_v,v);
}
//if i have a turn destination, turn toward it--DANGER!!--this will not work if you really want to be facing 0,0,0
else if (face[0]||face[1]||face[2])
{
VectorSubtract (face, monster.s.origin, v);
}
//if no turn destination specified, turn toward movement destination
else
{
VectorSubtract (dest, monster.s.origin, v);
}
vectoangles(v, which_ai.ideal_angles);
}
}
void running_melee_attack_action::InterpretFlags(int cur_flags, ai_c &which_ai, edict_t &monster)
{
//if frameflags specify jump & this action is primed for jumping, record
//our desired jump direction so it doesn't get changed until we land
if (cur_flags & FRAMEFLAG_JUMP)
{
if (move_mask & jump_movemask)
{
//Jump(which_ai, monster, jump_intent);
m_nActionCode = MELEE_ACTION_JUMPED;
}
}
//this ok? no keeping track of whether i've played this sound recently?
if (cur_flags & FRAMEFLAG_LAND)
{
// if (monster.groundentity)
{
// gi.dprintf("<thud!>\n");
switch(rand()%3)
{
default:
case 0:
gi.sound (&monster, CHAN_BODY, gi.soundindex ("impact/gore/fall1.wav"), 1.0, ATTN_NORM, 0);
break;
case 1:
gi.sound (&monster, CHAN_BODY, gi.soundindex ("impact/gore/fall2.wav"), 1.0, ATTN_NORM, 0);
break;
case 2:
gi.sound (&monster, CHAN_BODY, gi.soundindex ("impact/gore/fall3.wav"), 1.0, ATTN_NORM, 0);
break;
}
}
}
//play footsteps
if (cur_flags & FRAMEFLAG_FOOTSTEP)
{
FX_SetEvent(&monster, EV_FOOTSTEPLEFT);
}
if (cur_flags & FRAMEFLAG_ATTACK)
{
// need to call the actual attack function
body_c* body = which_ai.GetBody();
if (body)
{
body->FirePrimaryWeapon(monster, isLethal, NullTarget);
isLethal = false;
}
}
if (cur_flags & FRAMEFLAG_THROW)
{
// need to call the actual attack function
body_c* body = which_ai.GetBody();
if (body)
{
body->ThrowProjectile(monster);
}
cur_flags &= ~FRAMEFLAG_THROW;
}
if (cur_flags & FRAMEFLAG_MELEE)
{
// this flag means we hit someone with our attack so we need
//to change the direction of our motion
m_nActionCode = MELEE_ACTION_HIT;
if (rand()%2)
{
which_ai.ideal_angles[YAW] -= 20;
if (which_ai.ideal_angles[YAW] < 0)
{
which_ai.ideal_angles[YAW] += 360;
}
}
else
{
which_ai.ideal_angles[YAW] += 20;
if (which_ai.ideal_angles[YAW] > 360)
{
which_ai.ideal_angles[YAW] -= 360;
}
}
}
//frame holding--holds for random period of time, vey primitive.
if (cur_flags & FRAMEFLAG_HOLDFRAME)
{
}
}
running_melee_attack_action::running_melee_attack_action(running_melee_attack_action *orig)
: attack_action(orig)
{
m_nActionCode = orig->m_nActionCode;
}
void running_melee_attack_action::Evaluate(running_melee_attack_action *orig)
{
m_nActionCode = orig->m_nActionCode;
attack_action::Evaluate(orig);
}
void running_melee_attack_action::Read()
{
char loaded[sizeof(running_melee_attack_action)];
gi.ReadFromSavegame('AIRA', loaded, sizeof(running_melee_attack_action));
Evaluate((running_melee_attack_action *)loaded);
}
void running_melee_attack_action::Write()
{
running_melee_attack_action *savable;
savable = new running_melee_attack_action(this);
gi.AppendToSavegame('AIRA', savable, sizeof(*this));
delete savable;
}
/**********************************************************************************
**********************************************************************************/
dog_walk_action::dog_walk_action(decision_c *od, action_c *oa, mmove_t *newanim, vec3_t destination, vec3_t facing, float timeout, qboolean fullAnimation)
:move_action(od,oa,newanim, destination, facing, timeout,fullAnimation)
{
m_MoveStraightAnim = newanim;
m_MoveLeftAnim = newanim;
m_MoveRightAnim = newanim;
}
qboolean dog_walk_action::Turn(ai_c &which_ai, edict_t &monster, turninfo_s &turninfo)
{
float ideal;
float current;
float move;
anim = m_MoveStraightAnim;
current = anglemod(monster.s.angles[YAW]);
ideal = anglemod(which_ai.ideal_angles[YAW]);
//already facing ideal direction; consider turn successful
if (current == ideal)
{
Aim(which_ai, monster);
return true;
}
move = ideal - current;
// if we're turning to the left, use our 'walking left' anim, else 'walking right'
if (move < 0)
{
anim = m_MoveLeftAnim;
}
else
{
anim = m_MoveRightAnim;
}
if (anim != m_MoveStraightAnim)
{
BeginAction(which_ai, monster);
}
//make sure we take shortest route to desired yaw
if (ideal > current)
{
if (move >= 180)
{
move = move - 360;
}
}
else
{
if (move <= -180)
{
move = move + 360;
}
}
//cap turning speed to yawspeed
if (move > 0)
{
if (move > turninfo.max_turn)
{
move = turninfo.max_turn;
}
}
else
{
if (move < turninfo.min_turn)
{
move = turninfo.min_turn;
}
}
monster.s.angles[YAW] = anglemod (current + move);
Aim(which_ai, monster);
return true;
}
dog_walk_action::dog_walk_action(dog_walk_action *orig)
: move_action(orig)
{
*(int *)&m_MoveStraightAnim = GetMmoveNum(orig->m_MoveStraightAnim);
*(int *)&m_MoveLeftAnim = GetMmoveNum(orig->m_MoveLeftAnim);
*(int *)&m_MoveRightAnim = GetMmoveNum(orig->m_MoveRightAnim);
}
void dog_walk_action::Evaluate(dog_walk_action *orig)
{
m_MoveStraightAnim = GetMmovePtr((int)orig->m_MoveStraightAnim);
m_MoveLeftAnim = GetMmovePtr((int)orig->m_MoveLeftAnim);
m_MoveRightAnim = GetMmovePtr((int)orig->m_MoveRightAnim);
move_action::Evaluate(orig);
}
void dog_walk_action::Read()
{
char loaded[sizeof(dog_walk_action)];
gi.ReadFromSavegame('AIDW', loaded, sizeof(dog_walk_action));
Evaluate((dog_walk_action *)loaded);
}
void dog_walk_action::Write()
{
dog_walk_action *savable;
savable = new dog_walk_action(this);
gi.AppendToSavegame('AIDW', savable, sizeof(*this));
delete savable;
}
// -------------------------------------------------------------
stand_action::stand_action(stand_action *orig)
: action_c(orig)
{
}
void stand_action::Evaluate(stand_action *orig)
{
action_c::Evaluate(orig);
}
void stand_action::Read()
{
char loaded[sizeof(stand_action)];
gi.ReadFromSavegame('AISA', loaded, sizeof(stand_action));
Evaluate((stand_action *)loaded);
}
void stand_action::Write()
{
stand_action *savable;
savable = new stand_action(this);
gi.AppendToSavegame('AISA', savable, sizeof(*this));
delete savable;
}
// -------------------------------------------------------------
walk_action::walk_action(walk_action *orig)
: move_action(orig)
{
}
void walk_action::Evaluate(walk_action *orig)
{
move_action::Evaluate(orig);
}
void walk_action::Read()
{
char loaded[sizeof(walk_action)];
gi.ReadFromSavegame('AIWA', loaded, sizeof(walk_action));
Evaluate((walk_action *)loaded);
}
void walk_action::Write()
{
walk_action *savable;
savable = new walk_action(this);
gi.AppendToSavegame('AIWA', savable, sizeof(*this));
delete savable;
}
// -------------------------------------------------------------
shoot_attack_action::shoot_attack_action(shoot_attack_action *orig)
: attack_action(orig)
{
}
void shoot_attack_action::Evaluate(shoot_attack_action *orig)
{
attack_action::Evaluate(orig);
}
void shoot_attack_action::Read()
{
char loaded[sizeof(shoot_attack_action)];
gi.ReadFromSavegame('AIAA', loaded, sizeof(shoot_attack_action));
Evaluate((shoot_attack_action *)loaded);
}
void shoot_attack_action::Write()
{
shoot_attack_action *savable;
savable = new shoot_attack_action(this);
gi.AppendToSavegame('AIAA', savable, sizeof(*this));
delete savable;
}
// end