#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()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_timevelocity)+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. . { 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::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)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::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::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::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::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.healths.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