6536 lines
187 KiB
C++
6536 lines
187 KiB
C++
#include "g_local.h"
|
|
#include "ai_private.h"
|
|
#include "ds.h"
|
|
#include "ai_pathfinding.h"
|
|
|
|
#define PATH_FAILURE_ACQUIRE_DELAY 1.0//time i'll wait after i hit a bad path face until i look for path again
|
|
#define PATH_FAILURE_REFAIL_DELAY 1.0//time i'll wait after i hit a bad path face until i consider re-initiating badpath procedures
|
|
|
|
#define PATH_DECISION_LOSETARGETTIME .5//time it'll take me after losing track of target to give up on attack
|
|
#define POINTCOMBAT_DECISION_LOSETARGETTIME 15.0//time it'll take me after losing track of target to abondon combat point
|
|
|
|
|
|
#if _DEBUG
|
|
extern char *decName;
|
|
#endif
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Brush-Using Helper Functions--currently assuming doors, but should be fleshed out for elevators, etc.
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Use_DoorTrigger(edict_t *self, edict_t *other, edict_t *activator);
|
|
void button_use (edict_t *self, edict_t *other, edict_t *activator);
|
|
void plDoorUse(edict_t *self, edict_t *other, edict_t *activator);
|
|
void door_secret_use (edict_t *self, edict_t *other, edict_t *activator);
|
|
void useable_use (edict_t *self,edict_t *other,edict_t *activator);
|
|
void Use_Multi(edict_t *self, edict_t *other, edict_t *activator);
|
|
|
|
qboolean IsUseableBrush(edict_t *what)
|
|
{
|
|
if (!what)
|
|
{
|
|
return false;
|
|
}
|
|
if (what->plUse == Use_DoorTrigger || what->plUse == button_use || what->plUse == plDoorUse ||
|
|
what->plUse == door_secret_use || what->plUse == useable_use || what->plUse == Use_Multi)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//icky!!!!!!!!!! this is defined elsewhere!
|
|
#define STATE_TOP 0
|
|
#define STATE_BOTTOM 1
|
|
#define STATE_UP 2
|
|
#define STATE_DOWN 3
|
|
|
|
#define DOOR_NOMONSTER 0x0008
|
|
|
|
qboolean NotBadDoor(edict_t *what, float when, edict_t *by_whom)
|
|
{
|
|
if(!what)
|
|
{
|
|
return true;
|
|
}
|
|
if (!IsUseableBrush(what))
|
|
{
|
|
return true;
|
|
}
|
|
if(!what->plUse)
|
|
{
|
|
return true;
|
|
}
|
|
if(!(what->spawnflags & DOOR_NOMONSTER))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
qboolean IsBrushUseableNow(edict_t *what, float when, edict_t *by_whom)
|
|
{
|
|
if (!IsUseableBrush(what))
|
|
{
|
|
return false;
|
|
}
|
|
//don't use a door that's in motion, or already open.
|
|
if (what->plUse == plDoorUse && what->moveinfo.state != STATE_BOTTOM)
|
|
{
|
|
return false;
|
|
}
|
|
if (what->plUse == plDoorUse && (what->spawnflags & DOOR_NOMONSTER))
|
|
{
|
|
return false; //no good
|
|
}
|
|
if (what->last_move_time + 5.0 < level.time)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//returns whether or not the useInfo fields were set successfully
|
|
qboolean GetUseableBrushInfo(vec3_t normal, edict_t *what, brushUseInfo_t &useInfo, edict_t *by_whom)
|
|
{
|
|
vec3_t forward;
|
|
|
|
if (normal)
|
|
{
|
|
VectorCopy(normal, forward);
|
|
VectorInverse(forward);
|
|
}
|
|
else
|
|
{
|
|
AngleVectors(by_whom->s.angles, forward, NULL, NULL);
|
|
}
|
|
|
|
VectorCopy(by_whom->s.origin, useInfo.usePos);
|
|
VectorCopy(forward, useInfo.useDir);
|
|
useInfo.useType=USETYPE_MISC;
|
|
useInfo.useEnt = what;
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
decision_c *decision_c::NewClassForCode(int code)
|
|
{
|
|
switch (code)
|
|
{
|
|
case BASE_DECISION:
|
|
return new base_decision();
|
|
case PURSUE_DECISION:
|
|
return new pursue_decision();
|
|
case POINTCOMBAT_DECISION:
|
|
return new pointcombat_decision();
|
|
case SEARCH_DECISION:
|
|
return new search_decision();
|
|
case DODGE_DECISION:
|
|
return new dodge_decision();
|
|
case RETREAT_DECISION:
|
|
return new retreat_decision();
|
|
case PATHIDLE_DECISION:
|
|
return new pathidle_decision();
|
|
case PATHCOMBAT_DECISION:
|
|
return new pathcombat_decision();
|
|
case SCRIPTED_DECISION:
|
|
return new scripted_decision();
|
|
case DEKKER1_DECISION:
|
|
return new dekker1_decision();
|
|
case DEKKER2_DECISION:
|
|
return new dekker2_decision();
|
|
case DEKKER3_DECISION:
|
|
return new dekker3_decision();
|
|
case DEKKER4_DECISION:
|
|
return new dekker4_decision();
|
|
case DECISION:
|
|
gi.dprintf("ERROR: only allowed to use leaves of the decision class tree\n");
|
|
break;
|
|
default:
|
|
gi.dprintf("ERROR: invalid decision class code: %d\n", code);
|
|
break;
|
|
}
|
|
return new decision_c();
|
|
}
|
|
|
|
decision_c::decision_c(decision_c *orig)
|
|
{
|
|
last_dilution_time = orig->last_dilution_time;
|
|
priority = orig->priority;
|
|
priority_base = orig->priority_base;
|
|
validity = orig->validity;
|
|
timeout_time = orig->timeout_time;
|
|
allow_timeout = orig->allow_timeout;
|
|
lastClearTime = orig->lastClearTime;
|
|
isClear = orig->isClear;
|
|
}
|
|
|
|
void decision_c::Evaluate(decision_c *orig)
|
|
{
|
|
last_dilution_time = orig->last_dilution_time;
|
|
priority = orig->priority;
|
|
priority_base = orig->priority_base;
|
|
validity = orig->validity;
|
|
timeout_time = orig->timeout_time;
|
|
allow_timeout = orig->allow_timeout;
|
|
lastClearTime = orig->lastClearTime;
|
|
isClear = orig->isClear;
|
|
}
|
|
|
|
decision_c::decision_c(int priority_root, float timeout, edict_t* ScriptOwner)
|
|
{
|
|
priority = 0;
|
|
priority_base = priority_root;
|
|
validity = 0.5;
|
|
last_dilution_time = level.time;
|
|
|
|
//nathan addedness
|
|
lastClearTime = level.time;
|
|
isClear = false;
|
|
|
|
//disallow decisions timing out in 0.1 secs or less--plugging in values less than 0.1 makes decision last forever
|
|
if (timeout >= 0.1)
|
|
{
|
|
timeout_time = timeout + level.time;
|
|
allow_timeout = true;
|
|
}
|
|
else
|
|
{
|
|
timeout_time = level.time + 999999999;
|
|
allow_timeout = false;
|
|
}
|
|
}
|
|
|
|
qboolean decision_c::IsAdversary(edict_t &monster, edict_t *otherGuy)
|
|
{
|
|
return (!OnSameTeam(&monster, otherGuy));
|
|
}
|
|
|
|
action_c *decision_c::AddActionForSequence(ai_c &which_ai, edict_t &monster, mmove_t *move, vec3_t dest, vec3_t face, edict_t *target, action_c *owneraction, bool shouldKill, int NullTarget, float timeoutTime)
|
|
{
|
|
float lastingTime=timeoutTime;
|
|
action_c *newaction=NULL;
|
|
if (!move)//no move--forget it
|
|
{
|
|
return NULL;
|
|
}
|
|
switch(move->suggested_action)
|
|
{
|
|
default:
|
|
case ACTCODE_STAND:
|
|
//moves with the fullanim flag on them get long actions, so they can complete their anim without interruption
|
|
if (move->actionFlags&ACTFLAG_FULLANIM)
|
|
{
|
|
if (lastingTime<=0.01)
|
|
{
|
|
lastingTime = 4.0f;
|
|
}
|
|
newaction=which_ai.DefaultAction(this, owneraction, move, dest, face, NULL, lastingTime, true);
|
|
}
|
|
else
|
|
{
|
|
//for moves that have the optattack flag, use as attack if a target is passed in; otherwise, use as move
|
|
if (move->actionFlags&ACTFLAG_OPTATTACK&&target)
|
|
{
|
|
if (lastingTime<=0.01)
|
|
{
|
|
lastingTime = 0.25f;
|
|
}
|
|
newaction=which_ai.AttackAction(this, owneraction, move, NULL, dest, face, lastingTime, false);
|
|
break;
|
|
}
|
|
newaction=which_ai.DefaultAction(this, owneraction, move, dest, face);
|
|
}
|
|
break;
|
|
case ACTCODE_ATTACK:
|
|
if (target && (target->health > 0))
|
|
{
|
|
if (lastingTime<=0.01)
|
|
{
|
|
lastingTime = 0.25f;
|
|
}
|
|
newaction=which_ai.AttackAction(this, owneraction, move, target, dest, face, lastingTime, true, shouldKill, NullTarget);
|
|
}
|
|
else
|
|
{
|
|
if (lastingTime<=0.01)
|
|
{
|
|
lastingTime = 0.25f;
|
|
}
|
|
newaction=which_ai.AttackAction(this, owneraction, move, NULL, dest, face, lastingTime, true, shouldKill, NullTarget);
|
|
}
|
|
break;
|
|
case ACTCODE_MOVE:
|
|
if (move->actionFlags&ACTFLAG_FULLANIM)
|
|
{
|
|
if (lastingTime<=0.01)
|
|
{
|
|
lastingTime = 0.2f;
|
|
}
|
|
newaction=which_ai.WalkAction(this, owneraction, move, dest, face, lastingTime, true);
|
|
}
|
|
else
|
|
{
|
|
//for moves that have the optattack flag, use as attack if a target is passed in; otherwise, use as move
|
|
if (move->actionFlags&ACTFLAG_OPTATTACK&&target)
|
|
{
|
|
if (lastingTime<=0.01)
|
|
{
|
|
lastingTime = 0.25f;
|
|
}
|
|
newaction=which_ai.AttackAction(this, owneraction, move, NULL, dest, face, lastingTime, true);
|
|
break;
|
|
}
|
|
if (lastingTime<=0.01)
|
|
{
|
|
lastingTime = 0.5f;
|
|
}
|
|
newaction=which_ai.WalkAction(this, owneraction, move, dest, face, lastingTime);
|
|
}
|
|
break;
|
|
case ACTCODE_JUMP:
|
|
if (lastingTime<=0.01)
|
|
{
|
|
lastingTime = 1.5f;
|
|
}
|
|
newaction=which_ai.JumpAction(this, owneraction, move, dest, face, lastingTime);
|
|
break;
|
|
case ACTCODE_FALL:
|
|
if (lastingTime<=0.01)
|
|
{
|
|
lastingTime = 0.4f;
|
|
}
|
|
newaction=which_ai.FallAction(this, owneraction, move, move, dest, face, lastingTime);
|
|
break;
|
|
case ACTCODE_PAIN:
|
|
if (lastingTime<=0.01)
|
|
{
|
|
lastingTime = 4.0f;
|
|
}
|
|
newaction=which_ai.DefaultAction(this, owneraction, move, dest, face, NULL, lastingTime, true);
|
|
break;
|
|
case ACTCODE_DEATH:
|
|
lastingTime = 9999.0f;
|
|
newaction=which_ai.DeathAction(this, owneraction, move, monster, NULL, NULL, lastingTime, vec3_origin);
|
|
break;
|
|
case ACTCODE_SCRIPTRELEASE:
|
|
newaction=which_ai.EndScriptAction(this);
|
|
break;
|
|
}
|
|
if (newaction)
|
|
{
|
|
/* if (shouldKill && (move->suggested_action == ACTCODE_ATTACK))
|
|
{
|
|
((attack_action*)newaction)->kill = true;
|
|
}
|
|
*/ which_ai.NewAction(newaction, &monster);
|
|
}
|
|
return newaction;
|
|
}
|
|
|
|
qboolean decision_c::ClearShot(ai_c &which_ai, edict_t &monster, vec3_t goalpos, edict_t *ignore, bbox_preset testbbox, float *orgPos, int *blockingGuy)
|
|
{
|
|
trace_t tr;
|
|
vec3_t testpos, trashmaxs;
|
|
vec3_t targpos;
|
|
|
|
// if(lastClearTime > level.time - .2)
|
|
// { //good frequency? Bad frequency? I dunno... Nevermind - this can blow me.
|
|
// return isClear;
|
|
// }
|
|
|
|
which_ai.GetBBoxPreset(testbbox, testpos, trashmaxs);
|
|
|
|
if(orgPos)
|
|
{
|
|
testpos[0]=orgPos[0];
|
|
testpos[1]=orgPos[1];
|
|
testpos[2]=orgPos[2]+monster.mins[2]-testpos[2]+MONSTER_SHOOT_HEIGHT;//? Guys often seem to not be able to shoot past each other - this helps... :/ ?
|
|
}
|
|
else
|
|
{
|
|
testpos[0]=monster.s.origin[0];
|
|
testpos[1]=monster.s.origin[1];
|
|
testpos[2]=monster.s.origin[2]+monster.mins[2]-testpos[2]+MONSTER_SHOOT_HEIGHT;//? Guys often seem to not be able to shoot past each other - this helps... :/ ?
|
|
}
|
|
|
|
if(testbbox == BBOX_PRESET_CROUCH)
|
|
{
|
|
testpos[2] -= 16;//irk
|
|
}
|
|
|
|
VectorCopy(goalpos, targpos);
|
|
targpos[2] += MONSTER_SHOOT_HEIGHT;//same problem for player as for monsters - we need ta do something about this :/
|
|
|
|
if(which_ai.GetBody() && ((which_ai.GetBody()->GetRightHandWeapon(monster)==ATK_ROCKET)||
|
|
(which_ai.GetBody()->GetRightHandWeapon(monster)==ATK_AUTOSHOTGUN)))
|
|
{
|
|
vec3_t shotMin = {-6,-6,-6};
|
|
vec3_t shotMax = {6,6,6};
|
|
//gi.trace(testpos,shotMin,shotMax,targpos,&monster,CONTENTS_SOLID, &tr);
|
|
gi.trace(testpos,shotMin,shotMax,targpos,&monster,MASK_SHOT, &tr);
|
|
}
|
|
else
|
|
{
|
|
//gi.trace(testpos,NULL,NULL,targpos,&monster,CONTENTS_SOLID, &tr);
|
|
gi.trace(testpos,NULL,NULL,targpos,&monster,MASK_SHOT, &tr);
|
|
}
|
|
|
|
lastClearTime = level.time;
|
|
|
|
if ((tr.fraction>0.95 && !tr.startsolid && !tr.allsolid)||tr.ent==ignore)
|
|
//if (tr.ent==ignore)
|
|
{ //why was the second test here? I am confused =(
|
|
//now see if it hit any of tha folks
|
|
/* vec3_t dif;
|
|
VectorSubtract(targpos, testpos, dif);
|
|
float len = VectorLength(dif);
|
|
CRadiusContent rad(testpos, len, 1, 0, 1);
|
|
for(int i = 0; i < rad.getNumFound(); i++)
|
|
{ //if this passes too close to another guy, 'tis no good
|
|
if(rad.foundEdict(i) != ignore && rad.foundEdict(i) != &monster)
|
|
{
|
|
if(pointLineIntersect(testpos, targpos, rad.foundEdict(i)->s.origin, 16))
|
|
{
|
|
isClear = false;
|
|
return false;
|
|
}
|
|
}
|
|
}*/
|
|
if(blockingGuy)
|
|
{
|
|
*blockingGuy = 0;
|
|
}
|
|
|
|
isClear = true;
|
|
return true;
|
|
}
|
|
|
|
if(blockingGuy)
|
|
{
|
|
*blockingGuy = tr.ent - g_edicts;
|
|
}
|
|
|
|
|
|
isClear = false;
|
|
return false;
|
|
}
|
|
|
|
mmove_t *decision_c::GetSequenceForActionCode(ai_c &which_ai, edict_t &monster, action_code the_code, vec3_t dest, vec3_t face, edict_t *target, mmove_t *preferred_move, int reject_actionflags)
|
|
{
|
|
switch (the_code)
|
|
{
|
|
default:
|
|
gi.dprintf("Invalid action code: %d!\n",the_code);
|
|
case ACTCODE_STAND:
|
|
return GetSequenceForStand(which_ai, monster, dest, face, preferred_move, reject_actionflags);
|
|
case ACTCODE_MOVE:
|
|
return GetSequenceForMovement(which_ai, monster, dest, face, preferred_move, reject_actionflags);
|
|
case ACTCODE_ATTACK:
|
|
return GetSequenceForAttack(which_ai, monster, dest, face, target, preferred_move, reject_actionflags);
|
|
|
|
//eek! temporary! fixme soon!
|
|
case ACTCODE_JUMP:
|
|
return GetSequenceForStand(which_ai, monster, dest, face, preferred_move, reject_actionflags);
|
|
case ACTCODE_PAIN:
|
|
return GetSequenceForStand(which_ai, monster, dest, face, preferred_move, reject_actionflags);
|
|
case ACTCODE_DEATH:
|
|
return GetSequenceForStand(which_ai, monster, dest, face, preferred_move, reject_actionflags);
|
|
case ACTCODE_FALL:
|
|
return GetSequenceForStand(which_ai, monster, dest, face, preferred_move, reject_actionflags);
|
|
case ACTCODE_SCRIPTRELEASE:
|
|
return ScriptReleaseMove;
|
|
}
|
|
}
|
|
|
|
mmove_t *decision_c::GetSequenceForStand(ai_c &which_ai, edict_t &monster, vec3_t dest, vec3_t face, mmove_t *preferred_move, int reject_actionflags)
|
|
{
|
|
if (which_ai.GetBody())
|
|
{
|
|
if (preferred_move)
|
|
{
|
|
return which_ai.GetBody()->GetSequenceForStand(monster, dest, face, ACTSUB_NORMAL, preferred_move->bbox, preferred_move, reject_actionflags);
|
|
}
|
|
else
|
|
{
|
|
return which_ai.GetBody()->GetSequenceForStand(monster, dest, face, ACTSUB_NORMAL, BBOX_PRESET_NUMBER, NULL, reject_actionflags);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
float decision_c::GetGroupPosition(ai_c &which_ai, edict_t &monster, vec3_t relativeTo)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
qboolean decision_c::AtGroupFront(ai_c &which_ai, edict_t &monster, vec3_t dest, vec3_t face)
|
|
{
|
|
if (!which_ai.GetMove())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//check to see if guy is inside crouching aim cone
|
|
float aimCone = which_ai.GetAimConeDegrees(monster, BBOX_PRESET_CROUCH);
|
|
float tempAng = anglemod(which_ai.ideal_angles[YAW]-monster.s.angles[YAW]);
|
|
|
|
//i'm outside the aim cone...don't even!
|
|
if (tempAng > aimCone && tempAng < 360.0-aimCone)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(which_ai.getTarget())
|
|
{
|
|
vec3_t dif;
|
|
|
|
// VectorSubtract(monster.s.origin, which_ai.getTarget()->s.origin, dif);
|
|
|
|
vec3_t targ_pos;
|
|
which_ai.getTargetPos(targ_pos);
|
|
VectorSubtract(monster.s.origin, targ_pos, dif);
|
|
|
|
if(VectorLengthSquared(dif) < (64*64))
|
|
{ // if my target is too close to me, I don't want to be ducked - I want to back up
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (rand()&63)
|
|
{
|
|
return (which_ai.GetMove()->bbox==BBOX_PRESET_CROUCH);
|
|
}
|
|
else
|
|
{
|
|
return (which_ai.GetMove()->bbox!=BBOX_PRESET_CROUCH);
|
|
}
|
|
}
|
|
|
|
mmove_t *decision_c::GetSequenceForAttack(ai_c &which_ai, edict_t &monster, vec3_t dest, vec3_t face, edict_t *target, mmove_t *preferred_move, int reject_actionflags)
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (!body)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//if((!AtGroupFront(which_ai, monster, dest, face)||!ClearShot(which_ai, monster, face, target, BBOX_PRESET_CROUCH))||(monster.flags & FL_IAMTHEBOSS))
|
|
{
|
|
return body->GetSequenceForAttack(monster, dest, face, target, ACTSUB_NORMAL, BBOX_PRESET_STAND, preferred_move, reject_actionflags);
|
|
}
|
|
//return body->GetSequenceForAttack(monster, dest, face, target, ACTSUB_NORMAL, BBOX_PRESET_CROUCH, preferred_move, reject_actionflags);
|
|
}
|
|
|
|
mmove_t *decision_c::GetSequenceForJump(ai_c &which_ai, edict_t &monster, vec3_t dest, vec3_t face, mmove_t *preferred_move, int reject_actionflags)
|
|
{
|
|
if (which_ai.GetBody())
|
|
{
|
|
return which_ai.GetBody()->GetSequenceForJump(monster, dest, face, ACTSUB_NORMAL, BBOX_PRESET_CROUCH, preferred_move, reject_actionflags);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
mmove_t *decision_c::GetSequenceForMovement(ai_c &which_ai, edict_t &monster, vec3_t dest, vec3_t face, mmove_t *preferred_move, int reject_actionflags)
|
|
{
|
|
if (which_ai.GetBody())
|
|
{
|
|
return which_ai.GetBody()->GetSequenceForMovement(monster, dest, face, monster.s.origin, monster.s.angles, ACTSUB_NORMAL, BBOX_PRESET_STAND, preferred_move, reject_actionflags);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
mmove_t *decision_c::GetSequenceForDodge(ai_c &which_ai, edict_t &monster, vec3_t dest, vec3_t face, edict_t *target, mmove_t *preferred_move, int leftSide, int reject_actionflags)
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (!body)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return body->GetSequenceForDodge(monster, dest, face, target, ACTSUB_NORMAL, BBOX_PRESET_STAND, preferred_move, leftSide, reject_actionflags);
|
|
}
|
|
|
|
|
|
qboolean decision_c::IsTimedOut(void)
|
|
{
|
|
if (allow_timeout && timeout_time < level.time)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
qboolean decision_c::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
priority = 1;
|
|
|
|
#ifdef _DEBUG
|
|
decName = "decision";
|
|
#endif
|
|
|
|
return IsTimedOut();
|
|
}
|
|
|
|
|
|
void decision_c::Perform(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
vec3_t facedir;
|
|
vec3_t facepos;
|
|
|
|
AngleVectors(monster.s.angles,facedir,NULL,NULL);
|
|
VectorAdd(monster.s.origin, facedir, facepos);
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,facepos,facepos), facepos, facepos);
|
|
}
|
|
|
|
void decision_c::UpdateValidity(void)
|
|
{//edge toward neutral
|
|
float dilution_interval;
|
|
|
|
//for larger time intervals, edge further toward neutral
|
|
dilution_interval = level.time - last_dilution_time;
|
|
|
|
//only accept time intervals between 0 & 1
|
|
//(1 sec should be long enough for max interval--if it's exceeded, won't matter too much)
|
|
if (dilution_interval < 0)
|
|
{
|
|
dilution_interval = 0;
|
|
}
|
|
if (dilution_interval > 1)
|
|
{
|
|
dilution_interval = 1;
|
|
}
|
|
|
|
//weight previous validity tween .9 (for 1 sec interval) and 1.0 (for 0 sec interval),
|
|
//with neutralizing weighted the rest (from 0.0 to 0.1)
|
|
dilution_interval *= 0.1;
|
|
validity = validity*(1-dilution_interval)+0.5*dilution_interval;
|
|
last_dilution_time = level.time;
|
|
}
|
|
|
|
/**********************************************************************************
|
|
**********************************************************************************/
|
|
|
|
path_decision::path_decision(edict_t *goalent, int priority_root, float timeout, edict_t* ScriptOwner)
|
|
:decision_c(priority_root, timeout)
|
|
{
|
|
last_jump_time = level.time;
|
|
path_fail_time = level.time - PATH_FAILURE_REFAIL_DELAY;
|
|
|
|
VectorClear(path_nextpoint.pos);
|
|
VectorClear(path_nextpoint.direction);
|
|
path_nextpoint.targetEnt = NULL;
|
|
path_nextpoint.valid = false;
|
|
path_nextpoint.temp_type = 0;
|
|
path_nextpoint.time = level.time - 1000.0F;
|
|
|
|
path_goalentity = goalent;
|
|
path_goal_updatedtime = level.time - 1000.0F;
|
|
VectorClear(aimWanderVec);
|
|
VectorClear(path_goalpos);
|
|
VectorClear(path_fail_dir);
|
|
|
|
//nathan added junk
|
|
VectorClear(newMoveDir);
|
|
lastCheckTime = 0;
|
|
lastDuckInvalidateTime = 0;
|
|
lastDuckValidTime = 0;
|
|
stuckNonIdeal = false;
|
|
nonIdealReason = NI_TOOFAR;
|
|
blockingEnemy = 0;
|
|
}
|
|
|
|
qboolean path_decision::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
#ifdef _DEBUG
|
|
decName = "pathdecision";
|
|
#endif
|
|
|
|
//don't automatically do this?
|
|
SetGoalPosition(which_ai, monster);
|
|
|
|
//no path, but i do have a goal position
|
|
if (GetGoalPosition(which_ai,monster,NULL)&&which_ai.GetBody()&&which_ai.GetBody()->GetBestWeapon(monster)!=ATK_NOTHING)
|
|
{
|
|
priority = 2.5+floor(validity+0.5);
|
|
}
|
|
//no goal position
|
|
else
|
|
{
|
|
priority = 1;
|
|
}
|
|
|
|
UpdateValidity();
|
|
|
|
//if decision can time out, and its time has expired, then let ai get rid of it
|
|
return IsTimedOut();
|
|
}
|
|
|
|
void path_decision::Perform(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
// this is problematic
|
|
|
|
/* if((which_ai.GetPriority() != PRIORITY_HIGH)&&(path_goalentity == level.sight_client))
|
|
{ // no more than a set number of guys can actively attack the player at once.
|
|
// rework this?
|
|
return;
|
|
}*/
|
|
AddAction(which_ai, monster);
|
|
}
|
|
|
|
|
|
void path_decision::SetGoalPosition(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
vec3_t temp_v;
|
|
sensedEntInfo_t sensed_client, sensed_monster;
|
|
which_ai.GetSensedClientInfo(smask_all, sensed_client);
|
|
which_ai.GetSensedMonsterInfo(smask_all, sensed_monster);
|
|
//if i'm already after someone, keep at 'im
|
|
if (path_goalentity && (path_goalentity->ai || path_goalentity->client)
|
|
&& path_goalentity != sensed_client.ent
|
|
&& path_goalentity != sensed_monster.ent && path_goalentity->inuse
|
|
&& path_goalentity->health > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//give preference to attacking player by default
|
|
if (sensed_client.ent && sensed_client.ent->health>0 &&
|
|
(fabs(path_goal_updatedtime - sensed_client.time)<0.01 || path_goal_updatedtime < sensed_client.time)&&
|
|
// (sensed_client.time + 10 > level.time) &&
|
|
IsAdversary(monster,sensed_client.ent))
|
|
{
|
|
path_goalentity = sensed_client.ent;
|
|
VectorCopy(sensed_client.pos,path_goalpos);
|
|
path_goal_updatedtime = sensed_client.time;
|
|
|
|
VectorAdd(sensed_client.ent->mins,sensed_client.ent->maxs,temp_v);
|
|
VectorScale(temp_v,0.5,temp_v);
|
|
VectorAdd(path_goalpos,temp_v,path_goalpos);
|
|
}
|
|
else
|
|
{
|
|
if (sensed_monster.ent && sensed_monster.ent->inuse &&
|
|
(fabs(path_goal_updatedtime - sensed_monster.time)<0.01 || path_goal_updatedtime < sensed_monster.time)&&
|
|
// (sensed_monster.time + 10 > level.time) &&
|
|
IsAdversary(monster,sensed_monster.ent))
|
|
{
|
|
path_goalentity = sensed_monster.ent;
|
|
VectorCopy(sensed_monster.pos,path_goalpos);
|
|
path_goal_updatedtime = sensed_monster.time;
|
|
|
|
VectorAdd(sensed_monster.ent->mins,sensed_monster.ent->maxs,temp_v);
|
|
VectorScale(temp_v,0.5,temp_v);
|
|
VectorAdd(path_goalpos,temp_v,path_goalpos);
|
|
}
|
|
}
|
|
}
|
|
|
|
qboolean path_decision::GetGoalPosition(ai_c &which_ai, edict_t &monster, vec3_t gohere)
|
|
{
|
|
//copy ai's enemy_lastseen_pos into gohere vec
|
|
if (gohere)//pass in NULL if just want to know whether we have goal position
|
|
{
|
|
VectorCopy(path_goalpos, gohere);
|
|
}
|
|
|
|
//check the decision's goalentity for whether i have goal pos, not ai's enemy
|
|
if (path_goalentity)
|
|
{
|
|
//make sure goal entity is still valid
|
|
if ((!path_goalentity->client && !path_goalentity->ai) || !path_goalentity->inuse
|
|
|| path_goalentity->health <= 0 || level.time-path_goal_updatedtime>PATH_DECISION_LOSETARGETTIME)
|
|
{
|
|
path_goalentity = NULL;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//depending on type of path point path_nextpoint is, add appropriate action
|
|
qboolean path_decision::AddSpecialPathAction(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
vec3_t facedir,facevec;
|
|
vec3_t facepos;
|
|
|
|
//if i don't have a current pathpoint, i'm here by accident--just throw a default action on my stack
|
|
if (!path_nextpoint.valid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
VectorSubtract(path_nextpoint.pos, monster.s.origin, facedir);
|
|
VectorCopy(facedir,facevec);
|
|
VectorNormalize(facedir);
|
|
|
|
//next point in path is a temppoint--check its spawnflags to see what kinda point it is
|
|
//i'm considering all temp points to have special actions associated withem
|
|
//this temp point is already stale
|
|
if (level.time - path_nextpoint.time > 1.0)
|
|
{
|
|
AngleVectors(monster.s.angles,facedir,NULL,NULL);
|
|
VectorAdd(monster.s.origin, facedir, facepos);
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,monster.s.origin,facepos), monster.s.origin, facepos);
|
|
return true;
|
|
}
|
|
//jumping path point--assuming path points are close enough together that z difference greater than stepheight requires jump
|
|
if (path_nextpoint.temp_type==1)
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForJump(which_ai,monster,path_nextpoint.pos,path_nextpoint.pos), path_nextpoint.pos, path_nextpoint.pos);
|
|
return true;
|
|
}
|
|
//falling path point--assuming path points are close enough together that z difference greater than stepheight requires fall
|
|
if (path_nextpoint.temp_type==2)
|
|
{
|
|
AddActionForSequence(which_ai, monster, &generic_move_jumpdrop, path_nextpoint.pos, path_nextpoint.pos);
|
|
return true;
|
|
}
|
|
//use a brush
|
|
if (path_nextpoint.temp_type==3)
|
|
{
|
|
//make sure i know what brush to use, and it's got a valid use function
|
|
if (path_nextpoint.targetEnt && path_nextpoint.targetEnt->plUse)
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
path_nextpoint.targetEnt->plUse(path_nextpoint.targetEnt, &monster, &monster);
|
|
//urk, do an appropriate thing here
|
|
VectorScale(path_nextpoint.direction, 20, facepos);
|
|
VectorAdd(facepos, monster.s.origin, facepos);
|
|
if (body)
|
|
{
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForStand(monster, facepos, facepos, ACTSUB_NORMAL, BBOX_PRESET_STAND, &generic_move_opendoor), facepos, facepos);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, &generic_move_opendoor, facepos, facepos);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//?
|
|
return false;
|
|
|
|
//normal walking point
|
|
VectorScale(facedir,(VectorLength(which_ai.velocity)+10)*0.1,facepos);
|
|
VectorAdd(facepos, monster.s.origin, facepos);
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,facepos,facepos), facepos, facepos);
|
|
return true;
|
|
}
|
|
|
|
#define ADJUST_SEARCH_RANGE 100.0
|
|
|
|
void rotMe(vec3_t in, float ang, vec3_t out)
|
|
{
|
|
vec3_t temp;
|
|
float cosAng = cos(ang*M_PI/180);
|
|
float sinAng = sin(ang*M_PI/180);
|
|
|
|
temp[0] = in[0] * cosAng + in[1] * sinAng;
|
|
temp[1] = - in[0] * sinAng + in[1] * cosAng;
|
|
temp[2] = in[2];
|
|
|
|
VectorCopy(temp, out);
|
|
}
|
|
|
|
void debug_drawbox(edict_t* self,vec3_t vOrigin, vec3_t vMins, vec3_t vMaxs, int nColor);
|
|
|
|
float guyCheckForMove(vec3_t start, vec3_t dir, int requestMovement, edict_t *self)
|
|
{
|
|
vec3_t testCenter;
|
|
|
|
VectorMA(start, 50, dir, testCenter);
|
|
|
|
CRadiusContent rad(testCenter, 100, 1, 0);//so we get a rough, decent test spot
|
|
|
|
VectorMA(start, 150, dir, testCenter);// the endpoint of my move test
|
|
|
|
float distAchieved = 1.0;
|
|
|
|
for(int i = 0; i < rad.getNumFound(); i++)
|
|
{
|
|
if(rad.foundEdict(i) == self)
|
|
{ //sillyness
|
|
continue;
|
|
}
|
|
|
|
if(pointLineIntersect(start, testCenter, rad.foundEdict(i)->s.origin, 30))
|
|
{
|
|
vec3_t dif;
|
|
|
|
VectorSubtract(rad.foundEdict(i)->s.origin, start, dif);
|
|
|
|
float newDist = VectorLengthSquared(dif) / (150 * 150);//see how good this route is
|
|
|
|
vec3_t goPlease;
|
|
|
|
VectorCopy(dif, goPlease);
|
|
VectorNormalize(goPlease);
|
|
if(rad.foundEdict(i)->ai)
|
|
{
|
|
(rad.foundEdict(i)->ai)->RequestMoveOutOfWay(goPlease);
|
|
}
|
|
|
|
if((newDist < distAchieved) && (newDist > 0.005))//don't grab myself
|
|
{
|
|
distAchieved = newDist;
|
|
}
|
|
}
|
|
}
|
|
return distAchieved;
|
|
}
|
|
|
|
float checkVectForMove(vec3_t start, vec3_t dir, vec3_t min, vec3_t max, edict_t *ent, edict_t **hitEnt, int requestMovement)
|
|
{
|
|
vec3_t end;
|
|
trace_t walktrace;
|
|
|
|
VectorMA(start, 60, dir, end);
|
|
gi.trace (start, min, max, end, ent, MASK_MONSTERSOLID, &walktrace);
|
|
|
|
if(hitEnt)
|
|
{
|
|
*hitEnt = walktrace.ent;
|
|
}
|
|
|
|
float frac = guyCheckForMove(start, dir, requestMovement, ent);
|
|
|
|
// if(walktrace.fraction < .8)
|
|
{
|
|
return walktrace.fraction * frac;
|
|
}
|
|
|
|
return 1.0 * frac;
|
|
/*
|
|
VectorMA(start, 32, dir, end);//???????
|
|
VectorCopy(end, down);
|
|
down[2] -= (24.0+32+8);// furthest a guy can consider
|
|
|
|
gi.trace (end, vec3_origin, vec3_origin, down, ent, MASK_MONSTERSOLID, &walktrace);
|
|
|
|
if(walktrace.fraction < 1.0)
|
|
{ // this is what we hope for
|
|
return 1.0;
|
|
}
|
|
|
|
return 0.0;//BAD*/
|
|
}
|
|
|
|
//try some different angles to run and see if they're any better
|
|
int findBestDir(vec3_t start, vec3_t dir, vec3_t outDir, float speed, edict_t &guy, ai_c &which_ai)
|
|
{ //returns whether ANY paths were found.
|
|
|
|
float clear[5];
|
|
vec3_t dirs[5];
|
|
float firstDir;//1.0 or -1.0; randomly do left or right first
|
|
vec3_t testDir;
|
|
vec3_t testMin;
|
|
int i;
|
|
|
|
// Com_Printf("Doing bump check at %f\n", (float)level.time);
|
|
|
|
VectorCopy(guy.mins, testMin);
|
|
testMin[2] += 16;//eh?
|
|
|
|
int ang = 40;
|
|
|
|
//clear out clears, so we can safely abort the clear-checking process
|
|
for(i = 0; i < 5; i++)
|
|
{
|
|
clear[i]=0.0;
|
|
}
|
|
|
|
//check the direct route first, and abort as soon as we got a totally clear path
|
|
clear[0] = checkVectForMove(start, dir, testMin, guy.maxs, &guy, 0, 0);
|
|
VectorCopy(dir, dirs[0]);
|
|
if (clear[0]<0.99)
|
|
{
|
|
for(i = 1; i < 5; i++)
|
|
{
|
|
firstDir = (float)(gi.irand(0,1)*2-1);//1.0 or -1.0
|
|
rotMe(dir, ang*firstDir, testDir);
|
|
|
|
clear[i] = checkVectForMove(start, testDir, testMin, guy.maxs, &guy, 0, 0);
|
|
VectorCopy(testDir, dirs[i]);
|
|
|
|
if (clear[i]>0.99)//abort: we got our dir
|
|
break;
|
|
|
|
i++;//odd way to deal with a for loop, eh?
|
|
|
|
rotMe(dir, ang*firstDir*-1.0, testDir);
|
|
|
|
clear[i] = checkVectForMove(start, testDir, testMin, guy.maxs, &guy, 0, 0);
|
|
VectorCopy(testDir, dirs[i]);
|
|
|
|
if (clear[i]>0.99)//abort: we got our dir
|
|
break;
|
|
|
|
ang += 40;
|
|
//if((i == 2)||(i == 5))clear[i] *= .7;//these are totally perp and less good
|
|
}
|
|
}
|
|
|
|
//priority should be for directions that are similar to the current heading and have not much crowding
|
|
int bestVal = -1;
|
|
float bestDist = 0.2;
|
|
for(i = 0; i < 5; i++)
|
|
{
|
|
if(clear[i] > bestDist)
|
|
{
|
|
bestDist = clear[i];
|
|
bestVal = i;
|
|
}
|
|
}
|
|
if(bestVal > -1)
|
|
{
|
|
VectorCopy(dirs[bestVal], outDir);
|
|
return 1;
|
|
//FX_MakeElectricArc(start, 60, dirs[bestVal]);
|
|
}
|
|
else
|
|
{ //if all else fails, well, yer screwed. Sit still and shoot people...
|
|
VectorClear(outDir);
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
void path_decision::RecalcMoveVec(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
vec3_t point;
|
|
vec3_t testMin;
|
|
|
|
if(!path_nextpoint.valid || (aiPoints.isActive()))
|
|
{
|
|
VectorCopy(goalPos, point);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(path_nextpoint.pos, point);
|
|
}
|
|
|
|
VectorSubtract(point, monster.s.origin, newMoveDir);
|
|
VectorNormalize(newMoveDir);
|
|
|
|
//if my priority is low, just accept the ideal direction and be clumsy. hell, nobody'll see.
|
|
if (which_ai.GetPriority()==PRIORITY_LOW)
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorCopy(monster.mins, testMin);
|
|
testMin[2] += 16;
|
|
|
|
edict_t *hitGuy;
|
|
|
|
float clear = checkVectForMove(monster.s.origin, newMoveDir, testMin, monster.maxs, &monster, &hitGuy, 1);
|
|
|
|
//if i'm going to hit something that's not a useable brush, adjust my course
|
|
if(clear != 1.0 && (!hitGuy || !IsUseableBrush(hitGuy) ||
|
|
!IsBrushUseableNow(hitGuy, level.time, &monster)))
|
|
{
|
|
if(!findBestDir(monster.s.origin, newMoveDir, newMoveDir, VectorLength(which_ai.velocity)+10, monster, which_ai))
|
|
{
|
|
//stuckNonIdeal = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void path_decision::GetMovementVector(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
return;
|
|
#if 0
|
|
stuckNonIdeal = false;
|
|
if(monster.spawnflags & SPAWNFLAG_HOLD_POSITION)
|
|
{ //fixme - these guys need a bit of room for movement...
|
|
VectorClear(moveVec);
|
|
stuckNonIdeal = false;
|
|
return;
|
|
}
|
|
|
|
int normalWander = 1;
|
|
|
|
vec3_t targ;
|
|
vec3_t dif;
|
|
VectorSubtract(goalPos, monster.s.origin, dif);
|
|
if((GetClassCode() == PURSUE_DECISION)&&( (monster.flags) ||
|
|
(DotProduct(dif, dif) < which_ai.GetMySkills()->getRangeMin()*which_ai.GetMySkills()->getRangeMin())))
|
|
{ // too close! Just go directly away
|
|
//if(aiPoints.isActive())
|
|
if(0)
|
|
{
|
|
aiPoints.getReversePath(monster.s.origin, goalPos, &which_ai.getPathData(), &which_ai, &monster);
|
|
VectorCopy(which_ai.getPathData().goPoint, targ);
|
|
|
|
Enemy_Printf(&which_ai, "backup:%d\n", GetClassCode());
|
|
|
|
normalWander = 0;
|
|
|
|
if(which_ai.getPathData().blocked)
|
|
{ // can't go where I want to :(
|
|
VectorCopy(monster.s.origin, targ);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorNormalize(dif);
|
|
VectorScale(dif, -4.0, dif);
|
|
VectorAdd(dif, monster.s.origin, targ);
|
|
|
|
normalWander = 0;
|
|
}
|
|
}
|
|
|
|
float *avoidDir;
|
|
avoidDir = which_ai.GetMoveOutOfWay();
|
|
int wanderDirSet = 0;
|
|
|
|
if(avoidDir[0] || avoidDir[1] || avoidDir[2])
|
|
{
|
|
wanderDirSet = 1;
|
|
VectorMA(monster.s.origin, 64, avoidDir, targ);
|
|
VectorCopy(avoidDir, aimWanderVec);
|
|
normalWander = 0;
|
|
}
|
|
|
|
/* if(which_ai.GetMySkills()->testFlag(AIS_NOWANDER))
|
|
{
|
|
normalWander = 0;
|
|
}*/
|
|
|
|
if(normalWander)
|
|
{
|
|
//adjust where i'll wander whilst aiming
|
|
//ick, this is more expensive than it should be
|
|
/* CRadiusContent rad(monster.s.origin, 100);
|
|
vec3_t avoidDir;
|
|
if(rad.getNumFound())
|
|
{
|
|
VectorSubtract(rad.foundEdict(0)->s.origin, monster.s.origin, avoidDir);
|
|
for(int i = 1; i < rad.getNumFound(); i++)
|
|
{
|
|
vec3_t dif;
|
|
|
|
VectorSubtract(rad.foundEdict(i)->s.origin, monster.s.origin, dif);
|
|
if(DotProduct(dif, avoidDir) < 0)
|
|
{//conflicting, so go nowhere
|
|
VectorClear(avoidDir);
|
|
break;
|
|
}
|
|
}
|
|
VectorScale(avoidDir, 20, aimWanderVec);
|
|
}
|
|
else*/
|
|
{
|
|
float wanderDist = 20;
|
|
vec3_t toGoal;
|
|
float distToGoal;
|
|
|
|
//get my goal vector, to make sure i don't wander too close/far to goal
|
|
VectorSubtract(goalPos, monster.s.origin, toGoal);
|
|
toGoal[2]=0;
|
|
distToGoal=VectorNormalize(toGoal);
|
|
|
|
//no wandering up or down!
|
|
aimWanderVec[2]=0;
|
|
|
|
float minRange = which_ai.GetMySkills()->getRangeMin();
|
|
float maxRange = which_ai.GetMySkills()->getRangeMax();
|
|
|
|
//keep a cap on the wandering vector
|
|
wanderDist=VectorNormalize(aimWanderVec);
|
|
if(!wanderDirSet)
|
|
{
|
|
aimWanderVec[0]+=gi.flrand(-3,3);
|
|
aimWanderVec[1]+=gi.flrand(-3,3);
|
|
|
|
if (wanderDist>20)
|
|
{
|
|
wanderDist=20;
|
|
}
|
|
//too close
|
|
//if (distToGoal < 100)
|
|
if (distToGoal < minRange)
|
|
{
|
|
if (distToGoal<1)
|
|
{
|
|
distToGoal=1;
|
|
}
|
|
//going wrong way--reverse direction
|
|
if (DotProduct(aimWanderVec,toGoal)>0.0)
|
|
{
|
|
wanderDist*=-0.5;
|
|
//from a 2:1 to a 1:100 ratio of totallyAway:cur, decreasing the farther i am to guy
|
|
//VectorMA(aimWanderVec, -((101-distToGoal)/100), toGoal, aimWanderVec);
|
|
VectorMA(aimWanderVec, -((minRange + 1 - distToGoal)/minRange), toGoal, aimWanderVec);
|
|
VectorNormalize(aimWanderVec);
|
|
}
|
|
else
|
|
{
|
|
//going away--go faster (but i wanna go more clearly away too)
|
|
wanderDist+=1;
|
|
VectorInverse(toGoal);
|
|
//from a 1:1 to a 1:100 ratio of totallyAway:cur, decreasing the farther i am to guy
|
|
//VectorMA(aimWanderVec, ((101-distToGoal)/100), toGoal, aimWanderVec);
|
|
VectorMA(aimWanderVec, ((minRange + 1 - distToGoal)/minRange), toGoal, aimWanderVec);
|
|
VectorNormalize(aimWanderVec);
|
|
}
|
|
}
|
|
//too far
|
|
//if (distToGoal > 300)
|
|
if (distToGoal > maxRange)
|
|
{
|
|
//if (distToGoal>349)
|
|
//{
|
|
//distToGoal=349;
|
|
//}
|
|
if (distToGoal>maxRange + 50)
|
|
{
|
|
distToGoal=maxRange + 50;
|
|
}
|
|
//going wrong way--reverse direction
|
|
if (DotProduct(aimWanderVec,toGoal)<0.0)
|
|
{
|
|
wanderDist*=-0.5;
|
|
VectorInverse(toGoal);
|
|
//from a 1:1 to a 1:50 ratio of cur:totallyAway, decreasing the closer i am to guy
|
|
//VectorMA(aimWanderVec, (distToGoal-299)/50, toGoal, aimWanderVec);
|
|
VectorMA(aimWanderVec, (distToGoal - maxRange + 1)/50, toGoal, aimWanderVec);
|
|
VectorNormalize(aimWanderVec);
|
|
}
|
|
else
|
|
{
|
|
//going toward--go faster (but i wanna go more clearly toward too)
|
|
wanderDist+=1;
|
|
//from a 3:1 to a 1:50 ratio of cur:totallyAway, decreasing the closer i am to guy
|
|
//VectorMA(aimWanderVec, (distToGoal-299)/50, toGoal, aimWanderVec);
|
|
VectorMA(aimWanderVec, (distToGoal - maxRange + 1)/50, toGoal, aimWanderVec);
|
|
VectorNormalize(aimWanderVec);
|
|
}
|
|
}
|
|
}
|
|
VectorScale(aimWanderVec,wanderDist,aimWanderVec);
|
|
}
|
|
|
|
trace_t tr;
|
|
|
|
vec3_t spot;
|
|
|
|
VectorAdd(monster.s.origin, aimWanderVec, spot);
|
|
VectorAdd(spot, aimWanderVec, spot);//test a bit farther to see if we strike anything
|
|
|
|
vec3_t testmin, testmax;
|
|
VectorAdd(spot, monster.mins, testmin);
|
|
VectorAdd(spot, monster.maxs, testmax);
|
|
/*if(!aiPoints.inRegion(which_ai.getPathData().curRegion, testmin, testmax))
|
|
{
|
|
VectorClear(aimWanderVec);
|
|
}
|
|
else*/
|
|
{
|
|
gi.trace(monster.s.origin, monster.mins, monster.maxs, spot, &monster, MASK_MONSTERSOLID, &tr);
|
|
if(tr.fraction < 1.0)
|
|
{
|
|
VectorClear(aimWanderVec);//guys shouldn't wander into walls
|
|
}
|
|
}
|
|
//if(aiPoints.isActive())
|
|
if(0)
|
|
{
|
|
aiPoints.getPath(monster.s.origin, goalPos, &which_ai.getPathData(), &which_ai, &monster);
|
|
VectorCopy(which_ai.getPathData().goPoint, targ);
|
|
|
|
Enemy_Printf(&which_ai, "forward:%d(%d->%d=%d)\n", GetClassCode(),
|
|
which_ai.getPathData().curNode, which_ai.getPathData().nextNode, which_ai.getPathData().finalNode);
|
|
|
|
if(which_ai.getPathData().blocked)
|
|
{ // can't go where I want to :(
|
|
VectorCopy(monster.s.origin, targ);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(goalPos, targ);
|
|
}
|
|
}
|
|
|
|
if(monster.flags & FL_IAMTHEBOSS)
|
|
{ // the boss isn't interested in normal wandering
|
|
VectorClear(aimWanderVec);
|
|
}
|
|
|
|
RecalcMoveVec(which_ai, monster, targ, moveVec);
|
|
lastCheckTime = level.time;
|
|
|
|
VectorScale(newMoveDir, VectorLength(which_ai.velocity)+10, moveVec);
|
|
|
|
if(!normalWander)
|
|
{ // one and the same when you really need to back up como este
|
|
VectorCopy(moveVec, aimWanderVec);
|
|
VectorNormalize(aimWanderVec);
|
|
VectorScale(aimWanderVec, 20, aimWanderVec);
|
|
}
|
|
|
|
VectorClear(avoidDir);
|
|
#endif
|
|
//if 0'd this whole section here
|
|
}
|
|
|
|
|
|
qboolean path_decision::IsIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t curPos, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
vec3_t toGoal;
|
|
float distToGoal;
|
|
|
|
blockingEnemy = 0;
|
|
|
|
vec3_t request = {0,0,0};//uhoh
|
|
which_ai.RequestMoveOutOfWay(request);
|
|
|
|
if(which_ai.BeingWatched())
|
|
{
|
|
int asdf = 9;
|
|
}
|
|
|
|
if(monster.spawnflags & SPAWNFLAG_HOLD_POSITION)
|
|
{//very special case - there guys are always in an ideal position, sort of
|
|
nonIdealReason = NI_IDEALPOSITION;
|
|
which_ai.RequestMoveOutOfWay(vec3_origin);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
if(body->TestDamage())
|
|
{//damaged guys are never ever happy - they want to get away
|
|
which_ai.getPathData().backingUp = 1;
|
|
Enemy_Printf(&which_ai, "NIR - too close\n");
|
|
nonIdealReason = NI_TOOCLOSE;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
if (level.time - path_goal_updatedtime > PATH_DECISION_LOSETARGETTIME)
|
|
{
|
|
Enemy_Printf(&which_ai, "NIR - out of time\n");
|
|
nonIdealReason = NI_TOOFAR;//? Who cares, I suppose
|
|
return false;
|
|
}
|
|
VectorSubtract(goalPos, curPos, toGoal);
|
|
distToGoal=VectorLength(toGoal);
|
|
if (distToGoal < 10)
|
|
{
|
|
which_ai.RequestMoveOutOfWay(vec3_origin);
|
|
return true;
|
|
}
|
|
|
|
if(!which_ai.GetMove())
|
|
{
|
|
Enemy_Printf(&which_ai, "NIR - don't have a move\n");
|
|
nonIdealReason = NI_TOOFAR;//This I find confusing
|
|
return false;
|
|
}
|
|
|
|
if((distToGoal >= which_ai.GetMySkills()->getRangeMax()*.75) && (!which_ai.GetMySkills()->testFlag(AIS_WONTADVANCE)))
|
|
{
|
|
if(which_ai.getPathData().approaching || (distToGoal >= which_ai.GetMySkills()->getRangeMax()*1.2))
|
|
{
|
|
which_ai.getPathData().approaching = 1;
|
|
Enemy_Printf(&which_ai, "NIR - too far\n");
|
|
nonIdealReason = NI_TOOFAR;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
which_ai.getPathData().approaching = 0;
|
|
}
|
|
|
|
if(stuckNonIdeal)
|
|
{
|
|
//I hate this ;(
|
|
Enemy_Printf(&which_ai, "NIR - stuck nonideal\n");
|
|
nonIdealReason = NI_TOOFAR;//?
|
|
return false;
|
|
}
|
|
|
|
if(distToGoal <= which_ai.GetMySkills()->getRangeMin()*1.2)
|
|
{ // try to avoid extremely abrupt movements
|
|
if(which_ai.getPathData().backingUp || (distToGoal <= which_ai.GetMySkills()->getRangeMin()*.75))
|
|
{
|
|
which_ai.getPathData().backingUp = 1;
|
|
Enemy_Printf(&which_ai, "NIR - too close\n");
|
|
nonIdealReason = NI_TOOCLOSE;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
which_ai.getPathData().backingUp = 0;
|
|
}
|
|
|
|
if(!which_ai.GetMove() || !ClearShot(which_ai, monster, goalPos, path_goalentity, which_ai.GetMove()->bbox, 0, &blockingEnemy))
|
|
{
|
|
Enemy_Printf(&which_ai, "NIR - no clear shot\n");
|
|
nonIdealReason = NI_NOTCLEAR;
|
|
return false;
|
|
}
|
|
|
|
nonIdealReason = NI_IDEALPOSITION;
|
|
which_ai.RequestMoveOutOfWay(vec3_origin);
|
|
return true;
|
|
}
|
|
|
|
void BuildVectorForGuys(edict_t *source, vec3_t desiredVect, float radius = 64, int *nearProblems = 0)
|
|
{
|
|
CRadiusContent rad(source->s.origin, radius*2, 1, 1);
|
|
|
|
for(int i = 0; i < rad.getNumFound(); i++)
|
|
{
|
|
if(rad.foundEdict(i) == source)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ai_c *otherai = (ai_c *)((ai_public_c *)(rad.foundEdict(i)->ai));
|
|
if(otherai)
|
|
{
|
|
if(!(*otherai->GetMoveOutOfWay()))
|
|
{ //this guy is fine - don't move for him
|
|
continue;
|
|
}
|
|
}
|
|
|
|
vec3_t dif;
|
|
|
|
VectorSubtract(rad.foundEdict(i)->s.origin, source->s.origin, dif);
|
|
float distAway = VectorNormalize(dif);
|
|
|
|
*nearProblems = 1;
|
|
|
|
if(distAway > radius)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(DotProduct(otherai->GetMoveOutOfWay(), dif) < 0)
|
|
{ //this guy is going somewhere else - big deal
|
|
continue;
|
|
}
|
|
|
|
float mVal = distAway / radius;
|
|
mVal *= mVal;
|
|
|
|
|
|
if(DotProduct(dif, desiredVect) > 0)
|
|
{
|
|
//VectorCopy(dif, desiredVect);
|
|
VectorScale(desiredVect, mVal, desiredVect);
|
|
VectorMA(desiredVect, 1.0 - mVal, dif, desiredVect);
|
|
}
|
|
else
|
|
{
|
|
//VectorScale(dif, -1, desiredVect);
|
|
VectorScale(desiredVect, mVal, desiredVect);
|
|
VectorMA(desiredVect, -1.0 * (1.0 - mVal), dif, desiredVect);
|
|
}
|
|
}
|
|
desiredVect[2] = 0;
|
|
VectorNormalize(desiredVect);
|
|
}
|
|
|
|
void path_decision::AddActionIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
which_ai.GetMiscFlags() &= ~MISC_FLAG_FIRINGBLIND;
|
|
|
|
vec3_t atkPos,wanderPos;
|
|
if (path_goalentity->client)
|
|
{
|
|
VectorScale(path_goalentity->velocity, 0.2, atkPos);
|
|
}
|
|
else if (path_goalentity->ai)
|
|
{
|
|
VectorScale(path_goalentity->ai->velocity, 0.2, atkPos);
|
|
}
|
|
else
|
|
{
|
|
VectorClear(atkPos);
|
|
}
|
|
VectorAdd(goalPos, atkPos, atkPos);
|
|
VectorCopy(monster.s.origin, wanderPos);
|
|
// VectorAdd(monster.s.origin, aimWanderVec, wanderPos);
|
|
|
|
// VectorClear(wanderPos);//go nowhere right now! You love this spot!
|
|
|
|
vec3_t wanderDir;
|
|
VectorClear(wanderDir);
|
|
int nearbyUnruly = 0;
|
|
// BuildVectorForGuys(&monster, wanderDir, 96, &nearbyUnruly);
|
|
// int mustMove = (VectorLengthSquared(wanderDir) > .1);
|
|
// VectorMA(monster.s.origin, 32, wanderDir, wanderPos);
|
|
|
|
// if(mustMove)
|
|
// {
|
|
// vec3_t request = {1,0,0};//uhoh
|
|
// which_ai.RequestMoveOutOfWay(wanderDir);
|
|
// }
|
|
int mustMove = 0;
|
|
|
|
int attack = 1;
|
|
|
|
//no attacking if we're not ready to harm the player
|
|
float awakeTimeValue = level.time - which_ai.getFirstTargetTime();
|
|
//if(awakeTimeValue < which_ai.GetMySkills()->getHesitation() && (which_ai.GetStartleability()))
|
|
if(awakeTimeValue < which_ai.GetMySkills()->getHesitation() * ((gmonster.GetClosestEnemy() == &monster) ? .5:1))
|
|
{ // we are groggy for a bit when we first arise
|
|
attack = 0;
|
|
}
|
|
|
|
/* aiPoints.idealWanderPoint(&which_ai.getPathData(), &which_ai);
|
|
if(!which_ai.getPathData().blocked)
|
|
{
|
|
VectorCopy(which_ai.getPathData().goPoint, wanderPos);
|
|
mustMove = 1;
|
|
}*/
|
|
|
|
if(attack)
|
|
{
|
|
//Enemy_Printf(&which_ai, "Ideal attack \n");
|
|
//AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, wanderPos, goalPos, path_goalentity), wanderPos, goalPos, path_goalentity);
|
|
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
//mmove_t *myMove = body->GetSequenceForAttack(monster, dest, face, target, ACTSUB_NORMAL, BBOX_PRESET_STAND, preferred_move, reject_actionflags);
|
|
vec3_t fwd;
|
|
vec3_t toFace;
|
|
//which_ai.GetAimVector(fwd);
|
|
//vertical does not matter
|
|
AngleVectors(monster.s.angles, fwd, 0, 0);
|
|
fwd[2] = 0;
|
|
VectorNormalize(fwd);
|
|
VectorSubtract(goalPos, monster.s.origin, toFace);
|
|
toFace[2] = 0;
|
|
VectorNormalize(toFace);
|
|
mmove_t *myMove;
|
|
|
|
if((!which_ai.GetMySkills()->testFlag(AIS_NODUCK)) &&
|
|
(DotProduct(toFace, fwd) > .98) &&
|
|
!mustMove &&
|
|
!nearbyUnruly &&
|
|
((lastDuckInvalidateTime > lastDuckValidTime + 3.0)||(lastDuckInvalidateTime < lastDuckValidTime)) &&
|
|
ClearShot(which_ai, monster, goalPos, path_goalentity, BBOX_PRESET_CROUCH))
|
|
{
|
|
lastDuckValidTime = level.time;
|
|
myMove = body->GetSequenceForAttack(monster, wanderPos, goalPos, path_goalentity, ACTSUB_NORMAL, BBOX_PRESET_CROUCH);
|
|
}
|
|
else if(ClearShot(which_ai, monster, goalPos, path_goalentity, BBOX_PRESET_STAND))
|
|
{
|
|
lastDuckInvalidateTime = level.time;
|
|
myMove = body->GetSequenceForAttack(monster, wanderPos, goalPos, path_goalentity, ACTSUB_NORMAL, BBOX_PRESET_STAND);
|
|
}
|
|
else if((DotProduct(toFace, fwd) > .98)&&
|
|
((lastDuckInvalidateTime > lastDuckValidTime + 3.0)||(lastDuckInvalidateTime < lastDuckValidTime))&&
|
|
(!which_ai.GetMySkills()->testFlag(AIS_NODUCK)))
|
|
{ //well, at the very least minimize surface area
|
|
lastDuckValidTime = level.time;
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH);
|
|
}
|
|
else
|
|
{ //can't turn while crouched ;(
|
|
lastDuckInvalidateTime = level.time;
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_STAND);
|
|
}
|
|
|
|
AddActionForSequence(which_ai, monster, myMove, wanderPos, goalPos, path_goalentity);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
Enemy_Printf(&which_ai, "Ideal stand\n");
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_STAND), goalPos, goalPos);
|
|
//AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,wanderPos,goalPos), wanderPos, goalPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AdjustVectorByGuys(edict_t *source, vec3_t desiredVect, float radius = 96)
|
|
{
|
|
CRadiusContent rad(source->s.origin, radius, 1, 1);
|
|
|
|
for(int i = 0; i < rad.getNumFound(); i++)
|
|
{
|
|
if(rad.foundEdict(i) == source)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
vec3_t dif;
|
|
|
|
VectorSubtract(rad.foundEdict(i)->s.origin, source->s.origin, dif);
|
|
float distAway = VectorNormalize(dif);
|
|
|
|
if(DotProduct(desiredVect, dif) < 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//now do the incorrect rotation
|
|
float temp = dif[0];
|
|
dif[0] = dif[1];
|
|
dif[1] = -temp;
|
|
|
|
float mVal = distAway / radius;
|
|
mVal *= mVal;
|
|
|
|
|
|
if(DotProduct(dif, desiredVect) > 0)
|
|
{
|
|
//VectorCopy(dif, desiredVect);
|
|
VectorScale(desiredVect, mVal, desiredVect);
|
|
VectorMA(desiredVect, 1.0 - mVal, dif, desiredVect);
|
|
}
|
|
else
|
|
{
|
|
//VectorScale(dif, -1, desiredVect);
|
|
VectorScale(desiredVect, mVal, desiredVect);
|
|
VectorMA(desiredVect, -1.0 * (1.0 - mVal), dif, desiredVect);
|
|
}
|
|
}
|
|
desiredVect[2] = 0;
|
|
VectorNormalize(desiredVect);
|
|
}
|
|
|
|
|
|
void path_decision::AddActionNonIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
vec3_t actionGoal;
|
|
float move_dist_sq;
|
|
|
|
// move_dist_sq = VectorLengthSquared(moveVec);
|
|
move_dist_sq = 26;
|
|
|
|
if(which_ai.BeingWatched())
|
|
{
|
|
int asdf = 9;
|
|
}
|
|
|
|
int attack = 1;
|
|
|
|
if(nonIdealReason == NI_TOOFAR_SEARCHING)
|
|
{
|
|
attack = 0;
|
|
}
|
|
|
|
if(nonIdealReason == NI_TOOFAR && which_ai.GetMySkills()->testFlag(AIS_ATTACKONLYINRANGE))
|
|
{
|
|
attack = 0;
|
|
}
|
|
|
|
//no attacking if we're not ready to harm the player
|
|
float awakeTimeValue = level.time - which_ai.getFirstTargetTime();
|
|
//if(awakeTimeValue < which_ai.GetMySkills()->getHesitation() && (which_ai.GetStartleability()))
|
|
if(awakeTimeValue < which_ai.GetMySkills()->getHesitation() * ((gmonster.GetClosestEnemy() == &monster) ? .5:1))//the closest guy gets to attack quicker
|
|
{ // we are groggy for a bit when we first arise
|
|
attack = 0;
|
|
}
|
|
|
|
which_ai.GetMiscFlags() &= ~MISC_FLAG_FIRINGBLIND;
|
|
|
|
if(nonIdealReason == NI_DODGING)
|
|
{
|
|
mmove_t *dodgeMove;
|
|
|
|
dodgeMove=GetSequenceForDodge(which_ai, monster, goalPos, goalPos, path_goalentity, 0, gi.irand(0,1)?-1:1);
|
|
if(dodgeMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, dodgeMove, goalPos, goalPos);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_DUCKING;
|
|
}
|
|
}
|
|
|
|
if(nonIdealReason == NI_DUCKING)
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
mmove_t *myMove;
|
|
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid);
|
|
|
|
if(myMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, myMove, goalPos, goalPos, path_goalentity);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_FLEEING;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_FLEEING;
|
|
}
|
|
}
|
|
|
|
int fastStrafe = 0;
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_mrs) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_l) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_p))
|
|
{
|
|
fastStrafe = 1;
|
|
}
|
|
|
|
int fastRetreat = 0;
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_mrs) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_p2) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_l))
|
|
{
|
|
fastRetreat = 1;
|
|
}
|
|
|
|
int proceedForward = 1;
|
|
|
|
if((25>move_dist_sq) ||
|
|
(monster.spawnflags & SPAWNFLAG_HOLD_POSITION) ||
|
|
((which_ai.GetPriority() != PRIORITY_HIGH)&&(path_goalentity == level.sight_client)))
|
|
{
|
|
Enemy_Printf(&which_ai, "Non ideal stuck");
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,goalPos,goalPos), goalPos, goalPos);
|
|
proceedForward = 0;
|
|
}
|
|
else if(nonIdealReason == NI_TOOCLOSE)
|
|
{
|
|
proceedForward = 0;
|
|
int backup = 0;
|
|
vec3_t toAvoid, toTarg, looker;
|
|
if(aiPoints.isActive())
|
|
{
|
|
aiPoints.getReversePath(monster.s.origin, goalPos, &which_ai.getPathData(), &which_ai, &monster, which_ai.getTarget());
|
|
|
|
if(which_ai.getPathData().blocked)
|
|
{ // can't go where I want to :(
|
|
backup = 0;
|
|
}
|
|
else
|
|
{
|
|
Enemy_Printf(&which_ai, "Via %d->%d = %d\n", which_ai.getPathData().curNode,
|
|
which_ai.getPathData().nextNode, which_ai.getPathData().finalNode);
|
|
|
|
VectorSubtract(which_ai.getPathData().goPoint, monster.s.origin, toAvoid);
|
|
toAvoid[2] = 0;
|
|
float toAvoidLen = VectorNormalize(toAvoid);
|
|
/* if(VectorLength(toAvoid) < .01)
|
|
{
|
|
VectorCopy(goalPos, actionGoal);
|
|
}
|
|
else*/
|
|
{
|
|
VectorMA(monster.s.origin, 10, toAvoid, actionGoal);
|
|
}
|
|
|
|
//if(toAvoidLen < .05)
|
|
if(toAvoidLen < 10)
|
|
{
|
|
backup = 0;
|
|
}
|
|
else
|
|
{
|
|
if(which_ai.GetMySkills()->testFlag(AIS_NOATTACKONRETREAT) ||
|
|
(which_ai.GetMove() && ClearShot(which_ai, monster, toAvoid, path_goalentity, which_ai.GetMove()->bbox, actionGoal)))
|
|
{ // if we can still shoot once we backup, we like this spot - otherwise no
|
|
backup = 1;
|
|
}
|
|
else
|
|
{
|
|
backup = 0;
|
|
}
|
|
}
|
|
VectorMA(monster.s.origin, 40, toAvoid, actionGoal);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
backup = 0;
|
|
|
|
}
|
|
|
|
if(backup)
|
|
{
|
|
|
|
VectorSubtract(actionGoal, monster.s.origin, toAvoid);
|
|
toAvoid[2] = 0;
|
|
VectorNormalize(toAvoid);
|
|
|
|
AdjustVectorByGuys(&monster, toAvoid);
|
|
|
|
which_ai.RequestMoveOutOfWay(toAvoid);
|
|
|
|
VectorSubtract(goalPos, monster.s.origin, toTarg);
|
|
toTarg[2] = 0;
|
|
float distToTarg = VectorNormalize(toTarg);
|
|
|
|
if(DotProduct(toAvoid, toTarg) < -.717)
|
|
{ //move in a direction opposite of where I want to face
|
|
if(!fastRetreat)
|
|
{
|
|
VectorScale(toAvoid, 4.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
}
|
|
|
|
VectorScale(toAvoid, -2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else if(DotProduct(toAvoid, toTarg) > .717)
|
|
{ //move in a direction towards where I face
|
|
VectorScale(toAvoid, 2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
if(!fastStrafe)
|
|
{
|
|
VectorScale(toAvoid, 12.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
}
|
|
|
|
//set it to the side now - for strafing
|
|
toAvoid[2] = toAvoid[1];
|
|
toAvoid[1] = -toAvoid[0];
|
|
toAvoid[0] = toAvoid[2];
|
|
toAvoid[2] = 0;
|
|
|
|
if(DotProduct(toAvoid, toTarg) > 0)
|
|
{
|
|
VectorScale(toAvoid, 2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
VectorScale(toAvoid, -2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
}
|
|
|
|
if(aipoints_show->value)
|
|
{
|
|
paletteRGBA_t col = {250, 250, 250, 250};
|
|
//FX_MakeLine(node[startNode].getPos(), node[nextNode].getPos(), col, 1);
|
|
FX_MakeLine(which_ai.getPathData().goPoint, monster.s.origin, col, 1);
|
|
FX_MakeRing(which_ai.getPathData().goPoint, 8);
|
|
paletteRGBA_t col2 = {0, 0, 0, 250};
|
|
FX_MakeLine(actionGoal, monster.s.origin, col2, 1);
|
|
}
|
|
|
|
vec3_t testShoot;
|
|
|
|
VectorSubtract(goalPos, monster.s.origin, testShoot);
|
|
VectorNormalize(testShoot);
|
|
VectorMA(monster.s.origin, 64, testShoot, testShoot);
|
|
|
|
if(attack && (!which_ai.GetMySkills()->testFlag(AIS_NOATTACKONRETREAT)) && ClearShot(which_ai, monster, testShoot, path_goalentity, which_ai.GetMove()->bbox))
|
|
{
|
|
//AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, actionGoal, goalPos, path_goalentity), actionGoal, goalPos, path_goalentity);
|
|
AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, actionGoal, looker, path_goalentity), actionGoal, looker, path_goalentity);
|
|
}
|
|
else
|
|
{ //just back up a bit - can't shoot from here
|
|
//oh dear. gsfMovement is fairly different from gsfAttack... I did not know =/
|
|
if(!fastRetreat)
|
|
{
|
|
VectorScale(toAvoid, 4.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
}
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(attack && ClearShot(which_ai, monster, goalPos, path_goalentity, which_ai.GetMove()->bbox))
|
|
{ //can't move, but at least I can shoot!
|
|
AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, monster.s.origin, goalPos, path_goalentity), monster.s.origin, goalPos, path_goalentity);
|
|
}
|
|
else
|
|
{ //Stand around and do nothing - too close and not clear. Sad.
|
|
if(which_ai.GetMySkills()->testFlag(AIS_WONTADVANCE))
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,goalPos,goalPos), goalPos, goalPos);
|
|
}
|
|
else
|
|
{
|
|
proceedForward = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(blockingEnemy)
|
|
{ //this is only the case if clearshot is obstructed by another guy
|
|
vec3_t dif;
|
|
VectorSubtract(goalPos, monster.s.origin, dif);
|
|
dif[2] = 0;
|
|
float goalDist = VectorNormalize(dif);
|
|
|
|
if(goalDist > 1)
|
|
{
|
|
dif[2] = dif[1];
|
|
dif[1] = dif[0]*-1;
|
|
dif[0] = dif[2];
|
|
dif[2] = 0;
|
|
|
|
VectorMA(monster.s.origin, 64, dif, dif);
|
|
|
|
trace_t tr;
|
|
|
|
gi.trace(monster.s.origin, monster.mins, monster.maxs, dif, &monster, MASK_MONSTERSOLID, &tr);
|
|
|
|
vec3_t lookSpot;
|
|
VectorCopy(goalPos, lookSpot);
|
|
|
|
if(nonIdealReason == NI_FLEEING)
|
|
{
|
|
VectorCopy(dif, lookSpot);
|
|
}
|
|
|
|
if(tr.fraction > .99 && !tr.allsolid && !tr.startsolid)
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,dif,lookSpot), dif, lookSpot);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,goalPos,goalPos), goalPos, goalPos);
|
|
}
|
|
proceedForward = 0;
|
|
|
|
if(aipoints_show->value)
|
|
{
|
|
paletteRGBA_t col = {0, 0, 250, 250};
|
|
//FX_MakeLine(node[startNode].getPos(), node[nextNode].getPos(), col, 1);
|
|
FX_MakeLine(dif, monster.s.origin, col, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(proceedForward)// if(nonIdealReason == NI_TOOFAR)//make this catchall - why not
|
|
{
|
|
if(aiPoints.isActive())
|
|
{
|
|
aiPoints.getPath(monster.s.origin, goalPos, &which_ai.getPathData(), &which_ai, &monster, 1, which_ai.getTarget());
|
|
|
|
/* vec3_t sep;
|
|
float len;
|
|
VectorSubtract(which_ai.getPathData().goPoint, monster.s.origin, sep);
|
|
len = VectorLength(sep);
|
|
|
|
if(nonIdealReason == NI_TOOFAR_SEARCHING && which_ai.getPathData().curNode == which_ai.getPathData().nextNode && len < 48)
|
|
{
|
|
VectorCopy(monster.s.origin, which_ai.getPathData().goPoint);
|
|
}*/
|
|
|
|
Enemy_Printf(&which_ai, "Via %d->%d = %d\n", which_ai.getPathData().curNode,
|
|
which_ai.getPathData().nextNode, which_ai.getPathData().finalNode);
|
|
|
|
if(which_ai.getPathData().blocked)
|
|
{ // can't go where I want to :(
|
|
if(attack && which_ai.GetMove() && ClearShot(which_ai, monster, goalPos, path_goalentity, which_ai.GetMove()->bbox))
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, monster.s.origin, goalPos, path_goalentity), monster.s.origin, goalPos, path_goalentity);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,goalPos,goalPos), goalPos, goalPos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vec3_t toAvoid, toTarg, looker;
|
|
|
|
VectorSubtract(which_ai.getPathData().goPoint, monster.s.origin, toAvoid);
|
|
toAvoid[2] = 0;
|
|
VectorNormalize(toAvoid);
|
|
|
|
VectorScale(toAvoid, 10.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
|
|
VectorSubtract(goalPos, monster.s.origin, toTarg);
|
|
toTarg[2] = 0;
|
|
float distToTarg = VectorNormalize(toTarg);
|
|
|
|
if((nonIdealReason == NI_FLEEING)||(!fastStrafe)||(distToTarg > 448))
|
|
{ // good for folks who can't strafe
|
|
VectorScale(toAvoid, 500, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
|
|
}
|
|
else
|
|
{
|
|
if(DotProduct(toAvoid, toTarg) < -.717)
|
|
{
|
|
if(!fastRetreat)
|
|
{
|
|
VectorScale(toAvoid, 4.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
}
|
|
|
|
VectorScale(toAvoid, -2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else if(DotProduct(toAvoid, toTarg) > .717)
|
|
{
|
|
VectorScale(toAvoid, 2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
if(!fastStrafe)
|
|
{
|
|
VectorScale(toAvoid, 12.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
}
|
|
|
|
//set it to the side now - for strafing
|
|
toAvoid[2] = toAvoid[1];
|
|
toAvoid[1] = -toAvoid[0];
|
|
toAvoid[0] = toAvoid[2];
|
|
toAvoid[2] = 0;
|
|
|
|
if(DotProduct(toAvoid, toTarg) > 0)
|
|
{
|
|
VectorScale(toAvoid, 2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
VectorScale(toAvoid, -2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
}
|
|
}
|
|
|
|
vec3_t dif;
|
|
VectorSubtract(monster.s.origin, actionGoal, dif);
|
|
dif[2] = 0;
|
|
vec3_t dif2;
|
|
VectorSubtract(monster.s.origin, goalPos, dif2);
|
|
dif2[2] = 0;
|
|
if((VectorLengthSquared(dif) > 2)&&((VectorLengthSquared(dif2) > (50*50))||((nonIdealReason == NI_FLEEING)))&&((!which_ai.GetMySkills()->testFlag(AIS_WONTADVANCE))||(nonIdealReason == NI_FLEEING)))
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
else
|
|
{
|
|
if(nonIdealReason == NI_FLEEING)
|
|
{ //get down an' cower
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
mmove_t *myMove;
|
|
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid);
|
|
|
|
if(!myMove)
|
|
{
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid2);
|
|
}
|
|
|
|
if(myMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, myMove, goalPos, goalPos, path_goalentity);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(attack && which_ai.GetMove() && ClearShot(which_ai, monster, goalPos, path_goalentity, which_ai.GetMove()->bbox))
|
|
{ //can't move, but at least I can shoot! - fixme - some guys shouldn't do this
|
|
AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, monster.s.origin, goalPos, path_goalentity), monster.s.origin, goalPos, path_goalentity);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,actionGoal,goalPos), actionGoal, goalPos);
|
|
}
|
|
}
|
|
//AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
|
|
if(aipoints_show->value)
|
|
{
|
|
VectorScale(toAvoid, 40.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
|
|
paletteRGBA_t col = {250, 250, 250, 250};
|
|
paletteRGBA_t col2 = {0, 0, 0, 250};
|
|
//FX_MakeLine(node[startNode].getPos(), node[nextNode].getPos(), col, 1);
|
|
FX_MakeLine(which_ai.getPathData().goPoint, monster.s.origin, col, 1);
|
|
FX_MakeRing(which_ai.getPathData().goPoint, 4);
|
|
FX_MakeLine(actionGoal, monster.s.origin, col2, 1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract(goalPos, monster.s.origin, actionGoal);
|
|
VectorNormalize(actionGoal);
|
|
VectorScale(actionGoal, 10.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,actionGoal), actionGoal, actionGoal);
|
|
}
|
|
}
|
|
}
|
|
|
|
void path_decision::AddAction(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
vec3_t gohere,moveVec;
|
|
qboolean isIdealPos;
|
|
body_c *the_body=which_ai.GetBody();
|
|
|
|
//if i'm in midair, shouldn't bother with trying to walk to next point
|
|
//this is just here for safety
|
|
if (!monster.groundentity && monster.movetype != MOVETYPE_FLY)
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster));
|
|
return;
|
|
}
|
|
|
|
//have a target position, so go there--may have a path here!!!
|
|
if (GetGoalPosition(which_ai,monster,gohere))
|
|
{
|
|
if (the_body && which_ai.HasTarget() && gmonster.GetWakeSoundTime()<level.time-10.0
|
|
&& the_body->GetMove() && the_body->GetMove()->suggested_action != ACTCODE_PAIN)
|
|
{
|
|
// the_body->VoiceWakeSound(monster, 0.8);
|
|
|
|
//eck! this is expensive!
|
|
// PlayerNoise(which_ai.getTarget(), monster.s.origin, AI_SENSETYPE_SOUND_WAKEUP, NULL, 500);
|
|
}
|
|
|
|
//if i'm after a player or enemy monster, get mean! (but make sure they're on a different team than mine)
|
|
if (path_goalentity && (path_goalentity->ai || path_goalentity->client) && IsAdversary(monster, path_goalentity))
|
|
{
|
|
if(which_ai.GetBody()&&which_ai.GetBody()->GetBestWeapon(monster)==ATK_NOTHING)
|
|
{
|
|
which_ai.Emote(monster, EMOTION_AFRAID, 5.0);//fixme--this decision is too general to do this here!!
|
|
}
|
|
else
|
|
{
|
|
which_ai.Emote(monster, EMOTION_MEAN, 5.0);//fixme--this decision is too general to do this here!!
|
|
}
|
|
which_ai.SetTargetTime(path_goal_updatedtime, path_goalentity, gohere);
|
|
}
|
|
GetMovementVector(which_ai, monster, gohere, moveVec);
|
|
|
|
// Com_Printf("Go %f %f %f\n", moveVec[0], moveVec[1], moveVec[2]);
|
|
|
|
isIdealPos=IsIdealPosition(which_ai, monster, monster.s.origin, gohere, moveVec);
|
|
|
|
//if i have a path, go to the next point in it--may want to change this to Inch toward next path point
|
|
if (path_nextpoint.valid)
|
|
{
|
|
//if my path point is for some special action, do it
|
|
if (!isIdealPos && AddSpecialPathAction(which_ai, monster))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (isIdealPos)
|
|
{
|
|
AddActionIdealPosition(which_ai, monster, gohere, moveVec);
|
|
}
|
|
else
|
|
{
|
|
AddActionNonIdealPosition(which_ai, monster, gohere, moveVec);
|
|
}
|
|
}
|
|
//no goal position
|
|
else
|
|
{
|
|
decision_c::Perform(which_ai, monster);
|
|
}
|
|
}
|
|
|
|
//careful--used to be able to call this with no goal in mind, now it takes whatever goalpos
|
|
//yer plug in seriously
|
|
qboolean path_decision::EvaluatePathEndpoint(ai_c &which_ai, edict_t &monster,
|
|
ai_pathpoint_t endpoint_candidate, vec3_t goalpos)
|
|
{
|
|
//make sure i have a path!
|
|
if (!endpoint_candidate.valid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//simple distance check, weighted heavier in z
|
|
if (IsIdealPosition(which_ai, monster, endpoint_candidate.pos, goalpos, vec3_origin))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool made_puffs;
|
|
|
|
void path_think(edict_t *who)
|
|
{
|
|
FX_MakeSparks(who->s.origin, vec3_up, 2);
|
|
who->nextthink=level.time+0.5;
|
|
}
|
|
|
|
void path_think_2(edict_t *who)
|
|
{
|
|
FX_MakeSparks(who->s.origin, vec3_up, 1);
|
|
who->nextthink=level.time+0.5;
|
|
}
|
|
|
|
|
|
qboolean path_decision::NextPointInPath(ai_c &which_ai, edict_t &monster, vec3_t position)
|
|
{//this sets path_nextpoint
|
|
|
|
//are we traveling a path with another point to spare? if so, grab that
|
|
//next point.
|
|
if (path_nextpoint.valid && level.time-path_nextpoint.time < 5)
|
|
{
|
|
qboolean closeEnough = false;
|
|
vec3_t dist;
|
|
|
|
//consider me--haven't timed out yet
|
|
if (level.time - path_nextpoint.time < 1.0)
|
|
{
|
|
VectorSubtract(position, path_nextpoint.pos, dist);
|
|
dist[2]=0;
|
|
closeEnough=VectorLengthSquared(dist)<2500;
|
|
|
|
//still haven't hit the waypoint, keep goin at it
|
|
if (!closeEnough)
|
|
{
|
|
// gi.dprintf("NextPointInPath: moving to point\n");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
}
|
|
// gi.dprintf("NextPointInPath: path dried up\n");
|
|
path_nextpoint.valid = false;
|
|
return false;
|
|
}
|
|
|
|
void path_decision::TempPointCreate(vec3_t position, edict_t &monster)
|
|
{
|
|
VectorCopy(position, path_nextpoint.pos);
|
|
|
|
// if (ai_pathtest->value)
|
|
// {
|
|
// gi.dprintf("% creating temp point at time %f\n",monster.classname,level.time);
|
|
// }
|
|
path_nextpoint.valid = 1;
|
|
path_nextpoint.time = level.time;
|
|
}
|
|
|
|
void path_decision::AvoidPointCreate(vec3_t normal, edict_t &monster)
|
|
{
|
|
vec3_t newdir,tempdir,right, curfacing;
|
|
vec3_t finalpos;
|
|
float yaw, whichway;
|
|
|
|
if (path_nextpoint.valid && (level.time - path_nextpoint.time < 1.0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// gi.dprintf("warning! creating temp point!\n");
|
|
//get the direction i was facing, so i know which side of the normal is more desirable
|
|
yaw = monster.s.angles[YAW] * M_PI * 2 / 360;//fixme: don't know this is direction i was moving
|
|
|
|
curfacing[0] = cos(yaw);
|
|
curfacing[1] = sin(yaw);
|
|
curfacing[2] = 0;
|
|
|
|
//find a vector parallel to plane normal
|
|
//Fixme?--this doesn't work in 3d, but cheap & shouldn't be a problem for walking creatures
|
|
//er, could zero out normal's z, then normalize (making sure length n/ 0)
|
|
right[0] = normal[1];
|
|
right[1] = normal[0]*-1.0;
|
|
right[2] = normal[2];
|
|
|
|
//this random stuff makes it more likely i'll try to go with the grain of whatever i hit
|
|
|
|
if ((path_nextpoint.valid)||(level.time-path_fail_time<PATH_FAILURE_REFAIL_DELAY))
|
|
{
|
|
// whichway=0;
|
|
|
|
whichway = gi.flrand(-1.0F, 1.0F);
|
|
whichway = whichway*whichway*whichway*whichway;
|
|
if (!(rand()&3)&&(level.time-path_fail_time<PATH_FAILURE_REFAIL_DELAY))
|
|
{
|
|
VectorCopy(path_fail_dir,curfacing);
|
|
}
|
|
}
|
|
else
|
|
|
|
{
|
|
whichway = gi.flrand(0.0F, 2.0F);
|
|
whichway = 1.0-(whichway/**whichway*0.5*/);
|
|
}
|
|
|
|
//random check that'll usually choose to go to the same side of the normal
|
|
//that i was already going, but not always (so i'll Eventually get out of any
|
|
//corner :P )
|
|
if (DotProduct (curfacing, right) <= whichway)//don't turn around now
|
|
{
|
|
right[0] *= -1.0;
|
|
right[1] *= -1.0;
|
|
}
|
|
|
|
//go 40 units parallel to normal, and 10 with the normal
|
|
VectorScale(right,VectorLength(monster.ai->velocity)+30,newdir);
|
|
VectorScale(normal,(VectorLength(monster.ai->velocity)+30)*0.5,tempdir);//back up a bit too!
|
|
VectorAdd(monster.s.origin,newdir,newdir);
|
|
VectorAdd(tempdir,newdir,finalpos);
|
|
|
|
//spawn an edict for tempgoal usage
|
|
TempPointCreate(finalpos,monster);
|
|
//make sure spawn was successful
|
|
path_nextpoint.temp_type=0;//code for ordinary tempgoal
|
|
}
|
|
|
|
void path_decision::PoliteAvoidPointCreate(vec3_t normal, edict_t &monster)
|
|
{
|
|
vec3_t tempdir;
|
|
vec3_t finalpos;
|
|
|
|
if (path_nextpoint.valid && (level.time - path_nextpoint.time < 1.0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//go 70 units with the normal
|
|
VectorScale(normal,70,tempdir);//back up a bit too!
|
|
VectorAdd(monster.s.origin,tempdir,finalpos);
|
|
|
|
//spawn an edict for tempgoal usage
|
|
TempPointCreate(finalpos,monster);
|
|
//make sure spawn was successful
|
|
path_nextpoint.temp_type=0;//code for ordinary tempgoal
|
|
}
|
|
|
|
void path_decision::JumpPointCreate(vec3_t position, edict_t &monster)
|
|
{
|
|
// gi.dprintf("warning! creating jump point!\n");
|
|
TempPointCreate(position,monster);
|
|
path_nextpoint.temp_type=1;//code for jumpgoal
|
|
}
|
|
|
|
void path_decision::FallPointCreate(vec3_t position, edict_t &monster)
|
|
{
|
|
// gi.dprintf("warning! creating fall point!\n");
|
|
TempPointCreate(position,monster);
|
|
path_nextpoint.temp_type=2;//code for fallgoal
|
|
}
|
|
|
|
|
|
void path_decision::UsePointCreate(brushUseInfo_t &useeInfo, edict_t &monster)
|
|
{
|
|
// gi.dprintf("warning! creating fall point!\n");
|
|
TempPointCreate(useeInfo.usePos,monster);
|
|
VectorCopy(useeInfo.useDir, path_nextpoint.direction);
|
|
path_nextpoint.targetEnt=useeInfo.useEnt;
|
|
path_nextpoint.temp_type=3;//code for usegoal
|
|
}
|
|
|
|
void path_decision::PerformAvoidance(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
vec3_t normal, my_facing;
|
|
|
|
vec3_t endorg;
|
|
vec3_t checkorg;
|
|
trace_t walktrace;
|
|
float yaw;
|
|
bool avoided = false;
|
|
|
|
VectorClear(aimWanderVec);
|
|
|
|
//i'm in the air--can't really do any avoidance
|
|
if (!monster.groundentity && monster.movetype != MOVETYPE_FLY)
|
|
{
|
|
return;
|
|
}
|
|
|
|
yaw = monster.s.angles[YAW] * M_PI * 2 / 360;//fixme: don't know this is direction i was moving
|
|
|
|
//use my facing direction
|
|
endorg[0] = cos(yaw)*25;
|
|
endorg[1] = sin(yaw)*25;
|
|
endorg[2] = 0;
|
|
|
|
// try the move
|
|
VectorCopy (monster.s.origin, checkorg);
|
|
VectorScale (endorg, 0.04, my_facing);
|
|
VectorAdd (checkorg, endorg, endorg);
|
|
|
|
//do another trace to get info on which way to go to avoid obstacle
|
|
gi.trace (checkorg, monster.mins, monster.maxs, endorg, &monster, MASK_MONSTERSOLID, &walktrace);
|
|
|
|
checkorg[2]+=which_ai.GetJumpHeight();
|
|
endorg[2]+=which_ai.GetJumpHeight();
|
|
|
|
|
|
//no jumping or falling here, bub!
|
|
|
|
//hmm... if trace was successful, something's screwy--use monster's angles reversed as the normal for a temp point
|
|
if (walktrace.fraction == 1)
|
|
{
|
|
AngleVectors(monster.s.angles,normal,NULL,NULL);
|
|
VectorScale(normal,-1.0,normal);
|
|
AvoidPointCreate(normal, monster);
|
|
}
|
|
|
|
//if trace hit architecture or entity unlike monster, do temppoint avoidance for sure
|
|
//fixme: check if ent with ai is good guy or bad guy
|
|
else if (walktrace.ent == path_goalentity)
|
|
{
|
|
// NextPointInPath(which_ai, monster, monster.s.origin);
|
|
}
|
|
else if ((!walktrace.ent) || (!walktrace.ent->ai))
|
|
{
|
|
//open doors in the way--fixme--sophisticate this, add special action.
|
|
if (walktrace.ent && IsUseableBrush(walktrace.ent))
|
|
{
|
|
if (IsBrushUseableNow(walktrace.ent, level.time, &monster))
|
|
{
|
|
brushUseInfo_t useInfo;
|
|
if (GetUseableBrushInfo(walktrace.plane.normal, walktrace.ent, useInfo, &monster))
|
|
{
|
|
UsePointCreate(useInfo, monster);
|
|
}
|
|
}
|
|
//it's useable--do a special avoid here, to not block up doorways etc.
|
|
else
|
|
{
|
|
PoliteAvoidPointCreate(walktrace.plane.normal, monster);
|
|
}
|
|
}
|
|
//not a useable brush--just avoid it
|
|
else
|
|
{
|
|
AvoidPointCreate(walktrace.plane.normal, monster);
|
|
}
|
|
}
|
|
//hit a monster--not sure if it's an ally or not
|
|
|
|
else
|
|
{
|
|
float whichway;
|
|
vec3_t other_facing;
|
|
|
|
whichway = gi.flrand(0.0F, 2.0F);
|
|
whichway = 1.0-whichway;
|
|
|
|
//calculate direction other guy is facing
|
|
AngleVectors(walktrace.ent->s.angles,other_facing,NULL,NULL);
|
|
|
|
//this would skew results more in favor of not turning
|
|
// whichway = 1.0-(whichway*whichway*0.5);
|
|
|
|
//sometimes yield to higher ranked monsters, never yield to lower ranked guys
|
|
//random check that'll usually ignore friendly collision if we're going the same direction
|
|
if ((walktrace.ent->ai->GetRank() - monster.ai->GetRank() >= gi.flrand(-20.0F, 0.0F))
|
|
&&(DotProduct (my_facing, other_facing) <= whichway))//facing each other
|
|
{
|
|
vec3_t edictnorm;
|
|
|
|
//instead of using trace norm, calc a more natural-looking norm using the position of the 2 edicts
|
|
VectorSubtract(monster.s.origin, walktrace.ent->s.origin, edictnorm);
|
|
edictnorm[0]+=gi.flrand(-15.0F, 15.0F);
|
|
edictnorm[1]+=gi.flrand(-15.0F, 15.0F);
|
|
if (!VectorCompare(edictnorm,vec3_origin))
|
|
{
|
|
VectorNormalize(edictnorm);
|
|
|
|
AvoidPointCreate(edictnorm, monster);
|
|
}
|
|
}
|
|
//going same way as chum--do nothing? need to stall for time, right?
|
|
else
|
|
{
|
|
// NextPointInPath(which_ai, monster, monster.s.origin);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void path_decision::ActionCompleted(action_c &which_action, ai_c &which_ai, edict_t &monster, float percent_success)
|
|
{//go to next node in path, or set up temp goal for avoidance
|
|
|
|
if (percent_success > 0.25)
|
|
{
|
|
NextPointInPath(which_ai, monster, monster.s.origin);
|
|
}
|
|
//action completed unsuccessfully--set up temp waypoint for avoidance
|
|
else
|
|
{
|
|
PerformAvoidance(which_ai, monster);
|
|
}
|
|
|
|
if (which_action.Type() == atype_attack)//update validity of attacking here.
|
|
{
|
|
return;
|
|
}
|
|
|
|
//based on success of this last action, update how valid this decision is.
|
|
UpdateValidity(percent_success);
|
|
}
|
|
|
|
path_decision::path_decision(path_decision *orig)
|
|
: decision_c(orig)
|
|
{
|
|
last_jump_time = orig->last_jump_time;
|
|
path_fail_time = orig->path_fail_time;
|
|
VectorCopy(orig->path_fail_dir, path_fail_dir);
|
|
|
|
path_nextpoint = orig->path_nextpoint;
|
|
*(int *)&path_nextpoint.targetEnt = GetEdictNum(orig->path_nextpoint.targetEnt);
|
|
|
|
*(int *)&path_goalentity = GetEdictNum(orig->path_goalentity);
|
|
VectorCopy(orig->path_goalpos, path_goalpos);
|
|
path_goal_updatedtime = orig->path_goal_updatedtime;
|
|
|
|
VectorCopy(orig->newMoveDir, newMoveDir);
|
|
lastCheckTime = orig->lastCheckTime;
|
|
lastDuckInvalidateTime = orig->lastDuckInvalidateTime;
|
|
lastDuckValidTime = orig->lastDuckValidTime;
|
|
|
|
VectorCopy(orig->aimWanderVec, aimWanderVec);
|
|
|
|
stuckNonIdeal = orig->stuckNonIdeal;
|
|
nonIdealReason = orig->nonIdealReason;
|
|
blockingEnemy = orig->blockingEnemy;
|
|
}
|
|
|
|
void path_decision::Evaluate(path_decision *orig)
|
|
{
|
|
last_jump_time = orig->last_jump_time;
|
|
path_fail_time = orig->path_fail_time;
|
|
VectorCopy(orig->path_fail_dir, path_fail_dir);
|
|
|
|
path_nextpoint = orig->path_nextpoint;
|
|
path_nextpoint.targetEnt = GetEdictPtr((int)orig->path_nextpoint.targetEnt);
|
|
|
|
path_goalentity = GetEdictPtr((int)orig->path_goalentity);
|
|
|
|
VectorCopy(orig->path_goalpos, path_goalpos);
|
|
path_goal_updatedtime = orig->path_goal_updatedtime;
|
|
|
|
VectorCopy(orig->newMoveDir, newMoveDir);
|
|
lastCheckTime = orig->lastCheckTime;
|
|
lastDuckInvalidateTime = orig->lastDuckInvalidateTime;
|
|
lastDuckValidTime = orig->lastDuckValidTime;
|
|
|
|
VectorCopy(orig->aimWanderVec, aimWanderVec);
|
|
|
|
stuckNonIdeal = orig->stuckNonIdeal;
|
|
nonIdealReason = orig->nonIdealReason;
|
|
blockingEnemy = orig->blockingEnemy;
|
|
|
|
decision_c::Evaluate(orig);
|
|
}
|
|
|
|
/**********************************************************************************
|
|
**********************************************************************************/
|
|
|
|
pathcorner_decision::pathcorner_decision(edict_t *goalent, int priority_root, float timeout, edict_t *ScriptOwner)
|
|
: path_decision(goalent, priority_root, timeout, ScriptOwner)
|
|
{
|
|
curWaitActionNum = 1;
|
|
reactivate_time = 0.0f;
|
|
oldTarget = NULL;
|
|
firstActionAtWait = true;
|
|
curMoveAction = NULL;
|
|
isAtCorner = false;
|
|
}
|
|
|
|
void pathcorner_decision::GetMovementVector(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
float distToGoalPos;
|
|
VectorSubtract(goalPos, monster.s.origin, newMoveDir);
|
|
distToGoalPos=VectorNormalize(newMoveDir);
|
|
if (distToGoalPos>3)
|
|
{
|
|
VectorScale(newMoveDir, 3, moveVec);
|
|
}
|
|
else
|
|
{
|
|
VectorScale(newMoveDir, distToGoalPos, moveVec);
|
|
}
|
|
}
|
|
|
|
mmove_t *GetWaitAction(edict_t *self, int actionNum);
|
|
mmove_t *GetMoveAction(edict_t *self);
|
|
|
|
void pathcorner_decision::FinishPathPoint(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
edict_t *oldCorner=path_goalentity;
|
|
curWaitActionNum=1;
|
|
// while(path.path->GetPoint(path_nextpoint.point)){}
|
|
path_nextpoint.valid = 0;
|
|
isAtCorner=false;
|
|
if (!path_goalentity)
|
|
{
|
|
return;
|
|
}
|
|
if (ai_pathtest->value)
|
|
{
|
|
gi.dprintf("Finishing pathpoint: time %f\n",level.time);
|
|
}
|
|
if (ai_pathtest->value)
|
|
{
|
|
gi.dprintf("%d getting next point: old--%s, looking for %s!\n",monster.s.number,path_goalentity->targetname,path_goalentity->target);
|
|
}
|
|
mmove_t *tMove=GetMoveAction(path_goalentity);
|
|
if (tMove)
|
|
{
|
|
curMoveAction=tMove;
|
|
}
|
|
//use the path_corner's pathtarget
|
|
if (path_goalentity->pathtarget)
|
|
{
|
|
char *savetarget;
|
|
sensedEntInfo_t my_client;
|
|
|
|
savetarget = path_goalentity->target;
|
|
path_goalentity->target = path_goalentity->pathtarget;
|
|
which_ai.GetSensedClientInfo(smask_all, my_client);
|
|
//use any client i know about as activator
|
|
if (my_client.ent)
|
|
{
|
|
G_UseTargets (path_goalentity, my_client.ent);
|
|
}
|
|
else
|
|
{
|
|
G_UseTargets (path_goalentity, &monster);
|
|
}
|
|
path_goalentity->target = savetarget;
|
|
}
|
|
|
|
if (path_goalentity->target)
|
|
{
|
|
path_goalentity=G_PickTarget(path_goalentity->target);
|
|
}
|
|
else
|
|
{
|
|
path_goalentity = NULL;
|
|
}
|
|
if (path_goalentity)
|
|
{
|
|
VectorSubtract(path_goalentity->s.origin, monster.s.origin, path_fail_dir);
|
|
VectorNormalize(path_fail_dir);
|
|
}
|
|
else
|
|
{
|
|
//now check to see if i should hold my position here.
|
|
if (oldCorner && (oldCorner->spawnflags&1) && stricmp(oldCorner->classname, "path_corner"))
|
|
{
|
|
monster.spawnflags|=SPAWNFLAG_HOLD_POSITION;
|
|
monster.flags |= FL_NO_KNOCKBACK;
|
|
}
|
|
}
|
|
}
|
|
|
|
void pathcorner_decision::ActionCompleted(action_c &which_action, ai_c &which_ai, edict_t &monster, float percent_success)
|
|
{//go to next node in path, or set up temp goal for avoidance
|
|
if (which_action.Type() == atype_attack)//update validity of attacking here.
|
|
{
|
|
return;
|
|
}
|
|
|
|
//based on success of this last action, update how valid this decision is.
|
|
UpdateValidity(percent_success);
|
|
|
|
//action completed successfully--go to next waypoint (no matter if i'm not following a path)
|
|
if (path_goalentity)
|
|
{
|
|
if (isAtCorner)
|
|
{
|
|
if (!path_nextpoint.valid)
|
|
{
|
|
//check if i have waitactions left, if point has spawnflag 2 on that means i'm finished
|
|
body_c *body=which_ai.GetBody();
|
|
mmove_t *newmove=GetWaitAction(path_goalentity, curWaitActionNum);
|
|
|
|
//passed the end of the wait action list
|
|
if (!newmove)
|
|
{
|
|
//run thru anims once only
|
|
if (path_goalentity->spawnflags&2)
|
|
{
|
|
reactivate_time = level.time-1;
|
|
}
|
|
}
|
|
|
|
if (reactivate_time <= level.time)
|
|
{
|
|
FinishPathPoint(which_ai, monster);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (percent_success > 0.25)
|
|
{
|
|
NextPointInPath(which_ai, monster, monster.s.origin);
|
|
}
|
|
//action completed unsuccessfully--set up temp waypoint for avoidance
|
|
else
|
|
{
|
|
PerformAvoidance(which_ai, monster);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
qboolean pathcorner_decision::IsIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t curPos, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
if(ai_dumb->value)
|
|
{//show folks what we are up to
|
|
fxRunner.exec("edit/ignore", &monster);
|
|
}
|
|
|
|
vec3_t toGoal;
|
|
float distToGoalSq;
|
|
VectorSubtract(goalPos, curPos, toGoal);
|
|
distToGoalSq=VectorLengthSquared(toGoal);
|
|
//am i closer than 30 from goal position
|
|
if( isAtCorner || (distToGoalSq < 2500 && distToGoalSq > 2) )
|
|
{
|
|
// trace_t tr;
|
|
vec3_t toCur;
|
|
//test to see if i can shoot goalpos--should i be able to get this info from sense somehow?
|
|
VectorSubtract(curPos, monster.s.origin, toCur);
|
|
// if(toCur[0]*toCur[0]+toCur[1]*toCur[1]+toCur[2]*toCur[2] < 100)
|
|
// {
|
|
// gi.trace(monster.s.origin,NULL,NULL,goalPos,&monster,MASK_SHOT, &tr);
|
|
// if (tr.fraction>0.95||tr.ent==path_goalentity)
|
|
// {
|
|
if (path_goalentity && oldTarget != path_goalentity)
|
|
{
|
|
if (ai_pathtest->value)
|
|
{
|
|
gi.dprintf("waiting: time %f\n",level.time);
|
|
}
|
|
oldTarget=path_goalentity;
|
|
if (path_goalentity->wait == -1 || (path_goalentity->spawnflags&2))
|
|
{
|
|
reactivate_time=level.time+9999999999;
|
|
}
|
|
else
|
|
{
|
|
reactivate_time=level.time+path_goalentity->wait;
|
|
}
|
|
firstActionAtWait=true;
|
|
isAtCorner=true;
|
|
|
|
if (path_goalentity->spawnflags&4)
|
|
{
|
|
which_ai.SetConcentratingOnPlayer(true);
|
|
monster.spawnflags|=SPAWNFLAG_HOLD_POSITION;
|
|
monster.flags |= FL_NO_KNOCKBACK;
|
|
curMoveAction=NULL;
|
|
}
|
|
}
|
|
return true;
|
|
// }
|
|
// }
|
|
// else
|
|
// {
|
|
// return true;
|
|
// }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void pathcorner_decision::AddActionNonIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
vec3_t actionGoal,tGoal;
|
|
|
|
VectorCopy(moveVec, actionGoal);
|
|
VectorAdd(monster.s.origin,actionGoal,actionGoal);
|
|
VectorAdd(moveVec,monster.s.origin,tGoal);
|
|
|
|
//if i have an animation in mind, only accept guys with the same actflags
|
|
if (curMoveAction)
|
|
{
|
|
mmove_t *newMove=GetSequenceForMovement(which_ai,monster,tGoal,tGoal,curMoveAction,~(curMoveAction->actionFlags));
|
|
if (newMove != curMoveAction)
|
|
{
|
|
if (which_ai.GetBody() && which_ai.GetBody()->IsAvailableSequence(monster, curMoveAction))
|
|
{
|
|
gi.dprintf("path-traversal: couldn't set %s for %s %s (but it's available!), using %s!\n",
|
|
curMoveAction->ghoulSeqName, monster.classname, monster.targetname, newMove->ghoulSeqName);
|
|
}
|
|
else
|
|
{
|
|
gi.dprintf("path-traversal: %s not available for %s %s, using %s!\n",
|
|
curMoveAction->ghoulSeqName, monster.classname, monster.targetname, newMove->ghoulSeqName);
|
|
}
|
|
}
|
|
AddActionForSequence(which_ai, monster, newMove, actionGoal, actionGoal);
|
|
}
|
|
//if i don't have some specific animation in mind, don't allow backing up or strafing
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster,
|
|
GetSequenceForMovement(which_ai,monster,tGoal,tGoal,curMoveAction,ACTFLAG_BACKUP|ACTFLAG_LEFTSTRAFE|ACTFLAG_RIGHTSTRAFE),
|
|
actionGoal, actionGoal);
|
|
}
|
|
}
|
|
|
|
void pathcorner_decision::AddActionIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
body_c *body=which_ai.GetBody();
|
|
mmove_t *newmove=NULL;
|
|
vec3_t forward;
|
|
sensedEntInfo_t aClient;
|
|
|
|
which_ai.GetSensedClientInfo(smask_all, aClient);
|
|
if (aClient.ent && !OnSameTeam(aClient.ent, &monster))
|
|
{
|
|
which_ai.SetTargetTime(aClient.time, aClient.ent, aClient.pos);
|
|
}
|
|
|
|
if (path_goalentity)
|
|
{
|
|
AngleVectors(path_goalentity->s.angles, forward, NULL, NULL);
|
|
VectorScale(forward, 10000, forward);
|
|
VectorAdd(forward, monster.s.origin, forward);
|
|
}
|
|
else
|
|
{
|
|
VectorClear(forward);
|
|
}
|
|
|
|
if (!body)
|
|
{
|
|
gi.dprintf("path-traversal: no body!\n");
|
|
which_ai.NewAction(which_ai.DefaultAction(this, NULL, GetSequenceForStand(which_ai,monster,goalPos,forward), goalPos, forward, NULL, 0, true), &monster);
|
|
return;
|
|
}
|
|
|
|
if (!firstActionAtWait && body->HasAnimationHitEOS())
|
|
{
|
|
curWaitActionNum++;
|
|
}
|
|
|
|
newmove=GetWaitAction(path_goalentity, curWaitActionNum);
|
|
|
|
//passed the end of the wait action list
|
|
if (!newmove)
|
|
{
|
|
//run thru anims once only
|
|
if (path_goalentity->spawnflags&2)
|
|
{
|
|
newmove=GetWaitAction(path_goalentity, 1);
|
|
}
|
|
else
|
|
{
|
|
curWaitActionNum=1;
|
|
newmove=GetWaitAction(path_goalentity, curWaitActionNum);
|
|
}
|
|
}
|
|
|
|
mmove_t *newStand=body->GetSequenceForStand(monster,goalPos,forward,ACTSUB_NORMAL,BBOX_PRESET_STAND,newmove);
|
|
|
|
forward[2]+=MONSTER_SHOOT_HEIGHT;
|
|
|
|
//if i want to be attacking here, try to aim at a client
|
|
if (newmove && newmove->suggested_action==ACTCODE_ATTACK)
|
|
{
|
|
sensedEntInfo_t sensed_client;
|
|
which_ai.GetSensedClientInfo(smask_all, sensed_client);
|
|
|
|
//only do it if i really saw a client, and s/he/it's really an enemy...
|
|
if (sensed_client.ent && IsAdversary(monster, sensed_client.ent))
|
|
{
|
|
VectorCopy(sensed_client.pos, forward);
|
|
}
|
|
}
|
|
|
|
if (newmove && newStand != newmove)
|
|
{
|
|
if (which_ai.GetBody() && which_ai.GetBody()->IsAvailableSequence(monster, newmove))
|
|
{
|
|
gi.dprintf("path-traversal: couldn't set %s for %s %s (but it's available!), using %s!\n",
|
|
newmove->ghoulSeqName, monster.classname, monster.targetname, newStand->ghoulSeqName);
|
|
}
|
|
else
|
|
{
|
|
gi.dprintf("path-traversal: %s not available for %s %s, using %s!\n",
|
|
newmove->ghoulSeqName, monster.classname, monster.targetname, newStand->ghoulSeqName);
|
|
}
|
|
}
|
|
|
|
which_ai.NewAction(which_ai.DefaultAction(this, NULL, newStand, goalPos, forward, NULL, 0, true), &monster);
|
|
|
|
firstActionAtWait=false;
|
|
}
|
|
|
|
void pathcorner_decision::SetGoalPosition(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
if (path_goalentity && path_goalentity->inuse)
|
|
{
|
|
if (IsIdealPosition(which_ai, monster, monster.s.origin, path_goalentity->s.origin, vec3_origin))
|
|
{
|
|
if (!path_goalentity->target&&reactivate_time <= level.time)
|
|
{
|
|
FinishPathPoint(which_ai, monster);
|
|
// path_goalentity=NULL;
|
|
}
|
|
else if (path_goalentity->wait <= 0.1 && path_goalentity->wait >= -0.1 && !(path_goalentity->spawnflags&2))
|
|
{
|
|
FinishPathPoint(which_ai, monster);
|
|
}
|
|
}
|
|
if (path_goalentity)
|
|
{
|
|
VectorCopy(path_goalentity->s.origin, path_goalpos);
|
|
}
|
|
}
|
|
}
|
|
|
|
qboolean pathcorner_decision::GetGoalPosition(ai_c &which_ai, edict_t &monster, vec3_t gohere)
|
|
{
|
|
//copy ai's enemy_lastseen_pos into gohere vec
|
|
if (gohere)//pass in NULL if just want to know whether we have goal position
|
|
{
|
|
VectorCopy(path_goalpos, gohere);
|
|
}
|
|
|
|
//check the decision's goalentity for whether i have goal pos, not ai's enemy
|
|
if (path_goalentity)
|
|
{
|
|
//make sure goal entity is still valid
|
|
if (!path_goalentity->inuse)
|
|
{
|
|
path_goalentity = NULL;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
qboolean pathcorner_decision::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
#ifdef _DEBUG
|
|
decName = "pathcornerdecision";
|
|
#endif
|
|
|
|
//don't automatically do this?
|
|
SetGoalPosition(which_ai, monster);
|
|
|
|
//have a goal position
|
|
if (GetGoalPosition(which_ai,monster,NULL))
|
|
{
|
|
priority = 1.1;
|
|
}
|
|
//no goal position--get rid of me
|
|
else
|
|
{
|
|
priority = 1;
|
|
return true;
|
|
}
|
|
|
|
UpdateValidity();
|
|
|
|
//if decision can time out, and its time has expired, then let ai get rid of it
|
|
return (IsTimedOut()||which_ai.HasTarget());
|
|
}
|
|
|
|
pathcorner_decision::pathcorner_decision(pathcorner_decision *orig)
|
|
: path_decision(orig)
|
|
{
|
|
reactivate_time = orig->reactivate_time;
|
|
*(int *)&oldTarget = GetEdictNum(orig->oldTarget);
|
|
curWaitActionNum = orig->curWaitActionNum;
|
|
firstActionAtWait = orig->firstActionAtWait;
|
|
isAtCorner = orig->isAtCorner;
|
|
*(int *)&curMoveAction = GetMmoveNum(orig->curMoveAction);
|
|
}
|
|
|
|
void pathcorner_decision::Evaluate(pathcorner_decision *orig)
|
|
{
|
|
reactivate_time = orig->reactivate_time;
|
|
oldTarget = GetEdictPtr((int)orig->oldTarget);
|
|
curWaitActionNum = orig->curWaitActionNum;
|
|
firstActionAtWait = orig->firstActionAtWait;
|
|
isAtCorner = orig->isAtCorner;
|
|
curMoveAction = GetMmovePtr((int)orig->curMoveAction);
|
|
|
|
path_decision::Evaluate(orig);
|
|
}
|
|
|
|
/**********************************************************************************
|
|
**********************************************************************************/
|
|
|
|
void pathcombat_decision::FinishPathPoint(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
edict_t *oldGoal=path_goalentity;
|
|
|
|
pathcorner_decision::FinishPathPoint(which_ai, monster);
|
|
|
|
//now check to see if i should hold my position here.
|
|
if (oldGoal && (oldGoal->spawnflags&1))
|
|
{
|
|
monster.spawnflags|=SPAWNFLAG_HOLD_POSITION;
|
|
monster.flags |= FL_NO_KNOCKBACK;
|
|
}
|
|
}
|
|
|
|
void pathcombat_decision::GetMovementVector(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
float distToGoalPos;
|
|
VectorSubtract(goalPos, monster.s.origin, newMoveDir);
|
|
distToGoalPos=VectorNormalize(newMoveDir);
|
|
if (distToGoalPos>VectorLength(which_ai.velocity)+15)
|
|
{
|
|
VectorScale(newMoveDir, VectorLength(which_ai.velocity)+15, moveVec);
|
|
}
|
|
else
|
|
{
|
|
VectorScale(newMoveDir, distToGoalPos, moveVec);
|
|
}
|
|
if (path_nextpoint.valid && level.time-path_nextpoint.time<1.0)
|
|
{
|
|
VectorSubtract(path_nextpoint.pos, monster.s.origin, moveVec);
|
|
RecalcMoveVec(which_ai, monster, path_nextpoint.pos, moveVec);
|
|
}
|
|
}
|
|
|
|
qboolean pathcombat_decision::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
#ifdef _DEBUG
|
|
decName = "pathcombatdecision";
|
|
#endif
|
|
|
|
//don't automatically do this?
|
|
SetGoalPosition(which_ai, monster);
|
|
|
|
// AcquirePath(which_ai, monster);
|
|
|
|
//have a goal position
|
|
if (GetGoalPosition(which_ai,monster,NULL))
|
|
{
|
|
if (which_ai.HasHadTarget())
|
|
{
|
|
priority = 5;
|
|
}
|
|
else
|
|
{
|
|
priority = 0;
|
|
}
|
|
}
|
|
//no goal position
|
|
else
|
|
{
|
|
priority = 0;
|
|
return true;
|
|
}
|
|
|
|
UpdateValidity();
|
|
|
|
//if decision can time out, and its time has expired, then let ai get rid of it
|
|
return IsTimedOut();
|
|
}
|
|
|
|
pathcombat_decision::pathcombat_decision(pathcombat_decision *orig)
|
|
: pathcorner_decision(orig)
|
|
{
|
|
}
|
|
|
|
void pathcombat_decision::Evaluate(pathcombat_decision *orig)
|
|
{
|
|
pathcorner_decision::Evaluate(orig);
|
|
}
|
|
|
|
void pathcombat_decision::Read()
|
|
{
|
|
char loaded[sizeof(pathcombat_decision)];
|
|
|
|
gi.ReadFromSavegame('AIPD', loaded, sizeof(pathcombat_decision));
|
|
Evaluate((pathcombat_decision *)loaded);
|
|
}
|
|
|
|
void pathcombat_decision::Write()
|
|
{
|
|
pathcombat_decision *savable;
|
|
|
|
savable = new pathcombat_decision(this);
|
|
gi.AppendToSavegame('AIPD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
/**********************************************************************************
|
|
**********************************************************************************/
|
|
/*
|
|
void pointcombat_decision::AddActionNonIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
vec3_t actionGoal,tGoal;
|
|
float moveDist;
|
|
float moveSpeed=VectorLength(which_ai.velocity)+5;
|
|
|
|
//override moveVec, achtung!! ignoring pathfinding stuff!!
|
|
// VectorSubtract(goalPos, monster.s.origin, moveVec);
|
|
|
|
// if (moveSpeed > 10)
|
|
// {
|
|
// moveSpeed = 10;
|
|
// }
|
|
// if (ai_pathtest->value)
|
|
// {
|
|
// gi.dprintf("nonideal adding action : goalpos %s, path_goalpos %s.\n",vtos(goalPos),vtos(path_goalpos));
|
|
// }
|
|
|
|
//override whatever moveVec was set as.
|
|
// VectorSubtract(goalPos, monster.s.origin, moveVec);
|
|
moveDist = VectorNormalize(moveVec);
|
|
//scale my moveVec to be reasonable.
|
|
// if (moveSpeed > moveDist)
|
|
// {
|
|
// moveSpeed = moveDist;
|
|
// }
|
|
VectorScale(moveVec, moveSpeed, actionGoal);
|
|
VectorAdd(monster.s.origin,actionGoal,actionGoal);
|
|
VectorAdd(moveVec,monster.s.origin,tGoal);
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,tGoal,tGoal), actionGoal, actionGoal);
|
|
// which_ai.NewAction(which_ai.WalkAction(this, NULL, GetSequenceForMovement(which_ai,monster,tGoal,tGoal), actionGoal, actionGoal, 0.2), &monster);
|
|
}
|
|
*/
|
|
pointcombat_decision::pointcombat_decision(edict_t *goalent, int priority_root, float timeout, edict_t *ScriptOwner)
|
|
: path_decision(goalent, priority_root, timeout, ScriptOwner)
|
|
{
|
|
current_point = 0;
|
|
VectorClear(current_point_pos);
|
|
VectorClear(current_point_dest);
|
|
current_point_type = 0;
|
|
current_point_subtype = 0;
|
|
last_point_trans = 0;
|
|
last_consider_time = 0;
|
|
VectorClear(current_point_dir);
|
|
}
|
|
|
|
qboolean pointcombat_decision::IsIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t curPos, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
vec3_t toGoal;
|
|
float distToGoalSq;
|
|
VectorSubtract(goalPos, curPos, toGoal);
|
|
distToGoalSq=VectorLengthSquared(toGoal);
|
|
|
|
//am i closer than 50 from combat point
|
|
//if(distToGoalSq < 2500 && distToGoalSq > 2)
|
|
if(distToGoalSq < 400 && distToGoalSq > 2)//closer than 20
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int pointBetweenSpheres(vec3_t start, float startrad, vec3_t end, float endrad, vec3_t testpoint);
|
|
|
|
void pointcombat_decision::GetMovementVector(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
// these are extremely specific - just go straight to it.
|
|
|
|
vec3_t targ;
|
|
|
|
stuckNonIdeal = false;
|
|
|
|
if(aiPoints.isActive())
|
|
{
|
|
float rad = aiPoints.getNode(which_ai.getPathData().curNode)->getRadius();
|
|
|
|
if(rad < 40)
|
|
{
|
|
rad = 40;
|
|
}
|
|
|
|
// it's only safe to go once I'm between these two points
|
|
// otherwise, head to my node first
|
|
if(pointBetweenSpheres(aiPoints.getNode(which_ai.getPathData().curNode)->getPos(), rad,
|
|
goalPos, 30, monster.s.origin))
|
|
{
|
|
VectorCopy(goalPos, targ);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(aiPoints.getNode(which_ai.getPathData().curNode)->getPos(), targ);
|
|
}
|
|
/*if(aipoints_show->value)
|
|
{
|
|
paletteRGBA_t col = {250, 0, 0, 250};
|
|
//FX_MakeLine(node[startNode].getPos(), node[nextNode].getPos(), col, 1);
|
|
FX_MakeLine(targ, monster.s.origin, col, 1);
|
|
FX_MakeRing(targ, 12);
|
|
}*/
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(goalPos, targ);
|
|
}
|
|
|
|
RecalcMoveVec(which_ai, monster, targ, moveVec);
|
|
lastCheckTime = level.time;
|
|
|
|
VectorScale(newMoveDir, VectorLength(which_ai.velocity)+10, moveVec);
|
|
}
|
|
|
|
|
|
void pointcombat_decision::AddActionIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
/*
|
|
//do specialized attacking for combat point here!
|
|
//if (current_point != NO_COMBAT_POINT)
|
|
if (which_ai.getPathData().currentPointStyle)
|
|
{
|
|
//if (current_point_type == EWI_POINT_DUCK)
|
|
if (which_ai.getPathData().currentPointStyle == EPS_DUCK)
|
|
{
|
|
// gi.dprintf("duck!\n");
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForAttack(monster, path_goalpos, path_goalpos, path_goalentity, ACTSUB_NORMAL, BBOX_PRESET_CROUCH), path_goalpos, path_goalpos, path_goalentity);
|
|
return;
|
|
}
|
|
//else if (current_point_type == EWI_POINT_LEAN)
|
|
else if (which_ai.getPathData().currentPointStyle == EPS_LEAN)
|
|
{ // nathan fixme - which side is buddy leaning on now?
|
|
//fixme: use current_point_pos here somehow
|
|
if (last_point_trans > level.time - gi.flrand(3.0, 6.0))
|
|
{
|
|
vec3_t dif;
|
|
VectorScale(current_point_dir, -1, dif);
|
|
vectoangles(dif, monster.s.angles);//!!!!!!!
|
|
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_wallidle_pk))
|
|
{
|
|
which_ai.NewAction(which_ai.DefaultAction(this, NULL, GetSequenceForStand(which_ai, monster,path_goalpos,path_goalpos, &generic_move_wallidle_pk), path_goalpos, path_goalpos, NULL, 0, true), &monster);
|
|
return;
|
|
}
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_wallidle_mrs))
|
|
{
|
|
which_ai.NewAction(which_ai.DefaultAction(this, NULL, GetSequenceForStand(which_ai, monster,path_goalpos,path_goalpos, &generic_move_wallidle_mrs), path_goalpos, path_goalpos, NULL, 0, true), &monster);
|
|
return;
|
|
}
|
|
|
|
which_ai.NewAction(which_ai.DefaultAction(this, NULL, GetSequenceForStand(which_ai, monster,path_goalpos,path_goalpos), path_goalpos, path_goalpos, NULL, 0, true), &monster);
|
|
}
|
|
else
|
|
{
|
|
vec3_t dif;
|
|
VectorScale(current_point_dir, -1, dif);
|
|
vectoangles(dif, monster.s.angles);//!!!!!!!
|
|
last_point_trans = level.time;
|
|
// VectorCopy(which_ai.getPathData().combatPoint, monster.s.origin);
|
|
if (!(which_ai.getPathData().combatPointLeft))
|
|
{
|
|
if (which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_fcornerr_m2))
|
|
{
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForAttack(monster, monster.s.origin, path_goalpos, path_goalentity, ACTSUB_NORMAL, BBOX_PRESET_STAND,&generic_move_fcornerr_m2), path_goalpos, path_goalpos, path_goalentity);
|
|
return;
|
|
}
|
|
if (which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_fcornerr_p))
|
|
{
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForAttack(monster, monster.s.origin, path_goalpos, path_goalentity, ACTSUB_NORMAL, BBOX_PRESET_STAND,&generic_move_fcornerr_p), path_goalpos, path_goalpos, path_goalentity);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_fcornerl_m2))
|
|
{
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForAttack(monster, monster.s.origin, path_goalpos, path_goalentity, ACTSUB_NORMAL, BBOX_PRESET_STAND,&generic_move_fcornerl_m2), path_goalpos, path_goalpos, path_goalentity);
|
|
return;
|
|
}
|
|
if (which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_fcornerl_p))
|
|
{
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForAttack(monster, monster.s.origin, path_goalpos, path_goalentity, ACTSUB_NORMAL, BBOX_PRESET_STAND,&generic_move_fcornerl_p), path_goalpos, path_goalpos, path_goalentity);
|
|
return;
|
|
}
|
|
}
|
|
|
|
gi.dprintf("couldn't run corner-peeking animation!\n");
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForAttack(monster, monster.s.origin, path_goalpos, path_goalentity, ACTSUB_NORMAL, BBOX_PRESET_STAND), path_goalpos, path_goalpos, path_goalentity);
|
|
}
|
|
return;
|
|
}
|
|
else if (which_ai.getPathData().currentPointStyle == EPS_COVER)
|
|
//else if (current_point_type == EWI_POINT_COVER)
|
|
{
|
|
if(body->GetMove()->bbox == BBOX_PRESET_CROUCH)
|
|
{ //what to do when ducking
|
|
if(last_point_trans > level.time - gi.flrand(4.0, 6.0))
|
|
{
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForStand(monster, monster.s.origin, path_goalpos, ACTSUB_NORMAL, BBOX_PRESET_CROUCH), path_goalpos, path_goalpos, path_goalentity);
|
|
}
|
|
else
|
|
{
|
|
last_point_trans = level.time;
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForAttack(monster, monster.s.origin, path_goalpos, path_goalentity, ACTSUB_NORMAL, BBOX_PRESET_STAND), path_goalpos, path_goalpos, path_goalentity);
|
|
}
|
|
}
|
|
else
|
|
{ //what to do when standing
|
|
if(last_point_trans > level.time - gi.flrand(4.0, 6.0))
|
|
{
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForAttack(monster, monster.s.origin, path_goalpos, path_goalentity, ACTSUB_NORMAL, BBOX_PRESET_STAND), path_goalpos, path_goalpos, path_goalentity);
|
|
}
|
|
else
|
|
{
|
|
last_point_trans = level.time;
|
|
AddActionForSequence(which_ai, monster, body->GetSequenceForStand(monster, monster.s.origin, path_goalpos, ACTSUB_NORMAL, BBOX_PRESET_CROUCH), path_goalpos, path_goalpos, path_goalentity);
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
//AddActionForSequence(which_ai, monster, body->GetSequenceForAttack(which_ai, monster, path_goalpos, path_goalpos, path_goalentity, ACTSUB_NORMAL, BBOX_PRESET_CROUCH), path_goalpos, path_goalpos, path_goalentity);
|
|
}
|
|
}
|
|
gi.dprintf("unknown combat point type!\n");
|
|
which_ai.NewAction(which_ai.DefaultAction(this, NULL, GetSequenceForStand(which_ai, monster,goalPos,goalPos), goalPos, goalPos, NULL, 0, true), &monster);*/
|
|
}
|
|
|
|
void pointcombat_decision::SetGoalPosition(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
/* path_decision::SetGoalPosition(which_ai, monster);
|
|
if (path_goalentity && path_goalentity->inuse)
|
|
{
|
|
which_ai.getPathData().combatPointsAllowed = EPS_DIRECTED;
|
|
|
|
//if i don't have lean anims, don't look for a lean point...
|
|
if (which_ai.GetBody() && !which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_fcornerl_m2)
|
|
&&!which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_fcornerl_p))
|
|
{
|
|
which_ai.getPathData().combatPointsAllowed &= ~EPS_LEAN;
|
|
}
|
|
|
|
aiPoints.getCombatPoint(&which_ai.getPathData(), monster.s.origin, path_goalentity->s.origin, current_point_dir);
|
|
}*/
|
|
}
|
|
|
|
qboolean pointcombat_decision::GetGoalPosition(ai_c &which_ai, edict_t &monster, vec3_t gohere)
|
|
{
|
|
//copy ai's enemy_lastseen_pos into gohere vec
|
|
if (gohere)//pass in NULL if just want to know whether we have goal position
|
|
{
|
|
//VectorCopy(current_point_dest, gohere);
|
|
// VectorCopy(which_ai.getPathData().combatPoint, gohere);
|
|
}
|
|
|
|
//check the decision's goalentity for whether i have goal pos, not ai's enemy
|
|
if (path_goalentity)
|
|
{
|
|
//make sure goal entity is still valid
|
|
if ((!path_goalentity->client && !path_goalentity->ai) || !path_goalentity->inuse
|
|
|| path_goalentity->health <= 0 || level.time-path_goal_updatedtime>POINTCOMBAT_DECISION_LOSETARGETTIME
|
|
|| (which_ai.GetBody()&&which_ai.GetBody()->GetBestWeapon(monster)==ATK_NOTHING))
|
|
{
|
|
path_goalentity = NULL;
|
|
return false;
|
|
}
|
|
//return current_point!=NO_COMBAT_POINT;
|
|
return false;
|
|
// return which_ai.getPathData().currentPointStyle;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
qboolean pointcombat_decision::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
#ifdef _DEBUG
|
|
decName = "pointcombatdecision";
|
|
#endif
|
|
|
|
// AcquirePath(which_ai, monster);
|
|
|
|
// put these back in if I ever get them working =/
|
|
priority = .1;
|
|
return IsTimedOut();
|
|
|
|
if ((which_ai.GetBody())&&(which_ai.GetBody()->IsMovementImpeded()))
|
|
{
|
|
priority = .1;
|
|
}
|
|
else if(monster.spawnflags & SPAWNFLAG_HOLD_POSITION)
|
|
{ // um, these guys can't use this...
|
|
priority = .1;
|
|
}
|
|
else
|
|
{
|
|
//don't automatically do this?
|
|
SetGoalPosition(which_ai, monster);
|
|
|
|
if (GetGoalPosition(which_ai,monster,NULL))
|
|
{
|
|
priority = 5.0;
|
|
}
|
|
//no goal position--no priority
|
|
else
|
|
{
|
|
priority = 0.1;
|
|
}
|
|
}
|
|
|
|
UpdateValidity();
|
|
|
|
//if decision can time out, and its time has expired, then let ai get rid of it
|
|
return IsTimedOut();
|
|
}
|
|
|
|
pointcombat_decision::pointcombat_decision(pointcombat_decision *orig)
|
|
: path_decision(orig)
|
|
{
|
|
current_point = orig->current_point;
|
|
VectorCopy(orig->current_point_pos, current_point_pos);
|
|
VectorCopy(orig->current_point_dest, current_point_dest);
|
|
current_point_type = orig->current_point_type;
|
|
current_point_subtype = orig->current_point_subtype;
|
|
last_point_trans = orig->last_point_trans;
|
|
last_consider_time = orig->last_consider_time;
|
|
VectorCopy(orig->current_point_dir, current_point_dir);
|
|
}
|
|
|
|
void pointcombat_decision::Evaluate(pointcombat_decision *orig)
|
|
{
|
|
current_point = orig->current_point;
|
|
VectorCopy(orig->current_point_pos, current_point_pos);
|
|
VectorCopy(orig->current_point_dest, current_point_dest);
|
|
current_point_type = orig->current_point_type;
|
|
current_point_subtype = orig->current_point_subtype;
|
|
last_point_trans = orig->last_point_trans;
|
|
last_consider_time = orig->last_consider_time;
|
|
|
|
path_decision::Evaluate(orig);
|
|
}
|
|
|
|
void pointcombat_decision::Read()
|
|
{
|
|
char loaded[sizeof(pointcombat_decision)];
|
|
|
|
gi.ReadFromSavegame('AIOD', loaded, sizeof(pointcombat_decision));
|
|
Evaluate((pointcombat_decision *)loaded);
|
|
}
|
|
|
|
void pointcombat_decision::Write()
|
|
{
|
|
pointcombat_decision *savable;
|
|
|
|
savable = new pointcombat_decision(this);
|
|
gi.AppendToSavegame('AIOD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
/**********************************************************************************
|
|
**********************************************************************************/
|
|
|
|
search_decision::search_decision(edict_t *goalent, int priority_root, float timeout)
|
|
: path_decision(goalent, priority_root, timeout)
|
|
{
|
|
approach_search = true;
|
|
pursuit_search = true;
|
|
VectorClear(pursuit_dir);
|
|
last_fired_time = 0;
|
|
VectorClear(firedSpot);
|
|
VectorClear(trail1);
|
|
VectorClear(trail2);
|
|
VectorClear(trail3);
|
|
}
|
|
void search_decision::ActionCompleted(action_c &which_action, ai_c &which_ai, edict_t &monster, float percent_success)
|
|
{
|
|
path_decision::ActionCompleted(which_action, which_ai, monster, percent_success);
|
|
|
|
//if i hit something and i'm not going to last seen pos, cancel going in same direction that target was last seen going
|
|
if (percent_success < 0.25 && !approach_search)
|
|
{
|
|
pursuit_search = false;
|
|
}
|
|
}
|
|
|
|
void search_decision::SetGoalPosition(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
path_decision::SetGoalPosition(which_ai, monster);
|
|
|
|
vec3_t dif;
|
|
|
|
VectorSubtract(path_goalpos, trail1, dif);
|
|
if(VectorLengthSquared(dif) > (8*8))
|
|
{
|
|
VectorCopy(trail2, trail3);
|
|
VectorCopy(trail1, trail2);
|
|
VectorCopy(path_goalpos, trail1);
|
|
}
|
|
|
|
//i see my target; make sure i'm set up for approaching
|
|
if (level.time-path_goal_updatedtime<0.2)
|
|
{
|
|
approach_search = true;
|
|
pursuit_search = true;
|
|
}
|
|
//cheating time is now! update which direction my target went since i last saw him for a while
|
|
else if (path_goalentity && level.time-path_goal_updatedtime<1.0)
|
|
{
|
|
// VectorSubtract(path_goalentity->s.origin, path_goalpos, pursuit_dir);
|
|
// VectorNormalize(pursuit_dir);
|
|
}
|
|
}
|
|
|
|
qboolean search_decision::GetGoalPosition(ai_c &which_ai, edict_t &monster, vec3_t gohere)
|
|
{
|
|
// vec3_t forward;
|
|
// trace_t tr;
|
|
|
|
//copy ai's enemy_lastseen_pos into gohere vec
|
|
if (gohere)//pass in NULL if just want to know whether we have goal position
|
|
{
|
|
approach_search = false;
|
|
if (path_goalentity)
|
|
{
|
|
which_ai.getTargetPos(gohere);
|
|
}
|
|
}
|
|
|
|
//check the decision's goalentity for whether i have goal pos, not ai's enemy
|
|
if (path_goalentity)
|
|
{
|
|
//make sure goal entity is still valid
|
|
if (!path_goalentity->inuse || path_goalentity->health <= 0 || level.time-path_goal_updatedtime>90)
|
|
{
|
|
path_goalentity = NULL;
|
|
validity=0;
|
|
return false;
|
|
}
|
|
validity=0.2;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
validity=0;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
qboolean search_decision::IsIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t curPos, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#define CHEAT_SHOOT_TIME 4.0
|
|
#define CHEATING_TIME 10.0
|
|
|
|
void search_decision::AddActionNonIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
if(which_ai.BeingWatched())
|
|
{
|
|
int asdf = 9;
|
|
}
|
|
|
|
blockingEnemy = 0;
|
|
|
|
which_ai.setLastNonTargetTime(level.time);
|
|
|
|
int shooting = 0;
|
|
|
|
if(level.time < last_fired_time + 8.0 || ((path_goal_updatedtime + CHEAT_SHOOT_TIME > level.time && path_goalentity && !which_ai.GetMySkills()->testFlag(AIS_NOSUPPRESS))))
|
|
{
|
|
shooting = 1;
|
|
vec3_t shot;
|
|
int forceShoot = 0;
|
|
|
|
if(level.time < last_fired_time + 8.0)
|
|
{
|
|
VectorCopy(firedSpot, shot);
|
|
}
|
|
else
|
|
{
|
|
switch(0)
|
|
{
|
|
case 0:
|
|
if(VectorLengthSquared(trail3))
|
|
{
|
|
VectorCopy(trail3, shot);
|
|
break;
|
|
}
|
|
//intentional fall through
|
|
case 1:
|
|
if(VectorLengthSquared(trail2))
|
|
{
|
|
VectorCopy(trail2, shot);
|
|
break;
|
|
}
|
|
//intentional fall through
|
|
case 2:
|
|
if(VectorLengthSquared(trail1))
|
|
{
|
|
VectorCopy(trail1, shot);
|
|
break;
|
|
}
|
|
//intentional fall through
|
|
default:
|
|
VectorCopy(path_goalpos, shot);//??
|
|
break;
|
|
}
|
|
|
|
//forceShoot = 1;
|
|
}
|
|
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
//mmove_t *myMove = body->GetSequenceForAttack(monster, dest, face, target, ACTSUB_NORMAL, BBOX_PRESET_STAND, preferred_move, reject_actionflags);
|
|
vec3_t fwd;
|
|
vec3_t toFace;
|
|
//which_ai.GetAimVector(fwd);
|
|
//vertical does not matter
|
|
AngleVectors(monster.s.angles, fwd, 0, 0);
|
|
fwd[2] = 0;
|
|
VectorNormalize(fwd);
|
|
VectorSubtract(shot, monster.s.origin, toFace);
|
|
toFace[2] = 0;
|
|
VectorNormalize(toFace);
|
|
mmove_t *myMove;
|
|
|
|
// fxRunner.exec("weapons/world/slugexplode", shot);
|
|
|
|
/*if((!which_ai.GetMySkills()->testFlag(AIS_NODUCK)) &&
|
|
(DotProduct(toFace, fwd) > .98) &&
|
|
(forceShoot || ClearShot(which_ai, monster, shot, 0, BBOX_PRESET_CROUCH)))
|
|
{
|
|
myMove = body->GetSequenceForAttack(monster, monster.s.origin, shot, 0, ACTSUB_NORMAL, BBOX_PRESET_CROUCH);
|
|
}
|
|
else */if(forceShoot || ClearShot(which_ai, monster, shot, 0, BBOX_PRESET_STAND))
|
|
{
|
|
myMove = body->GetSequenceForAttack(monster, monster.s.origin, shot, 0, ACTSUB_NORMAL, BBOX_PRESET_STAND);
|
|
}
|
|
else
|
|
{ //well, at the very least minimize surface area
|
|
shooting = 0;
|
|
//myMove = body->GetSequenceForStand(monster,shot,shot,ACTSUB_NORMAL,BBOX_PRESET_CROUCH);
|
|
}
|
|
|
|
if(shooting)
|
|
{
|
|
AddActionForSequence(which_ai, monster, myMove, monster.s.origin, shot, path_goalentity);
|
|
|
|
which_ai.GetMiscFlags() |= MISC_FLAG_FIRINGBLIND;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!shooting)
|
|
{
|
|
if(monster.spawnflags & SPAWNFLAG_HOLD_POSITION)
|
|
{//very special case - there guys are always in an ideal position, sort of
|
|
path_decision::AddActionIdealPosition(which_ai, monster, goalPos, moveVec);
|
|
return;
|
|
}
|
|
|
|
vec3_t checkSpot;
|
|
|
|
if((path_goal_updatedtime + CHEATING_TIME > level.time || which_ai.GetMySkills()->testFlag(AIS_CHEATERSEARCH)) && path_goalentity)
|
|
{
|
|
VectorCopy(path_goalentity->s.origin, checkSpot);
|
|
}
|
|
else if(level.time < last_fired_time + 20.0)
|
|
{
|
|
VectorCopy(firedSpot, checkSpot);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(goalPos, checkSpot);
|
|
}
|
|
|
|
vec3_t distToMe;
|
|
VectorSubtract(checkSpot, monster.s.origin, distToMe);
|
|
|
|
nonIdealReason = NI_TOOFAR_SEARCHING;;
|
|
|
|
// if((VectorLengthSquared(distToMe) < (128*128))||
|
|
// (path_goalentity && gi.inPVS(monster.s.origin, checkSpot)))
|
|
if(VectorLengthSquared(distToMe) < (64*64))
|
|
{ //if I am very close to my search point or I can see it but not my target, start searching
|
|
vec3_t lookSpot;
|
|
|
|
//we want our guy to look at each possible spot for 4 seconds, but I don't feel like adding a counter
|
|
//the addition of monster will add a bit of randomness if two guys are at the same node (icky, though)
|
|
if(aiPoints.getLookSpot(monster.s.origin, lookSpot, ((int)(level.time/4.0)+(int)(&monster))))
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
mmove_t *myMove;
|
|
|
|
myMove = body->GetSequenceForStand(monster,monster.s.origin,lookSpot,ACTSUB_NORMAL,BBOX_PRESET_STAND);
|
|
|
|
AddActionForSequence(which_ai, monster, myMove, monster.s.origin, lookSpot, path_goalentity);
|
|
|
|
which_ai.GetMiscFlags() |= MISC_FLAG_FIRINGBLIND;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*if(aiPoints.search(&which_ai.getPathData(), &which_ai))
|
|
{
|
|
VectorCopy(which_ai.getPathData().goPoint, checkSpot);
|
|
}
|
|
else*/
|
|
/* {
|
|
if(VectorLengthSquared(distToMe) < (64*64))
|
|
{//just go where I was going anyway and hang out - this will make me look at my targ though
|
|
VectorSubtract(path_goalentity->s.origin, monster.s.origin, checkSpot);
|
|
VectorNormalize(checkSpot);
|
|
VectorMA(monster.s.origin, 3, checkSpot, checkSpot);
|
|
}
|
|
else
|
|
{//do what I wasss doing anyway
|
|
}
|
|
}*/
|
|
}
|
|
|
|
path_decision::AddActionNonIdealPosition(which_ai, monster, checkSpot, moveVec);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
qboolean search_decision::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
path_decision::Consider(which_ai, monster);
|
|
|
|
#ifdef _DEBUG
|
|
decName = "searchdecision";
|
|
#endif
|
|
return IsTimedOut();
|
|
}
|
|
|
|
search_decision::search_decision(search_decision *orig)
|
|
: path_decision(orig)
|
|
{
|
|
pursuit_search = orig->pursuit_search;
|
|
approach_search = orig->approach_search;
|
|
VectorCopy(orig->pursuit_dir, pursuit_dir);
|
|
last_fired_time = orig->last_fired_time;
|
|
VectorCopy(orig->firedSpot, firedSpot);
|
|
VectorCopy(orig->trail1, trail1);
|
|
VectorCopy(orig->trail2, trail2);
|
|
VectorCopy(orig->trail3, trail3);
|
|
}
|
|
|
|
void search_decision::Evaluate(search_decision *orig)
|
|
{
|
|
pursuit_search = orig->pursuit_search;
|
|
approach_search = orig->approach_search;
|
|
VectorCopy(orig->pursuit_dir, pursuit_dir);
|
|
last_fired_time = orig->last_fired_time;
|
|
VectorCopy(orig->firedSpot, firedSpot);
|
|
VectorCopy(orig->trail1, trail1);
|
|
VectorCopy(orig->trail2, trail2);
|
|
VectorCopy(orig->trail3, trail3);
|
|
|
|
|
|
path_decision::Evaluate(orig);
|
|
}
|
|
|
|
void search_decision::Read()
|
|
{
|
|
char loaded[sizeof(search_decision)];
|
|
|
|
gi.ReadFromSavegame('AISD', loaded, sizeof(search_decision));
|
|
Evaluate((search_decision *)loaded);
|
|
}
|
|
|
|
void search_decision::Write()
|
|
{
|
|
search_decision *savable;
|
|
|
|
savable = new search_decision(this);
|
|
gi.AppendToSavegame('AISD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
void search_decision::SetInfoForDodge(vec3_t start, vec3_t end)
|
|
{
|
|
last_fired_time = level.time;
|
|
VectorCopy(start, firedSpot);
|
|
}
|
|
|
|
/**********************************************************************************
|
|
**********************************************************************************/
|
|
retreat_decision::retreat_decision(int priority_root,float timeout)
|
|
:path_decision(NULL,priority_root,timeout)
|
|
{
|
|
//start with validity at 0--as i lose health, validity rises; priority weighted very heavily by validity
|
|
validity = 0;
|
|
prev_health = 0;
|
|
haveFleeDest = false;
|
|
fearIndex = 0;
|
|
spookStyle = SPOOK_NOT;
|
|
spookTime = 0;
|
|
VectorClear(spookCenter);
|
|
}
|
|
|
|
qboolean retreat_decision::IsAdversary(edict_t &monster, edict_t *otherGuy)
|
|
{
|
|
if (monster.ai && monster.ai->GetAbusedByTeam())//when you've been abused, everyone's an enemy. <sniff>.
|
|
{
|
|
return true;
|
|
}
|
|
return (!OnSameTeam(&monster, otherGuy));
|
|
}
|
|
|
|
#define SUCCESSFUL_RUN_LEN 1000*1000
|
|
qboolean retreat_decision::IsIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t curPos, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
vec3_t distFromAttacker;
|
|
|
|
blockingEnemy = 0;
|
|
|
|
which_ai.Emote(monster, EMOTION_AFRAID, 5.0);//fixme--this decision is too general to do this here!!
|
|
|
|
//hey, the important thing is not disappearing in front of the player, don't really care about path_goalentity; right? --sfs
|
|
if(level.sight_client)
|
|
{
|
|
VectorSubtract(level.sight_client->s.origin, monster.s.origin, distFromAttacker);
|
|
|
|
if(VectorLengthSquared(distFromAttacker) > SUCCESSFUL_RUN_LEN)
|
|
{
|
|
haveFleeDest = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(fearIndex > .7)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if((spookStyle == SPOOK_EXPLOSIVE || spookStyle == SPOOK_EXPLOSIVE2))
|
|
{
|
|
nonIdealReason = NI_TOOCLOSE;//just wander off
|
|
}
|
|
/* if((which_ai.GetMySkills()->getExplosive() == ESEX_DUCK) && (spookStyle == SPOOK_EXPLOSIVE || spookStyle == SPOOK_EXPLOSIVE2))
|
|
{
|
|
nonIdealReason = NI_DUCKING;
|
|
}
|
|
else if((which_ai.GetMySkills()->getExplosive() == ESEX_DODGE) && spookStyle == SPOOK_EXPLOSIVE)
|
|
{
|
|
nonIdealReason = NI_DODGING;
|
|
}*/
|
|
else
|
|
{
|
|
nonIdealReason = NI_FLEEING;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void retreat_decision::AddActionIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
if(fearIndex > .7)
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
mmove_t *myMove;
|
|
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid);
|
|
|
|
if(!myMove)
|
|
{
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid2);
|
|
}
|
|
|
|
if(myMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, myMove, goalPos, goalPos, path_goalentity);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//if i'm doin ok, stand & shoot
|
|
// gi.dprintf("validity: %f, goal %s, current %s\n",validity,vtos(goalPos),vtos(monster.s.origin));
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,monster.s.origin,goalPos), goalPos, goalPos, path_goalentity);
|
|
//consider removing myself here?
|
|
if(haveFleeDest)
|
|
{
|
|
which_ai.SetAttentionLevel(ATTENTION_ESCAPED);//this is my cue that I have gotten the hell out of dodge, as it were
|
|
}
|
|
}
|
|
}
|
|
|
|
#define DEATH_SPOOK_TIME 4.0
|
|
#define EXPLODE_TIME 2.0
|
|
|
|
qboolean retreat_decision::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
// vec3_t gohere;
|
|
|
|
#ifdef _DEBUG
|
|
decName = "retreatdecision";
|
|
#endif
|
|
|
|
if(which_ai.GetPriority() != PRIORITY_HIGH)
|
|
{
|
|
fearIndex = 0;
|
|
return false;//if we're not so important anymore, disregard our previous fleeing
|
|
}
|
|
|
|
//update where i'm going--goal position is opposite of where i'll be going
|
|
SetGoalPosition(which_ai, monster);
|
|
|
|
if (!prev_health||level.time-path_goal_updatedtime>90)
|
|
{
|
|
prev_health = monster.health;
|
|
validity = 0;
|
|
priority = 1;
|
|
fearIndex = 0;
|
|
return false;
|
|
}
|
|
|
|
//if i'm marked to hold position, don't bother with any of this
|
|
if (monster.spawnflags & SPAWNFLAG_HOLD_POSITION)
|
|
{
|
|
validity = 0.0;
|
|
}
|
|
|
|
sensedEntInfo_t sensed_client, sensed_monster;
|
|
which_ai.GetSensedClientInfo(smask_all, sensed_client);
|
|
which_ai.GetSensedMonsterInfo(smask_all, sensed_monster);
|
|
|
|
//fixme - readd later
|
|
/* if(spookStyle == SPOOK_DEAD_FRIEND && level.time - spookTime < DEATH_SPOOK_TIME && gi.irand(0,1) && (!(monster.spawnflags & SPAWNFLAG_HOLD_POSITION)))
|
|
{//my friend died, so I react
|
|
validity=1.0f;
|
|
priority = 4.9;//?
|
|
}
|
|
else*/ if((spookStyle == SPOOK_EXPLOSIVE || spookStyle == SPOOK_EXPLOSIVE2) && level.time - spookTime < EXPLODE_TIME && (!(monster.spawnflags & SPAWNFLAG_HOLD_POSITION)))
|
|
{//I am afraid of explosives
|
|
validity=1.0f;
|
|
priority = 4.9;
|
|
}
|
|
else
|
|
{
|
|
validity = 1.0f;
|
|
priority = 4.9;
|
|
if ((which_ai.HasHadTarget())&&
|
|
(which_ai.GetBody() && which_ai.GetBody()->GetRightHandWeapon(monster)==ATK_NOTHING))//do I have someone to run away from?
|
|
{
|
|
validity=1.0f;
|
|
priority = 4.9;//?
|
|
}
|
|
else
|
|
{
|
|
priority = 1.0;
|
|
}
|
|
}
|
|
|
|
fearIndex -= .003;
|
|
if(fearIndex < 0)
|
|
{
|
|
fearIndex = 0;
|
|
}
|
|
|
|
//lowest possible priority should be lower than path_decision's lowest --sfs
|
|
//priority = 1.9+(floor(8*validity+0.5) * (which_ai.GetMySkills()->getCowardice()+0.5));
|
|
|
|
//if decision can time out, and its time has expired, then let ai get rid of it
|
|
return IsTimedOut();
|
|
}
|
|
|
|
void retreat_decision::AddFear(float amount, vec3_t center)
|
|
{
|
|
if(amount >= spookStyle)
|
|
{
|
|
spookTime = level.time;
|
|
spookStyle = amount;
|
|
VectorCopy(center, spookCenter);
|
|
}
|
|
}
|
|
|
|
void retreat_decision::SetInfoForDodge(vec3_t start, vec3_t end)
|
|
{ // getting shot at is frightening...
|
|
if(fearIndex < 1.0)
|
|
{
|
|
fearIndex += .4;
|
|
if(fearIndex >= 1.0)
|
|
{
|
|
fearIndex = 1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
qboolean retreat_decision::GetGoalPosition(ai_c &which_ai, edict_t &monster, vec3_t gohere)
|
|
{
|
|
//copy ai's enemy_lastseen_pos into gohere vec
|
|
int spooked = 0;
|
|
vec3_t avoid;
|
|
|
|
/* if(level.time - spookTime < EXPLODE_TIME && (spookStyle == SPOOK_EXPLOSIVE || spookStyle == SPOOK_EXPLOSIVE2))
|
|
{
|
|
spooked = 1;
|
|
VectorCopy(spookCenter, avoid);
|
|
}
|
|
else
|
|
{*/
|
|
VectorCopy(path_goalpos, avoid);
|
|
// }
|
|
|
|
if(level.time - spookTime < EXPLODE_TIME && (spookStyle == SPOOK_EXPLOSIVE || spookStyle == SPOOK_EXPLOSIVE2))
|
|
{
|
|
VectorCopy(avoid, gohere);
|
|
}
|
|
else
|
|
{
|
|
if (gohere)//pass in NULL if just want to know whether we have goal position
|
|
{
|
|
haveFleeDest = true;
|
|
if(OnSameTeam(level.sight_client, &monster))
|
|
{
|
|
if (spooked || !aiPoints.getNearestFleePoint(&which_ai.getPathData(), gohere, which_ai.GetTeam(monster)))
|
|
{
|
|
aiPoints.getReversePath(monster.s.origin, avoid, &which_ai.getPathData(), &which_ai, &monster, which_ai.getTarget());
|
|
VectorCopy(which_ai.getPathData().goPoint, gohere);
|
|
|
|
if(which_ai.getPathData().blocked)
|
|
{ // can't go where I want to :(
|
|
VectorCopy(monster.s.origin, gohere);
|
|
haveFleeDest = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // monsters just run where they can
|
|
aiPoints.getReversePath(monster.s.origin, avoid, &which_ai.getPathData(), &which_ai, &monster, which_ai.getTarget());
|
|
VectorCopy(which_ai.getPathData().goPoint, gohere);
|
|
|
|
if(which_ai.getPathData().blocked)
|
|
{ // can't go where I want to :(
|
|
VectorCopy(monster.s.origin, gohere);
|
|
haveFleeDest = false;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//check the decision's goalentity for whether i have goal pos, not ai's enemy
|
|
if (path_goalentity)
|
|
{
|
|
//make sure goal entity is still valid
|
|
if (!path_goalentity->inuse || path_goalentity->health <= 0 || level.time-path_goal_updatedtime>90)
|
|
{
|
|
path_goalentity = NULL;
|
|
// return false;
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// return false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
retreat_decision::retreat_decision(retreat_decision *orig)
|
|
: path_decision(orig)
|
|
{
|
|
prev_health = orig->prev_health;
|
|
haveFleeDest = orig->haveFleeDest;
|
|
fearIndex = orig->fearIndex;
|
|
spookStyle = orig->spookStyle;
|
|
spookTime = orig->spookTime;
|
|
VectorCopy(orig->spookCenter, spookCenter);
|
|
}
|
|
|
|
void retreat_decision::Evaluate(retreat_decision *orig)
|
|
{
|
|
prev_health = orig->prev_health;
|
|
haveFleeDest = orig->haveFleeDest;
|
|
fearIndex = orig->fearIndex;
|
|
spookStyle = orig->spookStyle;
|
|
spookTime = orig->spookTime;
|
|
VectorCopy(orig->spookCenter, spookCenter);
|
|
|
|
path_decision::Evaluate(orig);
|
|
}
|
|
|
|
void retreat_decision::Read()
|
|
{
|
|
char loaded[sizeof(retreat_decision)];
|
|
|
|
gi.ReadFromSavegame('AIRD', loaded, sizeof(retreat_decision));
|
|
Evaluate((retreat_decision *)loaded);
|
|
}
|
|
|
|
void retreat_decision::Write()
|
|
{
|
|
retreat_decision *savable;
|
|
|
|
savable = new retreat_decision(this);
|
|
gi.AppendToSavegame('AIRD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
/**********************************************************************************
|
|
**********************************************************************************/
|
|
|
|
dodge_decision::dodge_decision(int priority_root, float timeout)
|
|
: path_decision(NULL, priority_root, timeout)
|
|
{
|
|
//start with validity at 0--as i lose health, validity rises; priority weighted very heavily by validity
|
|
validity = 0;
|
|
prev_health = 0;
|
|
last_dodge_time = level.time;
|
|
last_shotat_time = level.time;
|
|
dodgeSide = 0;
|
|
|
|
VectorClear(shotStart);
|
|
VectorClear(shotEnd);
|
|
}
|
|
|
|
#define ROLL_DIST 64
|
|
|
|
qboolean dodge_decision::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
// vec3_t gohere;
|
|
|
|
body_c *the_body=which_ai.GetBody();
|
|
|
|
#ifdef _DEBUG
|
|
decName = "dodgedecision";
|
|
#endif
|
|
|
|
if (which_ai.GetBody())
|
|
{
|
|
if (which_ai.GetBody()->IsMovementImpeded())
|
|
{
|
|
priority = .1;
|
|
return IsTimedOut();
|
|
}
|
|
}
|
|
|
|
if(monster.spawnflags & SPAWNFLAG_HOLD_POSITION)
|
|
{
|
|
priority = .1;
|
|
return IsTimedOut();
|
|
}
|
|
|
|
if(!which_ai.getTarget())
|
|
{
|
|
priority = .1;
|
|
return IsTimedOut();
|
|
}
|
|
|
|
SetGoalPosition(which_ai, monster);
|
|
|
|
if(level.time < last_dodge_time + 3.0)
|
|
{
|
|
priority = .1;
|
|
}
|
|
else if(last_shotat_time < level.time - .3)
|
|
{
|
|
priority = .1;
|
|
}
|
|
else if(path_goal_updatedtime + .2 < level.time)
|
|
{
|
|
priority = .1;
|
|
}
|
|
else if(gi.flrand(0, 1) > which_ai.GetMySkills()->getDodge() * game.playerSkills.getDodge())
|
|
{ // this allows us to specify how much a particular guy will roll
|
|
if (the_body && the_body->GetMove() && the_body->GetMove()->suggested_action != ACTCODE_PAIN)
|
|
{
|
|
// the_body->VoiceSound("duck", monster, 0);
|
|
}
|
|
priority = .1;
|
|
last_shotat_time = 0;//so we don't get delayed dodging
|
|
}
|
|
else
|
|
{
|
|
vec3_t angs;
|
|
vec3_t fwd, right, up;
|
|
vec3_t shotDir;
|
|
|
|
if (the_body && the_body->GetMove() && the_body->GetMove()->suggested_action != ACTCODE_PAIN)
|
|
{
|
|
// the_body->VoiceSound("duck", monster, 0);
|
|
}
|
|
|
|
which_ai.GetAimAngles(angs);
|
|
AngleVectors(angs, fwd, right, up);
|
|
|
|
VectorSubtract(monster.s.origin, shotStart, shotDir);
|
|
VectorNormalize(shotDir);
|
|
|
|
if(DotProduct(shotDir, fwd) > -.707)
|
|
{ // cosine of 45 degrees
|
|
priority = .1;
|
|
}
|
|
else
|
|
{
|
|
vec3_t sideDir;
|
|
|
|
VectorSubtract(shotEnd, shotStart, sideDir);
|
|
float temp = sideDir[1];
|
|
sideDir[1] = sideDir[0];
|
|
sideDir[0] = -temp;
|
|
|
|
vec3_t myDir;
|
|
VectorSubtract(monster.s.origin, shotStart, myDir);
|
|
|
|
if(DotProduct(sideDir, myDir) > 0)
|
|
{
|
|
dodgeSide = -1;
|
|
}
|
|
else
|
|
{
|
|
dodgeSide = 1;
|
|
}
|
|
|
|
vec3_t rollEnd;
|
|
|
|
VectorMA(monster.s.origin, dodgeSide * -ROLL_DIST, right, rollEnd);
|
|
|
|
trace_t tr;
|
|
vec3_t min = {-16, -16, -16};
|
|
vec3_t max = {16, 16, 16};
|
|
|
|
gi.trace(monster.s.origin, min, max, rollEnd, &monster, MASK_SOLID, &tr);
|
|
|
|
if(tr.fraction < 1.0)
|
|
{
|
|
dodgeSide = 0;
|
|
|
|
VectorMA(monster.s.origin, ROLL_DIST, fwd, rollEnd);
|
|
gi.trace(monster.s.origin, min, max, rollEnd, &monster, MASK_SOLID, &tr);
|
|
|
|
if(tr.fraction < 1.0)
|
|
{
|
|
dodgeSide = 666;//mark this for below
|
|
}
|
|
}
|
|
|
|
if(dodgeSide != 666)//hehe
|
|
{
|
|
priority = 4;
|
|
}
|
|
else
|
|
{
|
|
priority = .1;
|
|
last_shotat_time = 0;//so we don't get delayed dodging
|
|
}
|
|
}
|
|
}
|
|
|
|
//if decision can time out, and its time has expired, then let ai get rid of it
|
|
return IsTimedOut();
|
|
}
|
|
|
|
|
|
void dodge_decision::AddAction(ai_c &which_ai, edict_t &monster)
|
|
{//go exactly opposite of where my normal pursue would take me
|
|
last_dodge_time = level.time;
|
|
|
|
//ick? will this screw guys up? dunno my path_goalpos here...
|
|
|
|
if (path_goalentity && (path_goalentity->ai || path_goalentity->client) && IsAdversary(monster, path_goalentity))
|
|
{
|
|
which_ai.SetTargetTime(path_goal_updatedtime, path_goalentity, path_goalpos);
|
|
}
|
|
|
|
if (path_nextpoint.valid)
|
|
{
|
|
if(AddSpecialPathAction(which_ai, monster))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//fixme: this has been changed to allow jumps, but goalpos should be adjusted in that case as well
|
|
vec3_t dodgeGoal;
|
|
mmove_t *dodgeMove;
|
|
|
|
if(path_goalentity)
|
|
{
|
|
VectorCopy(path_goalentity->s.origin, dodgeGoal);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(monster.s.origin, dodgeGoal);
|
|
}
|
|
dodgeMove=GetSequenceForDodge(which_ai, monster, dodgeGoal, dodgeGoal, path_goalentity, 0, dodgeSide);
|
|
if (dodgeMove && dodgeMove->actionFlags&ACTFLAG_BACKUP)
|
|
{
|
|
which_ai.GetAimVector(dodgeGoal);
|
|
VectorInverse(dodgeGoal);
|
|
VectorScale(dodgeGoal,200.0f,dodgeGoal);
|
|
VectorAdd(dodgeGoal,monster.s.origin,dodgeGoal);
|
|
}
|
|
AddActionForSequence(which_ai, monster, dodgeMove, dodgeGoal, dodgeGoal);
|
|
}
|
|
|
|
last_dodge_time = level.time;
|
|
}
|
|
|
|
void dodge_decision::ActionCompleted(action_c &which_action, ai_c &which_ai, edict_t &monster, float percent_success)
|
|
{//go to next node in path, or set up temp goal for avoidance
|
|
// edict_t *oldtarget = path_nextpoint;
|
|
|
|
if (which_action.Type() == atype_attack)//update validity of attacking here.
|
|
{
|
|
return;
|
|
}
|
|
|
|
//based on success of this last action, update how valid this decision is.
|
|
UpdateValidity(percent_success);
|
|
|
|
//action completed successfully--go to next waypoint (no matter if i'm not following a path)
|
|
if (percent_success > 0.25)
|
|
{
|
|
NextPointInPath(which_ai, monster, monster.s.origin);
|
|
}
|
|
//action completed unsuccessfully--don't bother setting up avoidance points
|
|
else
|
|
{
|
|
NextPointInPath(which_ai, monster, monster.s.origin);
|
|
// PerformAvoidance(which_ai, monster);
|
|
}
|
|
}
|
|
|
|
dodge_decision::dodge_decision(dodge_decision *orig)
|
|
: path_decision(orig)
|
|
{
|
|
validity = orig->validity;
|
|
prev_health = orig->prev_health;
|
|
last_dodge_time = orig->last_dodge_time;
|
|
last_shotat_time = orig->last_shotat_time;
|
|
dodgeSide = 0;
|
|
|
|
VectorCopy(orig->shotStart, shotStart);
|
|
VectorCopy(orig->shotEnd, shotEnd);
|
|
}
|
|
|
|
void dodge_decision::Evaluate(dodge_decision *orig)
|
|
{
|
|
validity = orig->validity;
|
|
prev_health = orig->prev_health;
|
|
last_dodge_time = orig->last_dodge_time;
|
|
last_shotat_time = orig->last_shotat_time;
|
|
dodgeSide = 0;
|
|
|
|
VectorCopy(orig->shotStart, shotStart);
|
|
VectorCopy(orig->shotEnd, shotEnd);
|
|
|
|
path_decision::Evaluate(orig);
|
|
}
|
|
|
|
void dodge_decision::Read()
|
|
{
|
|
char loaded[sizeof(dodge_decision)];
|
|
|
|
gi.ReadFromSavegame('AIDD', loaded, sizeof(dodge_decision));
|
|
Evaluate((dodge_decision *)loaded);
|
|
}
|
|
|
|
void dodge_decision::Write()
|
|
{
|
|
dodge_decision *savable;
|
|
|
|
savable = new dodge_decision(this);
|
|
gi.AppendToSavegame('AIDD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
void dodge_decision::SetInfoForDodge(vec3_t start, vec3_t end)
|
|
{
|
|
last_shotat_time = level.time;
|
|
|
|
VectorCopy(start, shotStart);
|
|
VectorCopy(end, shotEnd);
|
|
}
|
|
|
|
/**********************************************************************************
|
|
**********************************************************************************/
|
|
scripted_decision::scripted_decision(edict_t *goalent, int priority_root, float timeout, edict_t* ScriptOwner)
|
|
:path_decision(goalent,priority_root,timeout)
|
|
{
|
|
scriptDone=false;
|
|
ScriptEnt=ScriptOwner;
|
|
self=NULL;
|
|
ignorePreferred =false;
|
|
orderStartTime=level.time;
|
|
lastMove = NULL;
|
|
VectorClear(oldPosition);
|
|
VectorCopy(vec3_up, actDestDir);
|
|
VectorClear(actDest);
|
|
}
|
|
|
|
|
|
bool scripted_decision::MatchScriptEnt(edict_t* Entity)
|
|
{
|
|
if (Entity == ScriptEnt)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void scripted_decision::ActionCompleted(action_c &which_action, ai_c &which_ai, edict_t &monster, float percent_success)
|
|
{
|
|
// if there is a variable to be signaled, signal it!
|
|
// if (which_action.GetSignalEvent())
|
|
// {
|
|
// which_action.SetSignalEventState(true); // signal it!
|
|
// }
|
|
path_decision::ActionCompleted(which_action, which_ai, monster, percent_success);
|
|
self = &monster;
|
|
|
|
list<scriptOrder_c *>::iterator curOrder = orders.begin();
|
|
|
|
if (curOrder==orders.end())
|
|
{
|
|
return;
|
|
}
|
|
|
|
qboolean isCloseEnoughToDest=false;
|
|
|
|
|
|
// kef -- trying desperately to clean up these boolean checks to the point where
|
|
//a rational individual can actually debug them
|
|
|
|
bool bKillOrder = (*curOrder)->kill,
|
|
bTimedOut = (level.time >= (*curOrder)->holdTime+orderStartTime),
|
|
bShootAttack = which_action.GetClassCode()==SHOOT_ATTACK_ACTION,
|
|
bRunningMeleeAttack = which_action.GetClassCode()==RUNNING_MELEE_ATTACK_ACTION,
|
|
bPreferredMove = !!(*curOrder)->preferredMove,
|
|
bActCodeFall = (*curOrder)->actionType==ACTCODE_FALL,
|
|
bActCodeJump = (*curOrder)->actionType==ACTCODE_JUMP,
|
|
bActCodeMove = (*curOrder)->actionType==ACTCODE_MOVE,
|
|
bFullAnim = bPreferredMove ? !!((*curOrder)->preferredMove->actionFlags & ACTFLAG_FULLANIM) : false,
|
|
bAnimFinished = which_ai.GetBody() ? !!which_ai.GetBody()->IsAnimationFinished() : false,
|
|
bAnimateHold = !!((*curOrder)->flags & ANIMATE_HOLD);
|
|
|
|
|
|
if ((*curOrder)->actionType==ACTCODE_MOVE)
|
|
{
|
|
vec3_t distToDest;
|
|
float closeEnough = 121.0;
|
|
|
|
if (ignorePreferred)//been encountering problems...
|
|
{
|
|
closeEnough=monster.mins[0]*monster.mins[0]+monster.mins[1]*monster.mins[1];
|
|
}
|
|
|
|
VectorSubtract(actDest, monster.s.origin, distToDest);
|
|
distToDest[2]=0;
|
|
if (VectorLengthSquared(distToDest)<closeEnough)
|
|
{
|
|
isCloseEnoughToDest=true;
|
|
}
|
|
else
|
|
{
|
|
VectorNormalize(distToDest);
|
|
float degreePassedDest=DotProduct(distToDest, actDestDir);
|
|
if (degreePassedDest<0.0f)
|
|
{
|
|
isCloseEnoughToDest=true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//done with order--move along...
|
|
//...if i've timed out...
|
|
if (!bKillOrder && bTimedOut ||
|
|
//...or i performed a kill order with the real potential of killing...
|
|
(bKillOrder && (bShootAttack||bRunningMeleeAttack)) ||
|
|
//...or i'm falling
|
|
(!bKillOrder && bPreferredMove && bActCodeFall)||
|
|
//...or i'm jumping
|
|
(!bKillOrder && bPreferredMove && bActCodeJump)||
|
|
//...or i've finished a simple animation and no hold time is specified...
|
|
(!bKillOrder && bPreferredMove && !(bActCodeMove && !bFullAnim) && bAnimFinished && (!(bAnimateHold)))||
|
|
//...or i've gotten Really close to a move target...
|
|
(isCloseEnoughToDest))
|
|
{
|
|
if ((*curOrder)->SignalEvent)
|
|
{
|
|
(*curOrder)->SignalEvent->SetEventState(true);
|
|
}
|
|
ignorePreferred = false;
|
|
orderStartTime = level.time;
|
|
lastMove = (*curOrder)->preferredMove;
|
|
delete (*curOrder);
|
|
orders.erase(curOrder);
|
|
VectorCopy(actDest, oldPosition);
|
|
curOrder=orders.begin();
|
|
|
|
if (curOrder != orders.end()&&(*curOrder)->Emotion)
|
|
{
|
|
which_ai.Emote(monster, (emotion_index) (*curOrder)->Emotion, (*curOrder)->holdTime, true);
|
|
delete (*curOrder);
|
|
orders.erase(curOrder);
|
|
curOrder=orders.begin();
|
|
}
|
|
|
|
if (curOrder != orders.end())
|
|
{
|
|
if ((*curOrder)->absoluteDest)
|
|
{
|
|
VectorCopy((*curOrder)->destVec, actDest);
|
|
}
|
|
else
|
|
{
|
|
VectorAdd((*curOrder)->destVec, monster.s.origin, actDest);
|
|
}
|
|
actDest[2]=monster.s.origin[2];
|
|
VectorSubtract(actDest, monster.s.origin, actDestDir);
|
|
VectorNormalize(actDestDir);
|
|
// gi.dprintf("new actdest: %4.3f %4.3f %4.3f; my pos: %4.3f %4.3f %4.3f\n",actDest[0],actDest[1],actDest[2],monster.s.origin[0],monster.s.origin[1],monster.s.origin[2]);
|
|
}
|
|
}
|
|
//hey, something's wrong! remember that i hit stuff...
|
|
else if (percent_success < 0.5)
|
|
{
|
|
ignorePreferred = true;
|
|
}
|
|
}
|
|
|
|
void scripted_decision::Perform(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
|
|
if (orders.size()>0)
|
|
{
|
|
list<scriptOrder_c *>::iterator tcurOrder = orders.begin();
|
|
if (tcurOrder != orders.end()&&(*tcurOrder)->Emotion)
|
|
{
|
|
which_ai.Emote(monster, (emotion_index) (*tcurOrder)->Emotion, (*tcurOrder)->holdTime, true);
|
|
delete (*tcurOrder);
|
|
orders.erase(tcurOrder);
|
|
tcurOrder=orders.begin();
|
|
}
|
|
}
|
|
|
|
if(ai_dumb->value)
|
|
{
|
|
fxRunner.exec("scripted", &monster);
|
|
}
|
|
|
|
if (orders.size()<=0)
|
|
{
|
|
if(ai_dumb->value)
|
|
{
|
|
fxRunner.exec("confusion", &monster);
|
|
}
|
|
|
|
// gi.dprintf("Script decision has run out of instructions--standing like an idiot!\n");
|
|
|
|
mmove_t *idiotMove = &generic_move_stand;
|
|
|
|
//i just finished up a move that's good for idling in...
|
|
if (lastMove && lastMove->bodyPosition == BODYPOS_IDLE && lastMove->actionFlags & ACTFLAG_LOOPANIM)
|
|
{
|
|
idiotMove = lastMove;
|
|
}
|
|
//i'm in the dark here--i'll just stand around and breathe...
|
|
else
|
|
{
|
|
if (which_ai.GetBody())
|
|
{
|
|
// 1/3/00 kef -- using a different anim when idling inbetween talking stints
|
|
if (which_ai.GetBody()->GetMove() &&
|
|
(which_ai.GetBody()->GetMove()->bodyPosition == BODYPOS_TALKING))
|
|
{ // I was talking, so let's go to std_ietalkpose_n_a_n
|
|
idiotMove = &generic_move_talkpose;
|
|
}
|
|
else
|
|
{
|
|
switch (which_ai.GetBody()->GetRightHandWeapon(monster))
|
|
{
|
|
case ATK_ROCKET:
|
|
idiotMove = &generic_move_staim_l;
|
|
break;
|
|
case ATK_ASSAULTRIFLE:
|
|
case ATK_SNIPER:
|
|
case ATK_AUTOSHOTGUN:
|
|
case ATK_MACHINEGUN:
|
|
case ATK_SHOTGUN:
|
|
case ATK_MICROWAVE:
|
|
case ATK_MICROWAVE_ALT:
|
|
case ATK_FLAMEGUN:
|
|
case ATK_DEKKER:
|
|
idiotMove = &generic_move_stand_mrs;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mmove_t *realIdiotMove = GetSequenceForStand(which_ai, monster, vec3_origin, vec3_origin, idiotMove/*NULL*/, 0);
|
|
|
|
if (idiotMove && realIdiotMove && idiotMove!=realIdiotMove)
|
|
{
|
|
gi.dprintf("Botched idiocy! Couldn't set %s, using %s", idiotMove->ghoulSeqName, realIdiotMove->ghoulSeqName);
|
|
}
|
|
|
|
AddActionForSequence(which_ai, monster, realIdiotMove,
|
|
vec3_origin, vec3_origin, NULL, NULL);
|
|
return;
|
|
}
|
|
list<scriptOrder_c *>::iterator curOrder = orders.begin();
|
|
mmove_t *prefMove;
|
|
|
|
//if i just finished doing a specific stand action with no hold value, consider it finished
|
|
if (((*curOrder)->preferredMove && (*curOrder)->actionType==ACTCODE_STAND) && !((*curOrder)->flags & ANIMATE_HOLD) &&
|
|
which_ai.GetBody() && ((*curOrder)->preferredMove==which_ai.GetBody()->GetMove())&&
|
|
(which_ai.GetBody()->IsAnimationFinished()))
|
|
{
|
|
if ((*curOrder)->SignalEvent)
|
|
{
|
|
(*curOrder)->SignalEvent->SetEventState(true);
|
|
}
|
|
ignorePreferred = false;
|
|
orderStartTime = level.time;
|
|
lastMove = (*curOrder)->preferredMove;
|
|
delete (*curOrder);
|
|
orders.erase(curOrder);
|
|
VectorCopy(actDest, oldPosition);
|
|
curOrder=orders.begin();
|
|
|
|
if (curOrder != orders.end()&&(*curOrder)->Emotion)
|
|
{
|
|
which_ai.Emote(monster, (emotion_index) (*curOrder)->Emotion, (*curOrder)->holdTime, true);
|
|
delete (*curOrder);
|
|
orders.erase(curOrder);
|
|
curOrder=orders.begin();
|
|
}
|
|
|
|
if (curOrder == orders.end())
|
|
{
|
|
path_decision::Perform(which_ai, monster);
|
|
return;
|
|
}
|
|
if ((*curOrder)->absoluteDest)
|
|
{
|
|
VectorCopy((*curOrder)->destVec, actDest);
|
|
}
|
|
else
|
|
{
|
|
VectorAdd((*curOrder)->destVec, monster.s.origin, actDest);
|
|
}
|
|
actDest[2]=monster.s.origin[2];
|
|
VectorSubtract(actDest, monster.s.origin, actDestDir);
|
|
VectorNormalize(actDestDir);
|
|
// gi.dprintf("new actdest: %4.3f %4.3f %4.3f; my pos: %4.3f %4.3f %4.3f\n",actDest[0],actDest[1],actDest[2],monster.s.origin[0],monster.s.origin[1],monster.s.origin[2]);
|
|
}
|
|
|
|
if (VectorCompare(oldPosition,vec3_origin))
|
|
{
|
|
VectorCopy(monster.s.origin, oldPosition);
|
|
|
|
if ((*curOrder)->absoluteDest)
|
|
{
|
|
VectorCopy((*curOrder)->destVec, actDest);
|
|
}
|
|
else
|
|
{
|
|
VectorAdd((*curOrder)->destVec, monster.s.origin, actDest);
|
|
}
|
|
actDest[2]=monster.s.origin[2];
|
|
VectorSubtract(actDest, monster.s.origin, actDestDir);
|
|
VectorNormalize(actDestDir);
|
|
// gi.dprintf("new actdest: %4.3f %4.3f %4.3f; my pos: %4.3f %4.3f %4.3f\n",actDest[0],actDest[1],actDest[2],monster.s.origin[0],monster.s.origin[1],monster.s.origin[2]);
|
|
}
|
|
|
|
// if (ignorePreferred)
|
|
// {
|
|
// prefMove = NULL;
|
|
// }
|
|
// else
|
|
// {
|
|
prefMove = (*curOrder)->preferredMove;
|
|
// }
|
|
|
|
vec3_t actFace;
|
|
|
|
if (VectorCompare((*curOrder)->turnVec, vec3_origin))
|
|
{
|
|
VectorClear(actFace);
|
|
}
|
|
else
|
|
{
|
|
VectorScale((*curOrder)->turnVec, 1000.0, actFace);
|
|
VectorAdd(actFace, monster.s.origin, actFace);
|
|
}
|
|
|
|
if ((!(*curOrder)->absoluteDest)&&VectorCompare((*curOrder)->destVec,vec3_origin))
|
|
{
|
|
VectorCopy(monster.s.origin, actDest);
|
|
|
|
VectorCopy(vec3_up, actDestDir);
|
|
}
|
|
|
|
//if my order has a target entity, set im to be dest and face
|
|
edict_t *orderTarget = (*curOrder)->target;
|
|
bool bTargetIsANotNull = orderTarget?( (orderTarget->classname && (0 == strcmp(orderTarget->classname, "info_notnull"))) ):false;
|
|
// don't bother checking the health if it's an info_notnull. just use it.
|
|
if ( orderTarget && orderTarget->inuse &&
|
|
((orderTarget->health>0) || bTargetIsANotNull) )
|
|
{
|
|
VectorCopy(orderTarget->s.origin, actDest);
|
|
VectorCopy(orderTarget->s.origin, actFace);
|
|
|
|
VectorSubtract(actDest, monster.s.origin, actDestDir);
|
|
VectorNormalize(actDestDir);
|
|
}
|
|
|
|
vec3_t shortDest;
|
|
|
|
VectorSubtract(actDest, monster.s.origin, shortDest);
|
|
if (VectorLengthSquared(shortDest)>0)
|
|
{
|
|
float distToGo=VectorNormalize(shortDest);
|
|
//don't break up the motion for jumping actions
|
|
if ((distToGo > (*curOrder)->speed*0.2)&&(*curOrder)->actionType!=ACTCODE_JUMP&&(*curOrder)->actionType!=ACTCODE_FALL)
|
|
{
|
|
distToGo=(*curOrder)->speed*0.2;
|
|
}
|
|
VectorScale(shortDest, distToGo, shortDest);
|
|
}
|
|
VectorAdd(shortDest, monster.s.origin, shortDest);
|
|
|
|
mmove_t *newMove=GetSequenceForActionCode(which_ai, monster, (*curOrder)->actionType, shortDest, actFace, NULL, prefMove);
|
|
|
|
if (prefMove && newMove && newMove != prefMove)
|
|
{
|
|
if (which_ai.GetBody() && which_ai.GetBody()->IsAvailableSequence(monster, prefMove))
|
|
{
|
|
gi.dprintf("script anim: couldn't set %s (but it's available!), using %s!\n", prefMove->ghoulSeqName, newMove->ghoulSeqName);
|
|
}
|
|
else
|
|
{
|
|
gi.dprintf("script anim: %s not available for %s, using %s!\n", prefMove->ghoulSeqName, monster.classname, newMove->ghoulSeqName);
|
|
}
|
|
}
|
|
|
|
float actionHoldTime=0.0;
|
|
if ((*curOrder)->flags & ANIMATE_HOLD)
|
|
{
|
|
actionHoldTime=(*curOrder)->holdTime;
|
|
}
|
|
|
|
//hey! do these still need to be different? COULD add target elsewise...
|
|
if ((*curOrder)->kill || (*curOrder)->NullTarget)
|
|
{
|
|
AddActionForSequence(which_ai, monster, newMove,
|
|
shortDest, actFace, (*curOrder)->target, NULL, (*curOrder)->kill, (*curOrder)->NullTarget, actionHoldTime);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, newMove,
|
|
shortDest, actFace, NULL, NULL, false, (*curOrder)->NullTarget, actionHoldTime);
|
|
}
|
|
}
|
|
|
|
void scripted_decision::AddOrder(ai_c &which_ai, edict_t &monster, scriptOrder_c &this_order)
|
|
{
|
|
if (orders.size()==0)
|
|
{
|
|
//if it's an emotion animate command, just do it (only if we're at the top of the orders list currently)
|
|
if (this_order.Emotion)
|
|
{
|
|
which_ai.Emote(monster, (emotion_index) this_order.Emotion, this_order.holdTime, true);
|
|
return;
|
|
}
|
|
|
|
ignorePreferred =false;
|
|
orderStartTime=level.time;
|
|
VectorClear(oldPosition);
|
|
VectorCopy(vec3_up, actDestDir);
|
|
VectorClear(actDest);
|
|
}
|
|
|
|
scriptOrder_c *newOrder = new scriptOrder_c();
|
|
newOrder->absoluteDest=this_order.absoluteDest;
|
|
newOrder->actionType=this_order.actionType;
|
|
VectorCopy(this_order.destVec,newOrder->destVec);
|
|
newOrder->holdTime=this_order.holdTime;
|
|
newOrder->preferredMove=this_order.preferredMove;
|
|
VectorCopy(this_order.turnVec,newOrder->turnVec);
|
|
newOrder->flags=this_order.flags;
|
|
newOrder->SignalEvent=this_order.SignalEvent;
|
|
newOrder->speed=this_order.speed;
|
|
newOrder->target=this_order.target;
|
|
newOrder->kill=this_order.kill;
|
|
newOrder->NullTarget = this_order.NullTarget;
|
|
newOrder->Emotion = this_order.Emotion;
|
|
|
|
orders.insert(orders.end(),newOrder);
|
|
}
|
|
|
|
|
|
scripted_decision::~scripted_decision(void)
|
|
{
|
|
list<scriptOrder_c *>::iterator curOrder = orders.begin();
|
|
path_decision::~path_decision();
|
|
while(curOrder!=orders.end())
|
|
{
|
|
delete (*curOrder);
|
|
orders.erase(curOrder++);
|
|
}
|
|
}
|
|
|
|
scripted_decision::scripted_decision(scripted_decision *orig)
|
|
: path_decision(orig)
|
|
{
|
|
scriptDone = orig->scriptDone;
|
|
*(int *)&self = GetEdictNum(orig->self);
|
|
ignorePreferred = orig->ignorePreferred;
|
|
orderStartTime = orig->orderStartTime;
|
|
VectorCopy(orig->oldPosition, oldPosition);
|
|
VectorCopy(orig->actDest, actDest);
|
|
VectorCopy(orig->actDestDir, actDestDir);
|
|
*(int *)&lastMove = GetMmoveNum(orig->lastMove);
|
|
*(int *)&ScriptEnt = GetEdictNum(orig->ScriptEnt);
|
|
}
|
|
|
|
void scripted_decision::Evaluate(scripted_decision *orig)
|
|
{
|
|
scriptDone = orig->scriptDone;
|
|
self = GetEdictPtr((int)orig->self);
|
|
ignorePreferred = orig->ignorePreferred;
|
|
orderStartTime = orig->orderStartTime;
|
|
VectorCopy(orig->oldPosition, oldPosition);
|
|
VectorCopy(orig->actDest, actDest);
|
|
VectorCopy(orig->actDestDir, actDestDir);
|
|
lastMove = GetMmovePtr((int)orig->lastMove);
|
|
ScriptEnt = GetEdictPtr((int)orig->ScriptEnt);
|
|
|
|
path_decision::Evaluate(orig);
|
|
}
|
|
|
|
void scripted_decision::Read()
|
|
{
|
|
char loaded[sizeof(scripted_decision)];
|
|
int i;
|
|
scriptOrder_c *temp;
|
|
int count;
|
|
|
|
gi.ReadFromSavegame('AISD', loaded, SCRIPTED_DECISION_END);
|
|
Evaluate((scripted_decision *)loaded);
|
|
|
|
gi.ReadFromSavegame('AION', &count, sizeof(count));
|
|
if(count)
|
|
{
|
|
for(i = 0; i < count; i++)
|
|
{
|
|
temp = new scriptOrder_c();
|
|
temp->Read();
|
|
orders.push_back(temp);
|
|
}
|
|
}
|
|
}
|
|
|
|
void scripted_decision::Write()
|
|
{
|
|
scripted_decision *savable;
|
|
int count;
|
|
list<scriptOrder_c *>::iterator it;
|
|
|
|
savable = new scripted_decision(this);
|
|
gi.AppendToSavegame('AISD', savable, SCRIPTED_DECISION_END);
|
|
delete savable;
|
|
|
|
count = orders.size();
|
|
gi.AppendToSavegame('AION', &count, sizeof(count));
|
|
if(count)
|
|
{
|
|
for(it = orders.begin(); it != orders.end(); it++)
|
|
{
|
|
(*it)->Write();
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
|
|
scriptOrder_c::scriptOrder_c(void)
|
|
{
|
|
}
|
|
|
|
scriptOrder_c::scriptOrder_c(scriptOrder_c *orig)
|
|
{
|
|
*(int *)&preferredMove = GetMmoveNum(orig->preferredMove);
|
|
holdTime = orig->holdTime;
|
|
VectorCopy(orig->turnVec, turnVec);
|
|
VectorCopy(orig->destVec, destVec);
|
|
absoluteDest = orig->absoluteDest;
|
|
kill = orig->kill;
|
|
actionType = orig->actionType;
|
|
flags = orig->flags;
|
|
*(int *)&SignalEvent = GetEventNum(orig->SignalEvent);
|
|
speed = orig->speed;
|
|
*(int *)&target = GetEdictNum(orig->target);
|
|
NullTarget = orig->NullTarget;
|
|
Emotion = orig->Emotion;
|
|
}
|
|
|
|
void scriptOrder_c::Evaluate(scriptOrder_c *orig)
|
|
{
|
|
preferredMove = GetMmovePtr((int)orig->preferredMove);
|
|
holdTime = orig->holdTime;
|
|
VectorCopy(orig->turnVec, turnVec);
|
|
VectorCopy(orig->destVec, destVec);
|
|
absoluteDest = orig->absoluteDest;
|
|
kill = orig->kill;
|
|
actionType = orig->actionType;
|
|
flags = orig->flags;
|
|
SignalEvent = GetEventPtr((int)orig->SignalEvent);
|
|
speed = orig->speed;
|
|
target = GetEdictPtr((int)orig->target);
|
|
NullTarget = orig->NullTarget;
|
|
Emotion = orig->Emotion;
|
|
}
|
|
|
|
void scriptOrder_c::Read()
|
|
{
|
|
char loaded[sizeof(scriptOrder_c)];
|
|
|
|
gi.ReadFromSavegame('SDSO', loaded, sizeof(scriptOrder_c));
|
|
Evaluate((scriptOrder_c *)loaded);
|
|
}
|
|
|
|
void scriptOrder_c::Write()
|
|
{
|
|
scriptOrder_c *savable;
|
|
|
|
savable = new scriptOrder_c(this);
|
|
gi.AppendToSavegame('SDSO', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
|
|
base_decision::base_decision(base_decision *orig)
|
|
: decision_c(orig)
|
|
{
|
|
}
|
|
|
|
void base_decision::Evaluate(base_decision *orig)
|
|
{
|
|
decision_c::Evaluate(orig);
|
|
}
|
|
|
|
void base_decision::Read()
|
|
{
|
|
char loaded[sizeof(base_decision)];
|
|
|
|
gi.ReadFromSavegame('AIBD', loaded, sizeof(base_decision));
|
|
Evaluate((base_decision *)loaded);
|
|
}
|
|
|
|
void base_decision::Write()
|
|
{
|
|
base_decision *savable;
|
|
|
|
savable = new base_decision(this);
|
|
gi.AppendToSavegame('AIBD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
|
|
pursue_decision::pursue_decision(pursue_decision *orig)
|
|
: path_decision(orig)
|
|
{
|
|
}
|
|
|
|
void pursue_decision::Evaluate(pursue_decision *orig)
|
|
{
|
|
path_decision::Evaluate(orig);
|
|
}
|
|
|
|
void pursue_decision::Read()
|
|
{
|
|
char loaded[sizeof(pursue_decision)];
|
|
|
|
gi.ReadFromSavegame('AIPD', loaded, sizeof(pursue_decision));
|
|
Evaluate((pursue_decision *)loaded);
|
|
}
|
|
|
|
void pursue_decision::Write()
|
|
{
|
|
pursue_decision *savable;
|
|
|
|
savable = new pursue_decision(this);
|
|
gi.AppendToSavegame('AIPD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
|
|
dekker1_decision::dekker1_decision(dekker1_decision *orig)
|
|
: path_decision(orig)
|
|
{
|
|
}
|
|
|
|
void dekker1_decision::Evaluate(dekker1_decision *orig)
|
|
{
|
|
path_decision::Evaluate(orig);
|
|
}
|
|
|
|
void dekker1_decision::Read()
|
|
{
|
|
char loaded[sizeof(dekker1_decision)];
|
|
|
|
gi.ReadFromSavegame('AIPD', loaded, sizeof(dekker1_decision));
|
|
Evaluate((dekker1_decision *)loaded);
|
|
}
|
|
|
|
void dekker1_decision::Write()
|
|
{
|
|
dekker1_decision *savable;
|
|
|
|
savable = new dekker1_decision(this);
|
|
gi.AppendToSavegame('AIPD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
qboolean dekker1_decision::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
path_decision::Consider(which_ai, monster);
|
|
return (monster.health<monster.max_health);
|
|
}
|
|
|
|
void dekker1_decision::AddAction(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
vec3_t toPlayer;
|
|
if (level.sight_client)
|
|
{
|
|
VectorSubtract(level.sight_client->s.origin, monster.s.origin, toPlayer);
|
|
if (VectorNormalize(toPlayer)<70)
|
|
{
|
|
// gi.dprintf("Welcome to my home page!!!!!!!!I hurt you!\n");
|
|
T_Damage(level.sight_client, &monster, &monster, toPlayer, level.sight_client->s.origin, monster.s.origin, 5, 0, DT_WATERZAP, MOD_MPG, 0.5, 0.5);
|
|
}
|
|
}
|
|
fxRunner.exec("environ/dekarmor1", &monster);//MWAHAHA! I SUCK! --sfs
|
|
|
|
// monster.ghoulInst->SetSpeed(gsOne);
|
|
path_decision::AddAction(which_ai, monster);
|
|
}
|
|
|
|
void dekker1_decision::AddActionNonIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
AddDekkerActionNonIdealPosition(which_ai, monster, goalPos, moveVec, 3.0);
|
|
}
|
|
|
|
void dekker1_decision::AddDekkerActionNonIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec, float speed)
|
|
{
|
|
vec3_t actionGoal;
|
|
float move_dist_sq;
|
|
|
|
// move_dist_sq = VectorLengthSquared(moveVec);
|
|
move_dist_sq = 26;
|
|
|
|
if(which_ai.BeingWatched())
|
|
{
|
|
int asdf = 9;
|
|
}
|
|
|
|
int attack = 1;
|
|
|
|
if(nonIdealReason == NI_TOOFAR_SEARCHING)
|
|
{
|
|
attack = 0;
|
|
}
|
|
|
|
//no attacking if we're not ready to harm the player
|
|
float awakeTimeValue = level.time - which_ai.getFirstTargetTime();
|
|
//if(awakeTimeValue < which_ai.GetMySkills()->getHesitation() && (which_ai.GetStartleability()))
|
|
if(awakeTimeValue < which_ai.GetMySkills()->getHesitation() * ((gmonster.GetClosestEnemy() == &monster) ? .5:1))//the closest guy gets to attack quicker
|
|
{ // we are groggy for a bit when we first arise
|
|
attack = 0;
|
|
}
|
|
|
|
which_ai.GetMiscFlags() &= ~MISC_FLAG_FIRINGBLIND;
|
|
|
|
if(nonIdealReason == NI_DODGING)
|
|
{
|
|
mmove_t *dodgeMove;
|
|
|
|
dodgeMove=GetSequenceForDodge(which_ai, monster, goalPos, goalPos, path_goalentity, 0, gi.irand(0,1)?-1:1);
|
|
if(dodgeMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, dodgeMove, goalPos, goalPos);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_DUCKING;
|
|
}
|
|
}
|
|
|
|
if(nonIdealReason == NI_DUCKING)
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
mmove_t *myMove;
|
|
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid);
|
|
|
|
if(myMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, myMove, goalPos, goalPos, path_goalentity);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_FLEEING;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_FLEEING;
|
|
}
|
|
}
|
|
|
|
int fastStrafe = 0;
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_mrs) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_l) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_p))
|
|
{
|
|
fastStrafe = 1;
|
|
}
|
|
|
|
int fastRetreat = 0;
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_mrs) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_p2) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_l))
|
|
{
|
|
fastRetreat = 1;
|
|
}
|
|
|
|
int proceedForward = 1;
|
|
|
|
if((25>move_dist_sq) ||
|
|
(monster.spawnflags & SPAWNFLAG_HOLD_POSITION) ||
|
|
((which_ai.GetPriority() != PRIORITY_HIGH)&&(path_goalentity == level.sight_client)))
|
|
{
|
|
Enemy_Printf(&which_ai, "Non ideal stuck");
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,goalPos,goalPos), goalPos, goalPos);
|
|
proceedForward = 0;
|
|
}
|
|
else if(nonIdealReason == NI_TOOCLOSE)
|
|
{
|
|
proceedForward = 0;
|
|
int backup = 0;
|
|
vec3_t toAvoid, toTarg, looker;
|
|
if(aiPoints.isActive())
|
|
{
|
|
aiPoints.getReversePath(monster.s.origin, goalPos, &which_ai.getPathData(), &which_ai, &monster, which_ai.getTarget());
|
|
|
|
if(which_ai.getPathData().blocked)
|
|
{ // can't go where I want to :(
|
|
backup = 0;
|
|
}
|
|
else
|
|
{
|
|
Enemy_Printf(&which_ai, "Via %d->%d = %d\n", which_ai.getPathData().curNode,
|
|
which_ai.getPathData().nextNode, which_ai.getPathData().finalNode);
|
|
|
|
VectorSubtract(which_ai.getPathData().goPoint, monster.s.origin, toAvoid);
|
|
toAvoid[2] = 0;
|
|
float toAvoidLen = VectorNormalize(toAvoid);
|
|
/* if(VectorLength(toAvoid) < .01)
|
|
{
|
|
VectorCopy(goalPos, actionGoal);
|
|
}
|
|
else*/
|
|
{
|
|
VectorMA(monster.s.origin, speed, toAvoid, actionGoal);
|
|
}
|
|
|
|
//if(toAvoidLen < .05)
|
|
if(toAvoidLen < 10)
|
|
{
|
|
backup = 0;
|
|
}
|
|
else
|
|
{
|
|
if(which_ai.GetMySkills()->testFlag(AIS_NOATTACKONRETREAT) ||
|
|
(which_ai.GetMove() && ClearShot(which_ai, monster, toAvoid, path_goalentity, which_ai.GetMove()->bbox, actionGoal)))
|
|
{ // if we can still shoot once we backup, we like this spot - otherwise no
|
|
backup = 1;
|
|
}
|
|
else
|
|
{
|
|
backup = 0;
|
|
}
|
|
}
|
|
VectorMA(monster.s.origin, speed, toAvoid, actionGoal);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
backup = 0;
|
|
|
|
}
|
|
|
|
if(backup)
|
|
{
|
|
|
|
VectorSubtract(actionGoal, monster.s.origin, toAvoid);
|
|
toAvoid[2] = 0;
|
|
VectorNormalize(toAvoid);
|
|
|
|
AdjustVectorByGuys(&monster, toAvoid);
|
|
|
|
which_ai.RequestMoveOutOfWay(toAvoid);
|
|
|
|
VectorSubtract(goalPos, monster.s.origin, toTarg);
|
|
toTarg[2] = 0;
|
|
float distToTarg = VectorNormalize(toTarg);
|
|
|
|
if(DotProduct(toAvoid, toTarg) < -.717)
|
|
{ //move in a direction opposite of where I want to face
|
|
if(!fastRetreat)
|
|
{
|
|
VectorScale(toAvoid, 4.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
}
|
|
|
|
VectorScale(toAvoid, -speed, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else if(DotProduct(toAvoid, toTarg) > .717)
|
|
{ //move in a direction towards where I face
|
|
VectorScale(toAvoid, speed, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
if(!fastStrafe)
|
|
{
|
|
VectorScale(toAvoid, 12.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
}
|
|
|
|
//set it to the side now - for strafing
|
|
toAvoid[2] = toAvoid[1];
|
|
toAvoid[1] = -toAvoid[0];
|
|
toAvoid[0] = toAvoid[2];
|
|
toAvoid[2] = 0;
|
|
|
|
if(DotProduct(toAvoid, toTarg) > 0)
|
|
{
|
|
VectorScale(toAvoid, speed, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
VectorScale(toAvoid, -speed, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
}
|
|
|
|
if(aipoints_show->value)
|
|
{
|
|
paletteRGBA_t col = {250, 250, 250, 250};
|
|
//FX_MakeLine(node[startNode].getPos(), node[nextNode].getPos(), col, 1);
|
|
FX_MakeLine(which_ai.getPathData().goPoint, monster.s.origin, col, 1);
|
|
FX_MakeRing(which_ai.getPathData().goPoint, 8);
|
|
paletteRGBA_t col2 = {0, 0, 0, 250};
|
|
FX_MakeLine(actionGoal, monster.s.origin, col2, 1);
|
|
}
|
|
|
|
vec3_t testShoot;
|
|
|
|
VectorSubtract(goalPos, monster.s.origin, testShoot);
|
|
VectorNormalize(testShoot);
|
|
VectorMA(monster.s.origin, 64, testShoot, testShoot);
|
|
|
|
if(attack && (!which_ai.GetMySkills()->testFlag(AIS_NOATTACKONRETREAT)) && ClearShot(which_ai, monster, testShoot, path_goalentity, which_ai.GetMove()->bbox))
|
|
{
|
|
//AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, actionGoal, goalPos, path_goalentity), actionGoal, goalPos, path_goalentity);
|
|
AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, actionGoal, looker, path_goalentity), actionGoal, looker, path_goalentity);
|
|
}
|
|
else
|
|
{ //just back up a bit - can't shoot from here
|
|
//oh dear. gsfMovement is fairly different from gsfAttack... I did not know =/
|
|
if(!fastRetreat)
|
|
{
|
|
VectorScale(toAvoid, 4.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
}
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(attack && ClearShot(which_ai, monster, goalPos, path_goalentity, which_ai.GetMove()->bbox))
|
|
{ //can't move, but at least I can shoot!
|
|
AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, monster.s.origin, goalPos, path_goalentity), monster.s.origin, goalPos, path_goalentity);
|
|
}
|
|
else
|
|
{ //Stand around and do nothing - too close and not clear. Sad.
|
|
if(which_ai.GetMySkills()->testFlag(AIS_WONTADVANCE))
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,goalPos,goalPos), goalPos, goalPos);
|
|
}
|
|
else
|
|
{
|
|
proceedForward = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(blockingEnemy)
|
|
{ //this is only the case if clearshot is obstructed by another guy
|
|
vec3_t dif;
|
|
VectorSubtract(goalPos, monster.s.origin, dif);
|
|
dif[2] = 0;
|
|
float goalDist = VectorNormalize(dif);
|
|
|
|
if(goalDist > 1)
|
|
{
|
|
dif[2] = dif[1];
|
|
dif[1] = dif[0]*-1;
|
|
dif[0] = dif[2];
|
|
dif[2] = 0;
|
|
|
|
VectorMA(monster.s.origin, 64, dif, dif);
|
|
|
|
trace_t tr;
|
|
|
|
gi.trace(monster.s.origin, monster.mins, monster.maxs, dif, &monster, MASK_MONSTERSOLID, &tr);
|
|
|
|
vec3_t lookSpot;
|
|
VectorCopy(goalPos, lookSpot);
|
|
|
|
if(nonIdealReason == NI_FLEEING)
|
|
{
|
|
VectorCopy(dif, lookSpot);
|
|
}
|
|
|
|
if(tr.fraction > .99 && !tr.allsolid && !tr.startsolid)
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,dif,lookSpot), dif, lookSpot);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,goalPos,goalPos), goalPos, goalPos);
|
|
}
|
|
proceedForward = 0;
|
|
|
|
if(aipoints_show->value)
|
|
{
|
|
paletteRGBA_t col = {0, 0, 250, 250};
|
|
//FX_MakeLine(node[startNode].getPos(), node[nextNode].getPos(), col, 1);
|
|
FX_MakeLine(dif, monster.s.origin, col, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(proceedForward)// if(nonIdealReason == NI_TOOFAR)//make this catchall - why not
|
|
{
|
|
if(aiPoints.isActive())
|
|
{
|
|
aiPoints.getPath(monster.s.origin, goalPos, &which_ai.getPathData(), &which_ai, &monster, 1, which_ai.getTarget());
|
|
|
|
/* vec3_t sep;
|
|
float len;
|
|
VectorSubtract(which_ai.getPathData().goPoint, monster.s.origin, sep);
|
|
len = VectorLength(sep);
|
|
|
|
if(nonIdealReason == NI_TOOFAR_SEARCHING && which_ai.getPathData().curNode == which_ai.getPathData().nextNode && len < 48)
|
|
{
|
|
VectorCopy(monster.s.origin, which_ai.getPathData().goPoint);
|
|
}*/
|
|
|
|
Enemy_Printf(&which_ai, "Via %d->%d = %d\n", which_ai.getPathData().curNode,
|
|
which_ai.getPathData().nextNode, which_ai.getPathData().finalNode);
|
|
|
|
if(which_ai.getPathData().blocked)
|
|
{ // can't go where I want to :(
|
|
if(attack && which_ai.GetMove() && ClearShot(which_ai, monster, goalPos, path_goalentity, which_ai.GetMove()->bbox))
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, monster.s.origin, goalPos, path_goalentity), monster.s.origin, goalPos, path_goalentity);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,goalPos,goalPos), goalPos, goalPos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vec3_t toAvoid, toTarg, looker;
|
|
|
|
VectorSubtract(which_ai.getPathData().goPoint, monster.s.origin, toAvoid);
|
|
toAvoid[2] = 0;
|
|
VectorNormalize(toAvoid);
|
|
|
|
VectorScale(toAvoid, speed, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
|
|
VectorSubtract(goalPos, monster.s.origin, toTarg);
|
|
toTarg[2] = 0;
|
|
float distToTarg = VectorNormalize(toTarg);
|
|
|
|
if((nonIdealReason == NI_FLEEING)||(!fastStrafe)||(distToTarg > 448))
|
|
{ // good for folks who can't strafe
|
|
VectorScale(toAvoid, 40, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
if(DotProduct(toAvoid, toTarg) < -.717)
|
|
{
|
|
if(!fastRetreat)
|
|
{
|
|
VectorScale(toAvoid, 4.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
}
|
|
|
|
VectorScale(toAvoid, -speed, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else if(DotProduct(toAvoid, toTarg) > .717)
|
|
{
|
|
VectorScale(toAvoid, speed, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
if(!fastStrafe)
|
|
{
|
|
VectorScale(toAvoid, 12.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
}
|
|
|
|
//set it to the side now - for strafing
|
|
toAvoid[2] = toAvoid[1];
|
|
toAvoid[1] = -toAvoid[0];
|
|
toAvoid[0] = toAvoid[2];
|
|
toAvoid[2] = 0;
|
|
|
|
if(DotProduct(toAvoid, toTarg) > 0)
|
|
{
|
|
VectorScale(toAvoid, speed, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
VectorScale(toAvoid, -speed, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
|
|
// VectorScale(toAvoid, speed, looker);
|
|
// VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
}
|
|
|
|
vec3_t dif;
|
|
VectorSubtract(monster.s.origin, actionGoal, dif);
|
|
dif[2] = 0;
|
|
vec3_t dif2;
|
|
VectorSubtract(monster.s.origin, goalPos, dif2);
|
|
dif2[2] = 0;
|
|
if((VectorLengthSquared(dif) > 2)&&((VectorLengthSquared(dif2) > (50*50))||((nonIdealReason == NI_FLEEING)))&&((!which_ai.GetMySkills()->testFlag(AIS_WONTADVANCE))||(nonIdealReason == NI_FLEEING)))
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
else
|
|
{
|
|
if(nonIdealReason == NI_FLEEING)
|
|
{ //get down an' cower
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
mmove_t *myMove;
|
|
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid);
|
|
|
|
if(!myMove)
|
|
{
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid2);
|
|
}
|
|
|
|
if(myMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, myMove, goalPos, goalPos, path_goalentity);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(attack && ClearShot(which_ai, monster, goalPos, path_goalentity, which_ai.GetMove()->bbox))
|
|
{ //can't move, but at least I can shoot! - fixme - some guys shouldn't do this
|
|
AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, monster.s.origin, goalPos, path_goalentity), monster.s.origin, goalPos, path_goalentity);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,actionGoal,goalPos), actionGoal, goalPos);
|
|
}
|
|
}
|
|
//AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
|
|
if(aipoints_show->value)
|
|
{
|
|
VectorScale(toAvoid, 40.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
|
|
paletteRGBA_t col = {250, 250, 250, 250};
|
|
paletteRGBA_t col2 = {0, 0, 0, 250};
|
|
//FX_MakeLine(node[startNode].getPos(), node[nextNode].getPos(), col, 1);
|
|
FX_MakeLine(which_ai.getPathData().goPoint, monster.s.origin, col, 1);
|
|
FX_MakeRing(which_ai.getPathData().goPoint, 4);
|
|
FX_MakeLine(actionGoal, monster.s.origin, col2, 1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract(goalPos, monster.s.origin, actionGoal);
|
|
VectorNormalize(actionGoal);
|
|
VectorScale(actionGoal, speed, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,actionGoal), actionGoal, actionGoal);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------
|
|
|
|
dekker2_decision::dekker2_decision(dekker2_decision *orig)
|
|
: dekker1_decision(orig)
|
|
{
|
|
}
|
|
|
|
void dekker2_decision::Evaluate(dekker2_decision *orig)
|
|
{
|
|
dekker1_decision::Evaluate(orig);
|
|
}
|
|
|
|
void dekker2_decision::Read()
|
|
{
|
|
char loaded[sizeof(dekker2_decision)];
|
|
|
|
gi.ReadFromSavegame('AIPD', loaded, sizeof(dekker2_decision));
|
|
Evaluate((dekker2_decision *)loaded);
|
|
}
|
|
|
|
void dekker2_decision::Write()
|
|
{
|
|
dekker2_decision *savable;
|
|
|
|
savable = new dekker2_decision(this);
|
|
gi.AppendToSavegame('AIPD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
qboolean dekker2_decision::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
dekker1_decision::Consider(which_ai, monster);
|
|
return (monster.health<((float)(monster.max_health))*0.75);
|
|
}
|
|
|
|
void dekker2_decision::AddActionNonIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
AddDekkerActionNonIdealPosition(which_ai, monster, goalPos, moveVec, 100.0);
|
|
}
|
|
|
|
void dekker2_decision::AddAction(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
vec3_t toPlayer;
|
|
if (level.sight_client)
|
|
{
|
|
VectorSubtract(level.sight_client->s.origin, monster.s.origin, toPlayer);
|
|
if (VectorNormalize(toPlayer)<70)
|
|
{
|
|
// gi.dprintf("Welcome to my home page!!!!!!!!I hurt you!\n");
|
|
T_Damage(level.sight_client, &monster, &monster, toPlayer, level.sight_client->s.origin, monster.s.origin, 5, 0, DT_WATERZAP, MOD_MPG, 0.5, 0.5);
|
|
}
|
|
}
|
|
fxRunner.exec("environ/dekarmor1", &monster);//MWAHAHA! I SUCK! --sfs
|
|
|
|
monster.ghoulInst->SetSpeed(gs4Over8);
|
|
dekker1_decision::AddAction(which_ai, monster);
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
|
|
dekker3_decision::dekker3_decision(dekker3_decision *orig)
|
|
: path_decision(orig)
|
|
{
|
|
reachedDest=orig->reachedDest;
|
|
}
|
|
|
|
void dekker3_decision::AddAction(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
vec3_t toPlayer;
|
|
if (level.sight_client)
|
|
{
|
|
VectorSubtract(level.sight_client->s.origin, monster.s.origin, toPlayer);
|
|
if (VectorNormalize(toPlayer)<70)
|
|
{
|
|
// gi.dprintf("Welcome to my home page!!!!!!!!I hurt you!\n");
|
|
T_Damage(level.sight_client, &monster, &monster, toPlayer, level.sight_client->s.origin, monster.s.origin, 5, 0, DT_WATERZAP, MOD_MPG, 0.5, 0.5);
|
|
}
|
|
}
|
|
fxRunner.exec("environ/dekarmor1", &monster);//MWAHAHA! I SUCK! --sfs
|
|
|
|
monster.ghoulInst->SetSpeed(gsOne);
|
|
path_decision::AddAction(which_ai, monster);
|
|
}
|
|
|
|
void dekker3_decision::Evaluate(dekker3_decision *orig)
|
|
{
|
|
reachedDest=orig->reachedDest;
|
|
path_decision::Evaluate(orig);
|
|
}
|
|
|
|
void dekker3_decision::Read()
|
|
{
|
|
char loaded[sizeof(dekker3_decision)];
|
|
|
|
gi.ReadFromSavegame('AIPD', loaded, sizeof(dekker3_decision));
|
|
Evaluate((dekker3_decision *)loaded);
|
|
}
|
|
|
|
void dekker3_decision::Write()
|
|
{
|
|
dekker3_decision *savable;
|
|
|
|
savable = new dekker3_decision(this);
|
|
gi.AppendToSavegame('AIPD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
qboolean dekker3_decision::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
path_decision::Consider(which_ai, monster);
|
|
|
|
return reachedDest;
|
|
}
|
|
|
|
qboolean dekker3_decision::GetGoalPosition(ai_c &which_ai, edict_t &monster, vec3_t gohere)
|
|
{
|
|
//copy ai's enemy_lastseen_pos into gohere vec
|
|
if (gohere)//pass in NULL if just want to know whether we have goal position
|
|
{
|
|
VectorCopy(path_goalpos, gohere);
|
|
}
|
|
|
|
if (!reachedDest)
|
|
{
|
|
edict_t *curPt = G_Find (NULL, FOFS(classname), "func_Dekker_console");
|
|
|
|
if (curPt)
|
|
{
|
|
if (gohere)
|
|
{
|
|
VectorCopy(curPt->s.origin, gohere);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;//there's no dekker-die position on this map--no fair!
|
|
}
|
|
}
|
|
|
|
//check the decision's goalentity for whether i have goal pos, not ai's enemy
|
|
if (path_goalentity)
|
|
{
|
|
//make sure goal entity is still valid
|
|
if ((!path_goalentity->client && !path_goalentity->ai) || !path_goalentity->inuse
|
|
|| path_goalentity->health <= 0 || level.time-path_goal_updatedtime>PATH_DECISION_LOSETARGETTIME)
|
|
{
|
|
path_goalentity = NULL;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
qboolean dekker3_decision::IsIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t curPos, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
|
|
if(!reachedDest)
|
|
{//very special case - there guys are always in an ideal position, sort of
|
|
edict_t *curPt = G_Find (NULL, FOFS(classname), "func_Dekker_console");
|
|
|
|
if (curPt)//got a die point, keep running toward it until i'm really really close
|
|
{
|
|
vec3_t toGoal;
|
|
float distToGoalSq;
|
|
|
|
VectorSubtract(curPt->s.origin, monster.s.origin, toGoal);
|
|
toGoal[2]=0;//ignore vertical diff
|
|
distToGoalSq=VectorLengthSquared(toGoal);
|
|
if (distToGoalSq > 8100)
|
|
{
|
|
nonIdealReason = NI_TOOFAR;//too far, keep runnin
|
|
return false;
|
|
}
|
|
}
|
|
reachedDest=true;//passed the far-away-from-die-point test--i'm either close enough, or it doesn't exist.
|
|
edict_t *curGun;
|
|
|
|
//set up the enthralling dekker-types-something boss stage
|
|
// curGun = G_PickTarget ("consolepoint");
|
|
// if (curGun && !strcmp(curGun->classname, "point_combat"))
|
|
// {
|
|
// monster.ai->NewDecision(new pathcombat_decision(curGun), &monster);
|
|
// monster.ai->SetStartleability(false);
|
|
// }
|
|
|
|
if (curGun = G_Find (NULL, FOFS(targetname), "dekkergun1"))
|
|
{
|
|
curGun->use(curGun, &monster, &monster);
|
|
}
|
|
if (curGun = G_Find (NULL, FOFS(targetname), "dekkergun2"))
|
|
{
|
|
curGun->use(curGun, &monster, &monster);
|
|
}
|
|
if (curGun = G_Find (NULL, FOFS(targetname), "dekkergun3"))
|
|
{
|
|
curGun->use(curGun, &monster, &monster);
|
|
}
|
|
// if (curGun = G_Find (NULL, FOFS(targetname), "dekkerclip"))
|
|
// {
|
|
// curGun->use(curGun, &monster, &monster);
|
|
// }
|
|
}
|
|
|
|
// return path_decision::IsIdealPosition(which_ai, monster, curPos, goalPos, moveVec);
|
|
return false;
|
|
}
|
|
|
|
void dekker3_decision::AddActionNonIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
vec3_t actionGoal;
|
|
float move_dist_sq;
|
|
|
|
// move_dist_sq = VectorLengthSquared(moveVec);
|
|
move_dist_sq = 26;
|
|
|
|
if(which_ai.BeingWatched())
|
|
{
|
|
int asdf = 9;
|
|
}
|
|
|
|
int attack = 1;
|
|
|
|
if(nonIdealReason == NI_TOOFAR_SEARCHING)
|
|
{
|
|
attack = 0;
|
|
}
|
|
|
|
//no attacking if we're not ready to harm the player
|
|
float awakeTimeValue = level.time - which_ai.getFirstTargetTime();
|
|
if(awakeTimeValue < which_ai.GetMySkills()->getHesitation() * ((gmonster.GetClosestEnemy() == &monster) ? .5:1))//the closest guy gets to attack quicker
|
|
{ // we are groggy for a bit when we first arise
|
|
attack = 0;
|
|
}
|
|
|
|
which_ai.GetMiscFlags() &= ~MISC_FLAG_FIRINGBLIND;
|
|
|
|
if(nonIdealReason == NI_DODGING)
|
|
{
|
|
mmove_t *dodgeMove;
|
|
|
|
dodgeMove=GetSequenceForDodge(which_ai, monster, goalPos, goalPos, path_goalentity, 0, gi.irand(0,1)?-1:1);
|
|
if(dodgeMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, dodgeMove, goalPos, goalPos);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_DUCKING;
|
|
}
|
|
}
|
|
|
|
if(nonIdealReason == NI_DUCKING)
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
mmove_t *myMove;
|
|
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid);
|
|
|
|
if(myMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, myMove, goalPos, goalPos, path_goalentity);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_FLEEING;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_FLEEING;
|
|
}
|
|
}
|
|
|
|
int fastStrafe = 0;
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_mrs) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_l) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_p))
|
|
{
|
|
fastStrafe = 1;
|
|
}
|
|
|
|
int fastRetreat = 0;
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_mrs) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_p2) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_l))
|
|
{
|
|
fastRetreat = 1;
|
|
}
|
|
|
|
int proceedForward = 1;
|
|
|
|
|
|
if(aiPoints.isActive())
|
|
{
|
|
bool doPath=true;
|
|
aiPoints.getPath(monster.s.origin, goalPos, &which_ai.getPathData(), &which_ai, &monster, 1, which_ai.getTarget());
|
|
|
|
|
|
Enemy_Printf(&which_ai, "Via %d->%d = %d\n", which_ai.getPathData().curNode,
|
|
which_ai.getPathData().nextNode, which_ai.getPathData().finalNode);
|
|
|
|
if(which_ai.getPathData().blocked)
|
|
{ // can't go where I want to :(
|
|
// if(attack && which_ai.GetMove() && ClearShot(which_ai, monster, goalPos, path_goalentity, which_ai.GetMove()->bbox))
|
|
// {
|
|
// AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, monster.s.origin, goalPos, path_goalentity), monster.s.origin, goalPos, path_goalentity);
|
|
// doPath=false;
|
|
// }
|
|
}
|
|
if (doPath)
|
|
{
|
|
vec3_t toAvoid, toTarg, looker;
|
|
|
|
VectorSubtract(which_ai.getPathData().goPoint, monster.s.origin, toAvoid);
|
|
toAvoid[2] = 0;
|
|
VectorNormalize(toAvoid);
|
|
|
|
VectorScale(toAvoid, 100.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
|
|
VectorSubtract(goalPos, monster.s.origin, toTarg);
|
|
toTarg[2] = 0;
|
|
float distToTarg = VectorNormalize(toTarg);
|
|
|
|
if((nonIdealReason == NI_FLEEING)||(!fastStrafe)||(distToTarg > 448))
|
|
{ // good for folks who can't strafe
|
|
VectorScale(toAvoid, 40, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
VectorScale(toAvoid, 2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
|
|
vec3_t dif;
|
|
VectorSubtract(monster.s.origin, actionGoal, dif);
|
|
dif[2] = 0;
|
|
vec3_t dif2;
|
|
VectorSubtract(monster.s.origin, goalPos, dif2);
|
|
dif2[2] = 0;
|
|
if((VectorLengthSquared(dif) > 2)&&((VectorLengthSquared(dif2) > (50*50))||((nonIdealReason == NI_FLEEING)))&&((!which_ai.GetMySkills()->testFlag(AIS_WONTADVANCE))||(nonIdealReason == NI_FLEEING)))
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
|
|
if(aipoints_show->value)
|
|
{
|
|
VectorScale(toAvoid, 40.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
|
|
paletteRGBA_t col = {250, 250, 250, 250};
|
|
paletteRGBA_t col2 = {0, 0, 0, 250};
|
|
FX_MakeLine(which_ai.getPathData().goPoint, monster.s.origin, col, 1);
|
|
FX_MakeRing(which_ai.getPathData().goPoint, 4);
|
|
FX_MakeLine(actionGoal, monster.s.origin, col2, 1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract(goalPos, monster.s.origin, actionGoal);
|
|
VectorNormalize(actionGoal);
|
|
VectorScale(actionGoal, 100.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,actionGoal), actionGoal, actionGoal);
|
|
}
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------
|
|
|
|
dekker4_decision::dekker4_decision(dekker4_decision *orig)
|
|
: path_decision(orig)
|
|
{
|
|
reachedDest=orig->reachedDest;
|
|
}
|
|
|
|
void dekker4_decision::AddAction(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
vec3_t toPlayer;
|
|
if (level.sight_client)
|
|
{
|
|
VectorSubtract(level.sight_client->s.origin, monster.s.origin, toPlayer);
|
|
if (VectorNormalize(toPlayer)<70)
|
|
{
|
|
// gi.dprintf("Welcome to my home page!!!!!!!!I hurt you!\n");
|
|
T_Damage(level.sight_client, &monster, &monster, toPlayer, level.sight_client->s.origin, monster.s.origin, 5, 0, DT_WATERZAP, MOD_MPG, 0.5, 0.5);
|
|
}
|
|
}
|
|
fxRunner.exec("environ/dekarmor1", &monster);//MWAHAHA! I SUCK! --sfs
|
|
|
|
monster.ghoulInst->SetSpeed(gsOne);
|
|
path_decision::AddAction(which_ai, monster);
|
|
}
|
|
|
|
void dekker4_decision::Evaluate(dekker4_decision *orig)
|
|
{
|
|
reachedDest=orig->reachedDest;
|
|
path_decision::Evaluate(orig);
|
|
}
|
|
|
|
void dekker4_decision::Read()
|
|
{
|
|
char loaded[sizeof(dekker3_decision)];
|
|
|
|
gi.ReadFromSavegame('AIPD', loaded, sizeof(dekker3_decision));
|
|
Evaluate((dekker4_decision *)loaded);
|
|
}
|
|
|
|
void dekker4_decision::Write()
|
|
{
|
|
dekker4_decision *savable;
|
|
|
|
savable = new dekker4_decision(this);
|
|
gi.AppendToSavegame('AIPD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
qboolean dekker4_decision::Consider(ai_c &which_ai, edict_t &monster)
|
|
{
|
|
path_decision::Consider(which_ai, monster);
|
|
|
|
return false;
|
|
}
|
|
|
|
qboolean dekker4_decision::GetGoalPosition(ai_c &which_ai, edict_t &monster, vec3_t gohere)
|
|
{
|
|
//copy ai's enemy_lastseen_pos into gohere vec
|
|
if (gohere)//pass in NULL if just want to know whether we have goal position
|
|
{
|
|
VectorCopy(path_goalpos, gohere);
|
|
}
|
|
|
|
if (!reachedDest)
|
|
{
|
|
edict_t *curPt = G_Find (NULL, FOFS(classname), "func_Dekker_diehere");
|
|
|
|
if (curPt)
|
|
{
|
|
if (gohere)
|
|
{
|
|
VectorCopy(curPt->s.origin, gohere);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;//there's no dekker-die position on this map--no fair!
|
|
}
|
|
}
|
|
|
|
//check the decision's goalentity for whether i have goal pos, not ai's enemy
|
|
if (path_goalentity)
|
|
{
|
|
//make sure goal entity is still valid
|
|
if ((!path_goalentity->client && !path_goalentity->ai) || !path_goalentity->inuse
|
|
|| path_goalentity->health <= 0 || level.time-path_goal_updatedtime>PATH_DECISION_LOSETARGETTIME)
|
|
{
|
|
path_goalentity = NULL;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
qboolean dekker4_decision::IsIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t curPos, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
|
|
if(!reachedDest)
|
|
{//very special case - there guys are always in an ideal position, sort of
|
|
edict_t *curPt = G_Find (NULL, FOFS(classname), "func_Dekker_diehere");
|
|
|
|
if (curPt)//got a die point, keep running toward it until i'm really really close
|
|
{
|
|
vec3_t toGoal;
|
|
float distToGoalSq;
|
|
|
|
VectorSubtract(curPt->s.origin, monster.s.origin, toGoal);
|
|
toGoal[2]=0;//ignore vertical diff
|
|
distToGoalSq=VectorLengthSquared(toGoal);
|
|
if (distToGoalSq > 4900)
|
|
{
|
|
nonIdealReason = NI_TOOFAR;//too far, keep runnin
|
|
return false;
|
|
}
|
|
}
|
|
reachedDest=true;//passed the far-away-from-die-point test--i'm either close enough, or it doesn't exist.
|
|
monster.spawnflags |= SPAWNFLAG_HOLD_POSITION;
|
|
// edict_t *curGun;
|
|
// if (curGun = G_Find (NULL, FOFS(targetname), "dekkergun1"))
|
|
// {
|
|
// curGun->use(curGun, &monster, &monster);
|
|
// }
|
|
// if (curGun = G_Find (NULL, FOFS(targetname), "dekkergun2"))
|
|
// {
|
|
// curGun->use(curGun, &monster, &monster);
|
|
// }
|
|
// if (curGun = G_Find (NULL, FOFS(targetname), "dekkergun3"))
|
|
// {
|
|
// curGun->use(curGun, &monster, &monster);
|
|
// }
|
|
// if (curGun = G_Find (NULL, FOFS(targetname), "dekkerclip"))
|
|
// {
|
|
// curGun->use(curGun, &monster, &monster);
|
|
// }
|
|
}
|
|
|
|
return path_decision::IsIdealPosition(which_ai, monster, curPos, goalPos, moveVec);
|
|
|
|
}
|
|
|
|
void dekker4_decision::AddActionNonIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
vec3_t actionGoal;
|
|
float move_dist_sq;
|
|
|
|
// move_dist_sq = VectorLengthSquared(moveVec);
|
|
move_dist_sq = 26;
|
|
|
|
if(which_ai.BeingWatched())
|
|
{
|
|
int asdf = 9;
|
|
}
|
|
|
|
int attack = 1;
|
|
|
|
if(nonIdealReason == NI_TOOFAR_SEARCHING)
|
|
{
|
|
attack = 0;
|
|
}
|
|
|
|
//no attacking if we're not ready to harm the player
|
|
float awakeTimeValue = level.time - which_ai.getFirstTargetTime();
|
|
if(awakeTimeValue < which_ai.GetMySkills()->getHesitation() * ((gmonster.GetClosestEnemy() == &monster) ? .5:1))//the closest guy gets to attack quicker
|
|
{ // we are groggy for a bit when we first arise
|
|
attack = 0;
|
|
}
|
|
|
|
which_ai.GetMiscFlags() &= ~MISC_FLAG_FIRINGBLIND;
|
|
|
|
if(nonIdealReason == NI_DODGING)
|
|
{
|
|
mmove_t *dodgeMove;
|
|
|
|
dodgeMove=GetSequenceForDodge(which_ai, monster, goalPos, goalPos, path_goalentity, 0, gi.irand(0,1)?-1:1);
|
|
if(dodgeMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, dodgeMove, goalPos, goalPos);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_DUCKING;
|
|
}
|
|
}
|
|
|
|
if(nonIdealReason == NI_DUCKING)
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
mmove_t *myMove;
|
|
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid);
|
|
|
|
if(myMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, myMove, goalPos, goalPos, path_goalentity);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_FLEEING;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_FLEEING;
|
|
}
|
|
}
|
|
|
|
int fastStrafe = 0;
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_mrs) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_l) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_p))
|
|
{
|
|
fastStrafe = 1;
|
|
}
|
|
|
|
int fastRetreat = 0;
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_mrs) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_p2) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_l))
|
|
{
|
|
fastRetreat = 1;
|
|
}
|
|
|
|
int proceedForward = 1;
|
|
|
|
|
|
if(aiPoints.isActive())
|
|
{
|
|
bool doPath=true;
|
|
aiPoints.getPath(monster.s.origin, goalPos, &which_ai.getPathData(), &which_ai, &monster, 1, which_ai.getTarget());
|
|
|
|
|
|
Enemy_Printf(&which_ai, "Via %d->%d = %d\n", which_ai.getPathData().curNode,
|
|
which_ai.getPathData().nextNode, which_ai.getPathData().finalNode);
|
|
|
|
if(which_ai.getPathData().blocked)
|
|
{ // can't go where I want to :(
|
|
// if(attack && which_ai.GetMove() && ClearShot(which_ai, monster, goalPos, path_goalentity, which_ai.GetMove()->bbox))
|
|
// {
|
|
// AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, monster.s.origin, goalPos, path_goalentity), monster.s.origin, goalPos, path_goalentity);
|
|
// doPath=false;
|
|
// }
|
|
}
|
|
if (doPath)
|
|
{
|
|
vec3_t toAvoid, toTarg, looker;
|
|
|
|
VectorSubtract(which_ai.getPathData().goPoint, monster.s.origin, toAvoid);
|
|
toAvoid[2] = 0;
|
|
VectorNormalize(toAvoid);
|
|
|
|
VectorScale(toAvoid, 100.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
|
|
VectorSubtract(goalPos, monster.s.origin, toTarg);
|
|
toTarg[2] = 0;
|
|
float distToTarg = VectorNormalize(toTarg);
|
|
|
|
if((nonIdealReason == NI_FLEEING)||(!fastStrafe)||(distToTarg > 448))
|
|
{ // good for folks who can't strafe
|
|
VectorScale(toAvoid, 40, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
VectorScale(toAvoid, 2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
|
|
vec3_t dif;
|
|
VectorSubtract(monster.s.origin, actionGoal, dif);
|
|
dif[2] = 0;
|
|
vec3_t dif2;
|
|
VectorSubtract(monster.s.origin, goalPos, dif2);
|
|
dif2[2] = 0;
|
|
if((VectorLengthSquared(dif) > 2)&&((VectorLengthSquared(dif2) > (50*50))||((nonIdealReason == NI_FLEEING)))&&((!which_ai.GetMySkills()->testFlag(AIS_WONTADVANCE))||(nonIdealReason == NI_FLEEING)))
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
|
|
if(aipoints_show->value)
|
|
{
|
|
VectorScale(toAvoid, 40.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
|
|
paletteRGBA_t col = {250, 250, 250, 250};
|
|
paletteRGBA_t col2 = {0, 0, 0, 250};
|
|
FX_MakeLine(which_ai.getPathData().goPoint, monster.s.origin, col, 1);
|
|
FX_MakeRing(which_ai.getPathData().goPoint, 4);
|
|
FX_MakeLine(actionGoal, monster.s.origin, col2, 1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract(goalPos, monster.s.origin, actionGoal);
|
|
VectorNormalize(actionGoal);
|
|
VectorScale(actionGoal, 100.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,actionGoal), actionGoal, actionGoal);
|
|
}
|
|
}
|
|
|
|
/*
|
|
void dekker4_decision::AddActionNonIdealPosition(ai_c &which_ai, edict_t &monster, vec3_t goalPos, vec3_t moveVec)
|
|
{
|
|
vec3_t actionGoal;
|
|
float move_dist_sq;
|
|
|
|
// move_dist_sq = VectorLengthSquared(moveVec);
|
|
move_dist_sq = 26;
|
|
|
|
if(which_ai.BeingWatched())
|
|
{
|
|
int asdf = 9;
|
|
}
|
|
|
|
int attack = 1;
|
|
|
|
if(nonIdealReason == NI_TOOFAR_SEARCHING)
|
|
{
|
|
attack = 0;
|
|
}
|
|
|
|
//no attacking if we're not ready to harm the player
|
|
float awakeTimeValue = level.time - which_ai.getFirstTargetTime();
|
|
//if(awakeTimeValue < which_ai.GetMySkills()->getHesitation() && (which_ai.GetStartleability()))
|
|
if(awakeTimeValue < which_ai.GetMySkills()->getHesitation() * ((gmonster.GetClosestEnemy() == &monster) ? .5:1))//the closest guy gets to attack quicker
|
|
{ // we are groggy for a bit when we first arise
|
|
attack = 0;
|
|
}
|
|
|
|
which_ai.GetMiscFlags() &= ~MISC_FLAG_FIRINGBLIND;
|
|
|
|
if(nonIdealReason == NI_DODGING)
|
|
{
|
|
mmove_t *dodgeMove;
|
|
|
|
dodgeMove=GetSequenceForDodge(which_ai, monster, goalPos, goalPos, path_goalentity, 0, gi.irand(0,1)?-1:1);
|
|
if(dodgeMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, dodgeMove, goalPos, goalPos);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_DUCKING;
|
|
}
|
|
}
|
|
|
|
if(nonIdealReason == NI_DUCKING)
|
|
{
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
mmove_t *myMove;
|
|
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid);
|
|
|
|
if(myMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, myMove, goalPos, goalPos, path_goalentity);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_FLEEING;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nonIdealReason = NI_FLEEING;
|
|
}
|
|
}
|
|
|
|
int fastStrafe = 0;
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_mrs) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_l) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_raimstrafr_p))
|
|
{
|
|
fastStrafe = 1;
|
|
}
|
|
|
|
int fastRetreat = 0;
|
|
if(which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_mrs) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_p2) ||
|
|
which_ai.GetBody()->IsAvailableSequence(monster, &generic_move_rbackaim_l))
|
|
{
|
|
fastRetreat = 1;
|
|
}
|
|
|
|
int proceedForward = 1;
|
|
|
|
// if(proceedForward)// if(nonIdealReason == NI_TOOFAR)//make this catchall - why not
|
|
// {
|
|
if(aiPoints.isActive())
|
|
{
|
|
aiPoints.getPath(monster.s.origin, goalPos, &which_ai.getPathData(), &which_ai, &monster, 1, which_ai.getTarget());
|
|
|
|
|
|
Enemy_Printf(&which_ai, "Via %d->%d = %d\n", which_ai.getPathData().curNode,
|
|
which_ai.getPathData().nextNode, which_ai.getPathData().finalNode);
|
|
|
|
if(which_ai.getPathData().blocked)
|
|
{ // can't go where I want to :(
|
|
if(attack && which_ai.GetMove() && ClearShot(which_ai, monster, goalPos, path_goalentity, which_ai.GetMove()->bbox))
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, monster.s.origin, goalPos, path_goalentity), monster.s.origin, goalPos, path_goalentity);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,goalPos,goalPos), goalPos, goalPos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vec3_t toAvoid, toTarg, looker;
|
|
|
|
VectorSubtract(which_ai.getPathData().goPoint, monster.s.origin, toAvoid);
|
|
toAvoid[2] = 0;
|
|
VectorNormalize(toAvoid);
|
|
|
|
VectorScale(toAvoid, 100.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
|
|
VectorSubtract(goalPos, monster.s.origin, toTarg);
|
|
toTarg[2] = 0;
|
|
float distToTarg = VectorNormalize(toTarg);
|
|
|
|
if((nonIdealReason == NI_FLEEING)||(!fastStrafe)||(distToTarg > 448))
|
|
{ // good for folks who can't strafe
|
|
VectorScale(toAvoid, 40, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
|
|
if(DotProduct(toAvoid, toTarg) > .717)
|
|
{
|
|
VectorScale(toAvoid, 2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
if(!fastStrafe)
|
|
{
|
|
VectorScale(toAvoid, 12.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
}
|
|
|
|
//set it to the side now - for strafing
|
|
toAvoid[2] = toAvoid[1];
|
|
toAvoid[1] = -toAvoid[0];
|
|
toAvoid[0] = toAvoid[2];
|
|
toAvoid[2] = 0;
|
|
|
|
if(DotProduct(toAvoid, toTarg) > 0)
|
|
{
|
|
VectorScale(toAvoid, 2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
else
|
|
{
|
|
VectorScale(toAvoid, -2440, looker);
|
|
VectorAdd(monster.s.origin, looker, looker);
|
|
}
|
|
}
|
|
}
|
|
|
|
vec3_t dif;
|
|
VectorSubtract(monster.s.origin, actionGoal, dif);
|
|
dif[2] = 0;
|
|
vec3_t dif2;
|
|
VectorSubtract(monster.s.origin, goalPos, dif2);
|
|
dif2[2] = 0;
|
|
if((VectorLengthSquared(dif) > 2)&&((VectorLengthSquared(dif2) > (50*50))||((nonIdealReason == NI_FLEEING)))&&((!which_ai.GetMySkills()->testFlag(AIS_WONTADVANCE))||(nonIdealReason == NI_FLEEING)))
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
else
|
|
{
|
|
if(nonIdealReason == NI_FLEEING)
|
|
{ //get down an' cower
|
|
body_c *body = which_ai.GetBody();
|
|
|
|
if (body)
|
|
{
|
|
mmove_t *myMove;
|
|
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid);
|
|
|
|
if(!myMove)
|
|
{
|
|
myMove = body->GetSequenceForStand(monster,goalPos,goalPos,ACTSUB_NORMAL,BBOX_PRESET_CROUCH, &generic_move_crouch_cower_mid2);
|
|
}
|
|
|
|
if(myMove)
|
|
{
|
|
AddActionForSequence(which_ai, monster, myMove, goalPos, goalPos, path_goalentity);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(attack && ClearShot(which_ai, monster, goalPos, path_goalentity, which_ai.GetMove()->bbox))
|
|
{ //can't move, but at least I can shoot! - fixme - some guys shouldn't do this
|
|
AddActionForSequence(which_ai, monster, GetSequenceForAttack(which_ai, monster, monster.s.origin, goalPos, path_goalentity), monster.s.origin, goalPos, path_goalentity);
|
|
}
|
|
else
|
|
{
|
|
AddActionForSequence(which_ai, monster, GetSequenceForStand(which_ai,monster,actionGoal,goalPos), actionGoal, goalPos);
|
|
}
|
|
}
|
|
//AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,looker), actionGoal, looker);
|
|
}
|
|
|
|
if(aipoints_show->value)
|
|
{
|
|
VectorScale(toAvoid, 40.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
|
|
paletteRGBA_t col = {250, 250, 250, 250};
|
|
paletteRGBA_t col2 = {0, 0, 0, 250};
|
|
//FX_MakeLine(node[startNode].getPos(), node[nextNode].getPos(), col, 1);
|
|
FX_MakeLine(which_ai.getPathData().goPoint, monster.s.origin, col, 1);
|
|
FX_MakeRing(which_ai.getPathData().goPoint, 4);
|
|
FX_MakeLine(actionGoal, monster.s.origin, col2, 1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract(goalPos, monster.s.origin, actionGoal);
|
|
VectorNormalize(actionGoal);
|
|
VectorScale(actionGoal, 100.0, actionGoal);
|
|
VectorAdd(monster.s.origin, actionGoal, actionGoal);
|
|
AddActionForSequence(which_ai, monster, GetSequenceForMovement(which_ai,monster,actionGoal,actionGoal), actionGoal, actionGoal);
|
|
}
|
|
// }
|
|
}
|
|
*/
|
|
|
|
// --------------------------------------------------------------
|
|
|
|
pathidle_decision::pathidle_decision(pathidle_decision *orig)
|
|
: pathcorner_decision(orig)
|
|
{
|
|
}
|
|
|
|
void pathidle_decision::Evaluate(pathidle_decision *orig)
|
|
{
|
|
pathcorner_decision::Evaluate(orig);
|
|
}
|
|
|
|
void pathidle_decision::Read()
|
|
{
|
|
char loaded[sizeof(pathidle_decision)];
|
|
|
|
gi.ReadFromSavegame('AIHD', loaded, sizeof(pathidle_decision));
|
|
Evaluate((pathidle_decision *)loaded);
|
|
}
|
|
|
|
void pathidle_decision::Write()
|
|
{
|
|
pathidle_decision *savable;
|
|
|
|
savable = new pathidle_decision(this);
|
|
gi.AppendToSavegame('AIHD', savable, sizeof(*this));
|
|
delete savable;
|
|
}
|
|
|
|
// end
|