3134 lines
No EOL
75 KiB
C++
3134 lines
No EOL
75 KiB
C++
#include "g_local.h"
|
|
#include "ai_private.h"
|
|
#include "m_generic.h"
|
|
#include "m_ecto.h"
|
|
#include "m_meso.h"
|
|
#include "m_female.h"
|
|
#include "m_heliai.h"
|
|
#include "m_tankai.h"
|
|
#include "m_snowcatai.h"
|
|
#include "fields.h"
|
|
#include "ai_pathfinding.h"
|
|
#include "..\strings\singleplr.h"
|
|
|
|
#if _DEBUG
|
|
char *decName;
|
|
#endif
|
|
|
|
static ai_c *AICurrentlyThinking=0;
|
|
|
|
qboolean IsLevelNamed (const char * thisname)
|
|
{
|
|
if (!stricmp(thisname, level.mapname))
|
|
{
|
|
return true;
|
|
}
|
|
char altname[100];
|
|
Com_sprintf(altname, sizeof(altname), "final/%s", thisname);
|
|
if (!stricmp(thisname, level.mapname))
|
|
{
|
|
return true;
|
|
}
|
|
// fixme: kef -- eventually, want this chunk out of here. right now, though, the
|
|
//designers really need it
|
|
if (strstr(level.mapname, thisname))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (!stricmp(thisname, level_ai_name))
|
|
{ // over-ride
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void GetLevelSoundSuffix(char *putSuffixHere)
|
|
{
|
|
if (!putSuffixHere)
|
|
{
|
|
return;
|
|
}
|
|
putSuffixHere[0]=0;
|
|
switch(GetInterpCode())
|
|
{
|
|
default:
|
|
case LEVCODE_TUTORIAL:
|
|
strcpy(putSuffixHere, "tut1");
|
|
break;
|
|
case LEVCODE_UNKNOWN:
|
|
case LEVCODE_NYC_SUBWAY:
|
|
strcpy(putSuffixHere, "TSR1");
|
|
break;
|
|
case LEVCODE_NYC_SUBWAY2:
|
|
strcpy(putSuffixHere, "TSR2");
|
|
break;
|
|
case LEVCODE_AFR_TRAIN:
|
|
strcpy(putSuffixHere, "trn1");
|
|
break;
|
|
case LEVCODE_KOS_SEWER:
|
|
strcpy(putSuffixHere, "kos1");
|
|
break;
|
|
case LEVCODE_KOS_BIGGUN:
|
|
strcpy(putSuffixHere, "kos2");
|
|
break;
|
|
case LEVCODE_KOS_HANGAR:
|
|
strcpy(putSuffixHere, "kos3");
|
|
break;
|
|
case LEVCODE_SIB_CANYON:
|
|
strcpy(putSuffixHere, "sib1");
|
|
break;
|
|
case LEVCODE_SIB_BASE:
|
|
strcpy(putSuffixHere, "sib2");
|
|
break;
|
|
case LEVCODE_SIB_PLANT:
|
|
strcpy(putSuffixHere, "sib3");
|
|
break;
|
|
case LEVCODE_IRQ_TOWNA:
|
|
strcpy(putSuffixHere, "irq1a");
|
|
break;
|
|
case LEVCODE_IRQ_BUNKER:
|
|
strcpy(putSuffixHere, "irq2a");
|
|
break;
|
|
case LEVCODE_IRQ_CARGO:
|
|
strcpy(putSuffixHere, "irq3a");
|
|
break;
|
|
case LEVCODE_NYC_WARE:
|
|
strcpy(putSuffixHere, "nyc1");
|
|
break;
|
|
case LEVCODE_NYC_STEAM:
|
|
strcpy(putSuffixHere, "nyc2");
|
|
break;
|
|
case LEVCODE_NYC_STREETS:
|
|
strcpy(putSuffixHere, "nyc3");
|
|
break;
|
|
case LEVCODE_AFR_YARD:
|
|
strcpy(putSuffixHere, "sud1");
|
|
break;
|
|
case LEVCODE_AFR_HOUSE:
|
|
strcpy(putSuffixHere, "sud2");
|
|
break;
|
|
case LEVCODE_AFR_FACT:
|
|
strcpy(putSuffixHere, "sud3");
|
|
break;
|
|
case LEVCODE_TOK_STREET:
|
|
strcpy(putSuffixHere, "jpn1");
|
|
break;
|
|
case LEVCODE_TOK_OFFICE:
|
|
strcpy(putSuffixHere, "jpn2");
|
|
break;
|
|
case LEVCODE_TOK_PENT:
|
|
strcpy(putSuffixHere, "jpn3");
|
|
break;
|
|
case LEVCODE_IRQ_STREETS:
|
|
strcpy(putSuffixHere, "irq1b");
|
|
break;
|
|
case LEVCODE_IRQ_FORT:
|
|
strcpy(putSuffixHere, "irq2b");
|
|
break;
|
|
case LEVCODE_IRQ_OIL:
|
|
strcpy(putSuffixHere, "irq3b");
|
|
break;
|
|
case LEVCODE_CAS_1:
|
|
strcpy(putSuffixHere, "ger1");
|
|
break;
|
|
case LEVCODE_CAS_2:
|
|
strcpy(putSuffixHere, "ger2");
|
|
break;
|
|
case LEVCODE_CAS_3:
|
|
strcpy(putSuffixHere, "ger3");
|
|
break;
|
|
case LEVCODE_CAS_4:
|
|
strcpy(putSuffixHere, "ger4");
|
|
break;
|
|
case LEVCODE_ARM_1:
|
|
strcpy(putSuffixHere, "arm1");
|
|
break;
|
|
case LEVCODE_ARM_2:
|
|
strcpy(putSuffixHere, "arm2");
|
|
break;
|
|
case LEVCODE_ARM_3:
|
|
strcpy(putSuffixHere, "arm3");
|
|
break;
|
|
}
|
|
}
|
|
|
|
lev_interp_code GetInterpCode(void)
|
|
{
|
|
if (IsLevelNamed("tut1"))
|
|
{
|
|
return LEVCODE_TUTORIAL;
|
|
}
|
|
if (IsLevelNamed("tsr1"))
|
|
{
|
|
return LEVCODE_NYC_SUBWAY;
|
|
}
|
|
if (IsLevelNamed("tsr2"))
|
|
{
|
|
return LEVCODE_NYC_SUBWAY2;
|
|
}
|
|
if (IsLevelNamed("arm1"))
|
|
{
|
|
return LEVCODE_ARM_1;
|
|
}
|
|
if (IsLevelNamed("arm2"))
|
|
{
|
|
return LEVCODE_ARM_2;
|
|
}
|
|
if (IsLevelNamed("arm3"))
|
|
{
|
|
return LEVCODE_ARM_3;
|
|
}
|
|
if (IsLevelNamed("trn1"))
|
|
{
|
|
return LEVCODE_AFR_TRAIN;
|
|
}
|
|
if (IsLevelNamed("sib2"))
|
|
{
|
|
return LEVCODE_SIB_BASE;
|
|
}
|
|
if (IsLevelNamed("kos1"))
|
|
{
|
|
return LEVCODE_KOS_SEWER;
|
|
}
|
|
if (IsLevelNamed("kos2"))
|
|
{
|
|
return LEVCODE_KOS_BIGGUN;
|
|
}
|
|
if (IsLevelNamed("kos3"))
|
|
{
|
|
return LEVCODE_KOS_HANGAR;
|
|
}
|
|
if (IsLevelNamed("sib1"))
|
|
{
|
|
return LEVCODE_SIB_CANYON;
|
|
}
|
|
if (IsLevelNamed("sib3"))
|
|
{
|
|
return LEVCODE_SIB_PLANT;
|
|
}
|
|
if (IsLevelNamed("irq1a"))
|
|
{
|
|
return LEVCODE_IRQ_TOWNA;
|
|
}
|
|
if (IsLevelNamed("irq2a"))
|
|
{
|
|
return LEVCODE_IRQ_BUNKER;
|
|
}
|
|
if (IsLevelNamed("irq3a"))
|
|
{
|
|
return LEVCODE_IRQ_CARGO;
|
|
}
|
|
if (IsLevelNamed("nyc1"))
|
|
{
|
|
return LEVCODE_NYC_WARE;
|
|
}
|
|
if (IsLevelNamed("nyc2"))
|
|
{
|
|
return LEVCODE_NYC_STEAM;
|
|
}
|
|
if (IsLevelNamed("nyc3"))
|
|
{
|
|
return LEVCODE_NYC_STREETS;
|
|
}
|
|
if (IsLevelNamed("sud1"))
|
|
{
|
|
return LEVCODE_AFR_YARD;
|
|
}
|
|
if (IsLevelNamed("sud2"))
|
|
{
|
|
return LEVCODE_AFR_HOUSE;
|
|
}
|
|
if (IsLevelNamed("sud3"))
|
|
{
|
|
return LEVCODE_AFR_FACT;
|
|
}
|
|
if (IsLevelNamed("jpn1"))
|
|
{
|
|
return LEVCODE_TOK_STREET;
|
|
}
|
|
if (IsLevelNamed("jpn2"))
|
|
{
|
|
return LEVCODE_TOK_OFFICE;
|
|
}
|
|
if (IsLevelNamed("jpn3"))
|
|
{
|
|
return LEVCODE_TOK_PENT;
|
|
}
|
|
if (IsLevelNamed("irq1b"))
|
|
{
|
|
return LEVCODE_IRQ_STREETS;
|
|
}
|
|
if (IsLevelNamed("irq2b"))
|
|
{
|
|
return LEVCODE_IRQ_FORT;
|
|
}
|
|
if (IsLevelNamed("irq3b"))
|
|
{
|
|
return LEVCODE_IRQ_OIL;
|
|
}
|
|
if (IsLevelNamed("ger1"))
|
|
{
|
|
return LEVCODE_CAS_1;
|
|
}
|
|
if (IsLevelNamed("ger2"))
|
|
{
|
|
return LEVCODE_CAS_2;
|
|
}
|
|
if (IsLevelNamed("ger3"))
|
|
{
|
|
return LEVCODE_CAS_3;
|
|
}
|
|
if (IsLevelNamed("ger4"))
|
|
{
|
|
return LEVCODE_CAS_4;
|
|
}
|
|
return LEVCODE_UNKNOWN;
|
|
}
|
|
|
|
//rubbishy save/load stuff first, to make a good first impression :(
|
|
ai_public_c *ai_public_c::NewClassForCode(int code)
|
|
{
|
|
switch (code)
|
|
{
|
|
default:
|
|
case AI_PUBLIC:
|
|
case AI_BASE:
|
|
case AI_GENERIC:
|
|
case AI_GENERIC_NPC:
|
|
case AI_GENERIC_ENEMY_NPC:
|
|
case AI_GENERIC_DOG:
|
|
case AI_GENERIC_MERC:
|
|
gi.dprintf("ERROR: invalid ai class code: %d\n",code);
|
|
return new ai_c();
|
|
|
|
case AI_GENERIC_COW:
|
|
return new cow_ai();
|
|
/* case AI_GENERIC_MERC_GRUNT:
|
|
return new merc_grunt_ai();
|
|
case AI_GENERIC_MERC_DEMO:
|
|
return new merc_demo_ai();
|
|
case AI_GENERIC_MERC_SNIPER:
|
|
return new merc_sniper_ai();
|
|
case AI_GENERIC_MERC_MEDIC:
|
|
return new merc_medic_ai();
|
|
case AI_GENERIC_MERC_HEAVY:
|
|
return new merc_heavy_ai();
|
|
case AI_GENERIC_MERC_TECH:
|
|
return new merc_tech_ai();
|
|
*/
|
|
case AI_HELI:
|
|
return new generic_ghoul_heli_ai();
|
|
case AI_TANK:
|
|
return new generic_ghoul_tank_ai();
|
|
case AI_SNOWCAT:
|
|
return new generic_ghoul_snowcat_ai();
|
|
|
|
case AI_DOG_HUSKY:
|
|
return new dog_husky_ai();
|
|
case AI_DOG_ROTTWEILER:
|
|
return new dog_rottweiler_ai();
|
|
case AI_BLOWNPART:
|
|
return new blownpart_ai();
|
|
case AI_PLAYERCORPSE:
|
|
return new playercorpse_ai();
|
|
|
|
case AI_ECTO_SKINLEADER:
|
|
return new skinleader_ai();
|
|
case AI_ECTO_STOCKBROKER:
|
|
return new stockbroker_ai();
|
|
case AI_ECTO_DCTOURIST:
|
|
return new tourist_ai();
|
|
case AI_ECTO_NYCBUM:
|
|
return new bum_ai();
|
|
case AI_ECTO_COLPRISONER1:
|
|
return new prisoner1_ai();
|
|
case AI_ECTO_COLPRISONER2:
|
|
return new prisoner2_ai();
|
|
case AI_ECTO_IRAQCITIZEN:
|
|
return new irqcitizen_ai();
|
|
case AI_ECTO_UGNCOMMANDER:
|
|
return new commander_ai();
|
|
case AI_ECTO_SERBOFFICER:
|
|
return new serbofficer_ai();
|
|
case AI_ECTO_KOSREFUGEE:
|
|
return new kosrefugee_ai();
|
|
case AI_ECTO_IRAQOFFICER:
|
|
return new iraqofficer_ai();
|
|
case AI_ECTO_FACTORY:
|
|
return new factoryworker_ai();
|
|
case AI_ECTO_CHEMIST:
|
|
return new chemist_ai();
|
|
case AI_ECTO_SIBSUIT:
|
|
return new sibsuit_ai();
|
|
case AI_ECTO_SIBSCIENCE:
|
|
return new sibscientist_ai();
|
|
case AI_ECTO_PUNK2:
|
|
return new zitpunk_ai();
|
|
|
|
case AI_MESO_JOHN:
|
|
return new john_ai();
|
|
case AI_MESO_JOHN_SNOW:
|
|
return new john_snow_ai();
|
|
case AI_MESO_JOHN_DESERT:
|
|
return new john_desert_ai();
|
|
case AI_MESO_HAWK:
|
|
return new hawk_ai();
|
|
case AI_MESO_HURTHAWK:
|
|
return new hurthawk_ai();
|
|
case AI_MESO_SAM:
|
|
return new sam_ai();
|
|
case AI_MESO_IRAQWORKER:
|
|
return new irqworker_ai();
|
|
case AI_MESO_NYCPUNK:
|
|
return new nypunk_ai();
|
|
case AI_MESO_AMU:
|
|
return new amu_ai();
|
|
case AI_MESO_RAIDERBOSS:
|
|
return new raiderboss_ai();
|
|
case AI_MESO_RAIDERBOSS2:
|
|
return new raiderboss2_ai();
|
|
case AI_MESO_IRAQSOLDIER1:
|
|
return new irqsoldier1_ai();
|
|
case AI_MESO_IRAQSOLDIER2:
|
|
return new irqsoldier2_ai();
|
|
case AI_MESO_IRAQSOLDIER2B:
|
|
return new irqsoldier2b_ai();
|
|
case AI_MESO_IRAQREPGUARD:
|
|
return new irqrepgd_ai();
|
|
case AI_MESO_IRAQREPGUARDB:
|
|
return new irqrepgdb_ai();
|
|
case AI_MESO_IRAQPOLICE:
|
|
return new irqpolice_ai();
|
|
case AI_MESO_IRAQCOMMANDER:
|
|
return new irqcommander_ai();
|
|
case AI_MESO_IRAQBRUTEA:
|
|
return new irqbrutea_ai();
|
|
case AI_MESO_IRAQBRUTEB:
|
|
return new irqbruteb_ai();
|
|
case AI_MESO_IRAQBODYGUARD:
|
|
return new irqbodyguard_ai();
|
|
case AI_MESO_IRAQROCKET:
|
|
return new irqrocket_ai();
|
|
case AI_MESO_IRAQSADDAM:
|
|
return new irqsaddam_ai();
|
|
case AI_MESO_IRAQMAN2:
|
|
return new irqman2_ai();
|
|
case AI_MESO_UGNSNIPER:
|
|
return new ugsniper_ai();
|
|
case AI_MESO_UGNBRUTE:
|
|
return new ugbrute_ai();
|
|
case AI_MESO_UGNROCKET:
|
|
return new ugrocket_ai();
|
|
case AI_MESO_UGNSOLDIER1:
|
|
return new ugsoldier1_ai();
|
|
case AI_MESO_UGNSOLDIER1B:
|
|
return new ugsoldier1b_ai();
|
|
case AI_MESO_UGNSOLDIER2:
|
|
return new ugsoldier2_ai();
|
|
case AI_MESO_UGNSOLDIER3:
|
|
return new ugsoldier3_ai();
|
|
case AI_MESO_NYCSWATGUY:
|
|
return new nyswatguy_ai();
|
|
case AI_MESO_NYCSWATLEADER:
|
|
return new nyswatleader_ai();
|
|
case AI_MESO_RAIDER1:
|
|
return new raider1_ai();
|
|
case AI_MESO_RAIDER2A:
|
|
return new raider2_ai();
|
|
case AI_MESO_RAIDER2B:
|
|
return new raider2b_ai();
|
|
case AI_MESO_RAIDERBRUTE:
|
|
return new raiderbrute_ai();
|
|
case AI_MESO_RAIDERROCKET:
|
|
return new raiderrocket_ai();
|
|
case AI_MESO_SIBTROOPER2:
|
|
return new sibtrooper2_ai();
|
|
case AI_MESO_SIBCLEANSUIT:
|
|
return new sibcleansuit_ai();
|
|
case AI_MESO_SERBGRUNT1:
|
|
return new serbgrunt1_ai();
|
|
case AI_MESO_SERBGRUNT2:
|
|
return new serbgrunt2_ai();
|
|
case AI_MESO_SERBGRUNT3:
|
|
return new serbgrunt3_ai();
|
|
case AI_MESO_SERBSNIPER1A:
|
|
return new serbsniper1a_ai();
|
|
case AI_MESO_SERBSNIPER1B:
|
|
return new serbsniper1b_ai();
|
|
case AI_MESO_SERBCOMTROOP:
|
|
return new serbcomtroop_ai();
|
|
case AI_MESO_SERBBRUTE1A:
|
|
return new serbbrute1a_ai();
|
|
case AI_MESO_SERBBRUTE1B:
|
|
return new serbbrute1b_ai();
|
|
case AI_MESO_SERBMECHANIC:
|
|
return new serbmechanic_ai();
|
|
case AI_MESO_KOSREBEL:
|
|
return new kosrebel_ai();
|
|
case AI_MESO_KOSKLAGUY:
|
|
return new kosklaguy_ai();
|
|
case AI_MESO_SKINHEAD1:
|
|
return new skinhead1_ai();
|
|
case AI_MESO_SKINHEAD2A:
|
|
return new skinhead2a_ai();
|
|
case AI_MESO_SKINHEAD2B:
|
|
return new skinhead2b_ai();
|
|
case AI_MESO_SKINHEADBOSS:
|
|
return new skinheadboss_ai();
|
|
case AI_MESO_MALEPOLITICIAN:
|
|
return new malepolitician_ai();
|
|
case AI_MESO_TOKHENCH1:
|
|
return new tokhench1_ai();
|
|
case AI_MESO_TOKHENCH2:
|
|
return new tokhench2_ai();
|
|
case AI_MESO_TOKKILLER:
|
|
return new tokkiller_ai();
|
|
case AI_MESO_TOKNINJA:
|
|
return new tokninja_ai();
|
|
case AI_MESO_TOKBRUTE:
|
|
return new tokbrute_ai();
|
|
case AI_MESO_TOKMALEHOSTAGE:
|
|
return new tokmalehostage_ai();
|
|
case AI_MESO_JAPANSUIT:
|
|
return new japansuit_ai();
|
|
case AI_MESO_SIBTROOPER1A:
|
|
return new sibtrooper1a_ai();
|
|
case AI_MESO_SIBTROOPER1B:
|
|
return new sibtrooper1b_ai();
|
|
case AI_MESO_SIBGUARD:
|
|
return new sibguard_ai();
|
|
case AI_MESO_SIBGUARD3:
|
|
return new sibguard3_ai();
|
|
case AI_MESO_SIBGUARD4:
|
|
return new sibguard4_ai();
|
|
case AI_MESO_SIBMECH:
|
|
return new sibmech_ai();
|
|
|
|
case AI_FEMALE_SKINCHICK:
|
|
return new skinchick_ai();
|
|
case AI_FEMALE_NYWOMAN:
|
|
return new nycwoman_ai();
|
|
case AI_FEMALE_SIBGUARD:
|
|
return new fsibguard_ai();
|
|
case AI_FEMALE_SIBSCIENCE:
|
|
return new fsibscience_ai();
|
|
case AI_FEMALE_IRQWOMAN1:
|
|
return new irqwoman1_ai();
|
|
case AI_FEMALE_IRQWOMAN2:
|
|
return new irqwoman2_ai();
|
|
case AI_FEMALE_TOKWOMAN1:
|
|
return new tokwoman1_ai();
|
|
case AI_FEMALE_TOKWOMAN2:
|
|
return new tokwoman2_ai();
|
|
case AI_FEMALE_TOKASSASSIN:
|
|
return new tokassassin_ai();
|
|
case AI_FEMALE_RAIDER:
|
|
return new fraider_ai();
|
|
case AI_FEMALE_TAYLOR:
|
|
return new taylor_ai();
|
|
}
|
|
}
|
|
|
|
ai_public_c::ai_public_c(ai_public_c *orig)
|
|
{
|
|
VectorCopy(orig->velocity, velocity);
|
|
VectorCopy(orig->ideal_angles, ideal_angles);
|
|
}
|
|
|
|
void ai_public_c::Evaluate(ai_public_c *orig)
|
|
{
|
|
VectorCopy(orig->velocity, velocity);
|
|
VectorCopy(orig->ideal_angles, ideal_angles);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
ai_c::ai_c(ai_c *orig)
|
|
: ai_public_c(orig)
|
|
{
|
|
int i;
|
|
|
|
lFootPos = orig->lFootPos;
|
|
rFootPos = orig->rFootPos;
|
|
|
|
done_firstframe = orig->done_firstframe;
|
|
allowActionChangeTime = orig->allowActionChangeTime;
|
|
|
|
for(i = 0; i < BBOX_PRESET_NUMBER; i++)
|
|
{
|
|
VectorCopy(orig->preset_mins[i], preset_mins[i]);
|
|
VectorCopy(orig->preset_maxs[i], preset_maxs[i]);
|
|
}
|
|
|
|
current_bbox = orig->current_bbox;
|
|
ground = orig->ground;
|
|
hasHadTarget = orig->hasHadTarget;
|
|
lastTargetTime = orig->lastTargetTime;
|
|
firstTargetTime = orig->firstTargetTime;
|
|
abusedByTeam = orig->abusedByTeam;
|
|
lastNonTargetTime = orig->lastNonTargetTime;
|
|
|
|
nextWakeReactTime = orig->nextWakeReactTime;
|
|
|
|
nodeData = orig->nodeData;
|
|
*(int *)&ent = GetEdictNum(orig->ent);
|
|
*(int *)&curTarget = GetEdictNum(orig->curTarget);
|
|
|
|
current_action.MakeIndex(orig->current_action);
|
|
recycle_action.MakeIndex(orig->recycle_action);
|
|
|
|
linkcount = orig->linkcount;
|
|
isActive = orig->isActive;
|
|
isStartleable = orig->isStartleable;
|
|
m_bHostage = orig->m_bHostage;
|
|
m_bSpecialBuddy = orig->m_bSpecialBuddy;
|
|
m_bConcentratingOnPlayer = orig->m_bConcentratingOnPlayer;
|
|
|
|
VectorCopy(orig->requestedMoveDir, requestedMoveDir);
|
|
|
|
scale = orig->scale;
|
|
sense_mask = orig->sense_mask;
|
|
jumpdistance = orig->jumpdistance;
|
|
jumpheight = orig->jumpheight;
|
|
fallheight = orig->fallheight;
|
|
stepheight = orig->stepheight;
|
|
move_mask = orig->move_mask;
|
|
|
|
worldpriority = orig->worldpriority;
|
|
lastCheckTime = orig->lastCheckTime;
|
|
attentionLevel = orig->attentionLevel;
|
|
watchState = orig->watchState;
|
|
|
|
miscFlags = orig->miscFlags;
|
|
|
|
lastCorpseCheck = orig->lastCorpseCheck;
|
|
|
|
rank = orig->rank;
|
|
|
|
mySkills = orig->mySkills;
|
|
|
|
body.MakeIndex(orig->body);
|
|
MyGhoulObj.MakeIndex(orig->MyGhoulObj);
|
|
|
|
VectorCopy(orig->aim_angles, aim_angles);
|
|
VectorCopy(orig->look_angles, look_angles);
|
|
}
|
|
|
|
void ai_c::Evaluate(ai_c *orig)
|
|
{
|
|
int i;
|
|
|
|
lFootPos = orig->lFootPos;
|
|
rFootPos = orig->rFootPos;
|
|
|
|
done_firstframe = orig->done_firstframe;
|
|
allowActionChangeTime = orig->allowActionChangeTime;
|
|
|
|
for(i = 0; i < BBOX_PRESET_NUMBER; i++)
|
|
{
|
|
VectorCopy(orig->preset_mins[i], preset_mins[i]);
|
|
VectorCopy(orig->preset_maxs[i], preset_maxs[i]);
|
|
}
|
|
|
|
current_bbox = orig->current_bbox;
|
|
ground = orig->ground;
|
|
hasHadTarget = orig->hasHadTarget;
|
|
lastTargetTime = orig->lastTargetTime;
|
|
firstTargetTime = orig->firstTargetTime;
|
|
abusedByTeam = orig->abusedByTeam;
|
|
lastNonTargetTime = orig->lastNonTargetTime;
|
|
|
|
nextWakeReactTime = orig->nextWakeReactTime;
|
|
|
|
nodeData = orig->nodeData;
|
|
|
|
ent = GetEdictPtr((int)orig->ent);
|
|
curTarget = GetEdictPtr((int)orig->curTarget);
|
|
|
|
current_action.MakePtr(*(int *)&orig->current_action);
|
|
recycle_action.MakePtr(*(int *)&orig->recycle_action);
|
|
|
|
linkcount = orig->linkcount;
|
|
isActive = orig->isActive;
|
|
isStartleable = orig->isStartleable;
|
|
m_bHostage = orig->m_bHostage;
|
|
m_bSpecialBuddy = orig->m_bSpecialBuddy;
|
|
m_bConcentratingOnPlayer = orig->m_bConcentratingOnPlayer;
|
|
|
|
VectorCopy(orig->requestedMoveDir, requestedMoveDir);
|
|
|
|
scale = orig->scale;
|
|
sense_mask = orig->sense_mask;
|
|
jumpdistance = orig->jumpdistance;
|
|
jumpheight = orig->jumpheight;
|
|
fallheight = orig->fallheight;
|
|
stepheight = orig->stepheight;
|
|
move_mask = orig->move_mask;
|
|
|
|
worldpriority = orig->worldpriority;
|
|
lastCheckTime = orig->lastCheckTime;
|
|
attentionLevel = orig->attentionLevel;
|
|
watchState = orig->watchState;
|
|
|
|
miscFlags = orig->miscFlags;
|
|
|
|
lastCorpseCheck = orig->lastCorpseCheck;
|
|
|
|
rank = orig->rank;
|
|
mySkills = orig->mySkills;
|
|
|
|
body.MakePtr(*(int *)&orig->body);
|
|
MyGhoulObj.MakePtr(*(int *)&orig->MyGhoulObj);
|
|
|
|
VectorCopy(orig->aim_angles, aim_angles);
|
|
VectorCopy(orig->look_angles, look_angles);
|
|
|
|
ai_public_c::Evaluate(orig);
|
|
}
|
|
|
|
void ai_c::Read()
|
|
{
|
|
int i, count;
|
|
int *index_list;
|
|
char loaded[sizeof(ai_c)];
|
|
|
|
gi.ReadFromSavegame('AIAI', loaded, AI_SAVE_SIZE);
|
|
Evaluate((ai_c *)loaded);
|
|
|
|
gi.ReadFromSavegame('AISL', &count, sizeof(count));
|
|
if(count)
|
|
{
|
|
index_list = new int [count];
|
|
gi.ReadFromSavegame('AISE', index_list, count * sizeof(int));
|
|
for(i = 0; i < count; i++)
|
|
{
|
|
senses.push_back(senseL.GetPointerFromIndex(index_list[i]));
|
|
}
|
|
delete [] index_list;
|
|
}
|
|
|
|
gi.ReadFromSavegame('AIDL', &count, sizeof(count));
|
|
if(count)
|
|
{
|
|
index_list = new int [count];
|
|
gi.ReadFromSavegame('AIDE', index_list, count * sizeof(int));
|
|
for(i = 0; i < count; i++)
|
|
{
|
|
decisions.push_back(decisionL.GetPointerFromIndex(index_list[i]));
|
|
}
|
|
delete [] index_list;
|
|
}
|
|
|
|
gi.ReadFromSavegame('AIAL', &count, sizeof(count));
|
|
if(count)
|
|
{
|
|
index_list = new int [count];
|
|
gi.ReadFromSavegame('AIAE', index_list, count * sizeof(int));
|
|
for(i = 0; i < count; i++)
|
|
{
|
|
actions.push_back(actionL.GetPointerFromIndex(index_list[i]));
|
|
}
|
|
delete [] index_list;
|
|
}
|
|
|
|
if(ent->ai)
|
|
{
|
|
ent->ai = this;
|
|
}
|
|
}
|
|
|
|
void ai_c::Write()
|
|
{
|
|
ai_c *savable;
|
|
list<action_c_ptr>::iterator ia;
|
|
list<sense_c_ptr>::iterator is;
|
|
list<decision_c_ptr>::iterator id;
|
|
int count, i;
|
|
int *index_list;
|
|
|
|
savable = new ai_c(this);
|
|
gi.AppendToSavegame('AIAI', savable, AI_SAVE_SIZE);
|
|
savable->current_action = NULL;
|
|
savable->recycle_action = NULL;
|
|
savable->body = NULL;
|
|
savable->MyGhoulObj = NULL;
|
|
delete savable;
|
|
|
|
count = senses.size();
|
|
gi.AppendToSavegame('AISL', &count, sizeof(count));
|
|
if(count)
|
|
{
|
|
index_list = new int [count];
|
|
for (is = senses.begin(), i = 0; is != senses.end(); is++, i++)
|
|
{
|
|
index_list[i] = (*is).GetIndex();
|
|
}
|
|
gi.AppendToSavegame('AISE', index_list, count * sizeof(int));
|
|
delete index_list;
|
|
}
|
|
|
|
count = decisions.size();
|
|
gi.AppendToSavegame('AIDL', &count, sizeof(count));
|
|
if(count)
|
|
{
|
|
index_list = new int [count];
|
|
for (id = decisions.begin(), i = 0; id != decisions.end(); id++, i++)
|
|
{
|
|
index_list[i] = (*id).GetIndex();
|
|
}
|
|
gi.AppendToSavegame('AIDE', index_list, count * sizeof(int));
|
|
delete index_list;
|
|
}
|
|
|
|
count = actions.size();
|
|
gi.AppendToSavegame('AIAL', &count, sizeof(count));
|
|
if(count)
|
|
{
|
|
index_list = new int [count];
|
|
for (ia = actions.begin(), i = 0; ia != actions.end(); ia++, i++)
|
|
{
|
|
index_list[i] = (*ia).GetIndex();
|
|
}
|
|
gi.AppendToSavegame('AIAE', index_list, count * sizeof(int));
|
|
delete index_list;
|
|
}
|
|
}
|
|
|
|
void ai_c::AddBody(edict_t *monster)
|
|
{
|
|
if (!monster)
|
|
{
|
|
return;
|
|
}
|
|
// body = new bodyhuman_c();
|
|
}
|
|
|
|
void ai_c::UseMonster(edict_t *user)
|
|
{
|
|
if (ent && OnSameTeam(ent, user))
|
|
{
|
|
SetAbusedByTeam(true);
|
|
}
|
|
if (body)
|
|
{
|
|
body->UseMonster(*ent, user);
|
|
}
|
|
}
|
|
|
|
void ai_c::TouchMonster(edict_t *user)
|
|
{
|
|
if (body)
|
|
{
|
|
body->TouchMonster(*ent, user);
|
|
}
|
|
}
|
|
|
|
void ai_c::Init(edict_t *monster, char *ghoulname, char *subname)
|
|
{
|
|
if(body)
|
|
{
|
|
body->SetOwner(monster);
|
|
}
|
|
}
|
|
|
|
//ACHTUNG! need to have rf_ghoul set on edict by now!
|
|
ai_c::ai_c(edict_t *monster)
|
|
{
|
|
int i;
|
|
|
|
VectorClear(velocity);
|
|
VectorClear(ideal_angles);
|
|
|
|
for(i = 0; i < BBOX_PRESET_NUMBER; i++)
|
|
{
|
|
VectorClear(preset_mins[i]);
|
|
VectorClear(preset_maxs[i]);
|
|
}
|
|
|
|
lFootPos = 0;
|
|
rFootPos = 0;
|
|
done_firstframe = false;
|
|
allowActionChangeTime = 0.0F;
|
|
|
|
current_bbox = BBOX_PRESET_NUMBER;
|
|
ground = 0.0F;
|
|
hasHadTarget = false;
|
|
lastTargetTime = 0.0F;
|
|
firstTargetTime = 0.0F;
|
|
lastNonTargetTime = 0.0F;
|
|
abusedByTeam = false;
|
|
|
|
nextWakeReactTime = -20.0F;
|
|
|
|
//constructors? Who needs constructors? I'm really regretting making this a plain vanilla struct
|
|
VectorClear(nodeData.curSpot);
|
|
VectorClear(nodeData.goPoint);
|
|
nodeData.curNode = 0;
|
|
nodeData.curRegion = 0;
|
|
nodeData.nextNode = 0;
|
|
nodeData.finalNode = 0;
|
|
nodeData.blocked = 0;
|
|
nodeData.lastNode = 0;
|
|
nodeData.lastNode2 = 0;
|
|
nodeData.lastNode3 = 0;
|
|
nodeData.backingUp = 0;
|
|
nodeData.approaching = 0;
|
|
nodeData.corner1 = -1;
|
|
nodeData.corner2 = -1;
|
|
nodeData.corner3 = -1;
|
|
nodeData.corner4 = -1;
|
|
VectorClear(nodeData.lastDir);
|
|
nodeData.lastDirSetTime = 0;
|
|
// End of nodeData setup
|
|
|
|
ent = monster;
|
|
curTarget = NULL;
|
|
current_action = NULL;
|
|
recycle_action = NULL;
|
|
linkcount = 0;
|
|
isActive = false;
|
|
isStartleable = true;
|
|
m_bHostage = false;
|
|
m_bSpecialBuddy = false;
|
|
m_bConcentratingOnPlayer = false;
|
|
VectorClear(requestedMoveDir);
|
|
scale = 1.0F;
|
|
sense_mask = alarm_mask|sight_mask|sound_mask;
|
|
jumpdistance = 100.0F;
|
|
jumpheight = 50.0F;
|
|
fallheight = 1000000.0F;
|
|
stepheight = 24.0F;
|
|
move_mask = jump_movemask|fall_movemask|step_movemask;
|
|
worldpriority = PRIORITY_LOW;
|
|
lastCheckTime = 0;
|
|
attentionLevel = ATTENTION_IDLE;
|
|
watchState = 0;
|
|
lastCorpseCheck = 0;
|
|
miscFlags = 0;
|
|
rank = 0;//grunt
|
|
body = NULL;
|
|
MyGhoulObj = NULL;
|
|
|
|
VectorClear(aim_angles);
|
|
if (monster)
|
|
{
|
|
SetLookAngles(monster->s.angles);
|
|
}
|
|
//eek, no ent yet, set me to trash
|
|
else
|
|
{
|
|
SetLookVector(vec3_up);
|
|
}
|
|
}
|
|
|
|
ai_c::~ai_c(void)
|
|
{
|
|
list<action_c_ptr>::iterator ia;
|
|
list<sense_c_ptr>::iterator is;
|
|
list<decision_c_ptr>::iterator id;
|
|
|
|
if (body)
|
|
{
|
|
body.Destroy();
|
|
}
|
|
|
|
// lists will clean up their nodes during their destruction
|
|
// huh? should have list-eating separated out
|
|
for (is=senses.begin();is != senses.end();is++)
|
|
{
|
|
(*is).Destroy();
|
|
}
|
|
|
|
for (ia=actions.begin();ia!=actions.end();ia++)
|
|
{
|
|
(*ia).Destroy();
|
|
}
|
|
|
|
for (id=decisions.begin();id!=decisions.end();id++)
|
|
{
|
|
(*id).Destroy();
|
|
}
|
|
|
|
if (current_action)
|
|
{
|
|
current_action.Destroy();
|
|
}
|
|
|
|
if (recycle_action)
|
|
{
|
|
recycle_action.Destroy();
|
|
}
|
|
}
|
|
|
|
float ai_c::GetAimConeDegrees(edict_t &monster, bbox_preset bbox)
|
|
{
|
|
//if i'm crouching or prone, lemme aim wider than if i'm standing.
|
|
if (bbox==BBOX_PRESET_CROUCH||bbox==BBOX_PRESET_PRONE)
|
|
{
|
|
return 45.0;
|
|
}
|
|
return 20.0;
|
|
}
|
|
|
|
float ai_c::GetAimDeviationDegrees(edict_t &monster)
|
|
{
|
|
CRadiusContent rad(monster.s.origin, 200, 1, 0);
|
|
//got pals around, be careful.
|
|
if (rad.getNumFound())
|
|
{
|
|
return 5.0/(float)rad.getNumFound();
|
|
}
|
|
//nobody's around, can be sloppy
|
|
return 20.0;
|
|
}
|
|
|
|
qboolean ai_c::IsFirstFrameTime(void)
|
|
{
|
|
return !done_firstframe;
|
|
}
|
|
|
|
void ai_c::FirstFrame(edict_t *monster)
|
|
{
|
|
done_firstframe = true;
|
|
}
|
|
|
|
bool ai_c::AmIAsGoodAsDead()
|
|
{
|
|
bodyorganic_c *body = (bodyorganic_c*)GetBody();
|
|
|
|
if (body)
|
|
{
|
|
return body->IsNextShotGonnaKillMe();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
qboolean ai_c::IsArmed(edict_t &monster)
|
|
{
|
|
if (body)
|
|
{
|
|
return (body->GetBestWeapon(monster)!=ATK_NOTHING);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
qboolean ai_c::SafeToRemove(edict_t &monster)
|
|
{
|
|
list<action_c_ptr>::iterator ia;
|
|
list<sense_c_ptr>::iterator is;
|
|
list<decision_c_ptr>::iterator id;
|
|
|
|
// go thru all lists, make sure everything is good to go
|
|
|
|
// go thru senses, make sure everything is good to go
|
|
for (is=senses.begin();is != senses.end();is++)
|
|
{
|
|
if (!(*is)->SafeToRemove(monster))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// go thru actions, make sure everything is good to go
|
|
for (ia=actions.begin();ia!=actions.end();ia++)
|
|
{
|
|
if (!(*ia)->SafeToRemove(monster))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// go thru decisions, make sure everything is good to go
|
|
for (id=decisions.begin();id!=decisions.end();id++)
|
|
{
|
|
if (!(*id)->SafeToRemove(monster))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// check my current action, make sure everything is good to go
|
|
if (current_action)
|
|
{
|
|
if (!current_action->SafeToRemove(monster))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//pay no attention to recycle_action (it's not really there)
|
|
|
|
return true;//everything returned ok for removal
|
|
}
|
|
|
|
void ai_c::NewSense(sense_c *new_sense, edict_t *monster)
|
|
{
|
|
if (monster->deadflag == DEAD_DEAD)
|
|
{
|
|
//just in case the new sense is in the pointer class list already:
|
|
sense_c_ptr bad_sense = new_sense;
|
|
bad_sense.Destroy();
|
|
}
|
|
else
|
|
{
|
|
assert(senses.size()<30);
|
|
senses.push_back(new_sense);
|
|
}
|
|
}
|
|
|
|
void ai_c::NewAction(action_c *new_action, edict_t *monster, qboolean activatenow)
|
|
{
|
|
if (activatenow && !isActive)
|
|
{
|
|
Activate(*monster);
|
|
}
|
|
/* if (monster->deadflag == DEAD_DEAD)
|
|
{
|
|
//just in case the new action is in the pointer class list already:
|
|
action_c_ptr bad_action = new_action;
|
|
bad_action.Destroy();
|
|
}
|
|
else
|
|
{
|
|
*/ actions.push_back(new_action);
|
|
// }
|
|
}
|
|
|
|
void ai_c::NewNextAction(action_c *new_action, edict_t *monster, qboolean activatenow)
|
|
{
|
|
if (activatenow && !isActive)
|
|
{
|
|
Activate(*monster);
|
|
}
|
|
/* if (monster->deadflag == DEAD_DEAD)
|
|
{
|
|
//just in case the new action is in the pointer class list already:
|
|
action_c_ptr bad_action = new_action;
|
|
bad_action.Destroy();
|
|
}
|
|
else
|
|
{
|
|
*/ actions.push_front(new_action);
|
|
// }
|
|
}
|
|
|
|
|
|
void ai_c::NewDecision(decision_c *new_decision, edict_t *monster)
|
|
{
|
|
//eek!script stuff expects this decision to stick around! should be safe to allow it--death action shouldn't ever finish!
|
|
// if (monster->deadflag == DEAD_DEAD)
|
|
// {
|
|
// //just in case the new decision is in the pointer class list already:
|
|
// decision_c_ptr bad_decision = new_decision;
|
|
// bad_decision.Destroy();
|
|
// }
|
|
// else
|
|
// {
|
|
decisions.push_back(new_decision);
|
|
// }
|
|
}
|
|
|
|
void ai_c::NewCurrentAction(action_c *new_action, edict_t &monster)
|
|
{
|
|
if (current_action)
|
|
{
|
|
current_action->SetInterrupted(true);
|
|
actions.push_front(current_action);
|
|
}
|
|
|
|
current_action = new_action;
|
|
current_action->BeginAction(*this,monster);
|
|
}
|
|
|
|
void ai_c::NewDeathArmorAction(edict_t &monster)
|
|
{
|
|
NewCurrentAction( DeathArmorAction( NULL, NULL, NULL,
|
|
monster, NULL, NULL,
|
|
0, vec3_origin),
|
|
monster);
|
|
}
|
|
|
|
void ai_c::NextAction(edict_t &monster)
|
|
{
|
|
list<action_c_ptr>::iterator ia;
|
|
|
|
if (current_action)
|
|
{
|
|
//save whatever i was doing to be recycled (cuts down on allocation costs)
|
|
if (recycle_action)
|
|
{
|
|
//only save off one action per guy for recycling
|
|
recycle_action.Destroy();
|
|
}
|
|
recycle_action=current_action;
|
|
current_action=NULL;
|
|
}
|
|
|
|
if (actions.size())
|
|
{
|
|
ia = actions.begin();
|
|
current_action = *ia;
|
|
current_action->BeginAction(*this,monster);
|
|
actions.erase(ia);
|
|
}
|
|
}
|
|
|
|
void ai_c::EvaluateDecisions(edict_t &monster)
|
|
{
|
|
list<decision_c_ptr>::iterator id, pick, t_id;
|
|
int priority, max_priority;
|
|
bool found;
|
|
int i,dec_siz;
|
|
|
|
|
|
#ifdef _DEBUG
|
|
char *chosenDec;
|
|
#endif
|
|
|
|
dec_siz=decisions.size();
|
|
|
|
if (dec_siz==0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
max_priority = 0;
|
|
found = false;
|
|
for (id=decisions.begin(), i=dec_siz;i>0;i--)
|
|
{
|
|
if((*id)->Consider(*this, monster))//decision has let me know it's used up, trash it.
|
|
{
|
|
t_id = id;
|
|
id--;
|
|
(*t_id).Destroy();
|
|
decisions.erase(t_id);
|
|
}
|
|
else
|
|
{
|
|
priority = (*id)->Priority();
|
|
|
|
if (priority > max_priority)
|
|
{
|
|
pick = id;
|
|
max_priority = priority;
|
|
found = true;
|
|
#ifdef _DEBUG
|
|
chosenDec = decName;// global string pointer that has the name of the chosen decision
|
|
// I have this in here because I want to see what guys are doing when they
|
|
// are standing around looking less than clever... They shouldn't do that :)
|
|
#endif
|
|
}
|
|
}
|
|
id++;
|
|
}
|
|
|
|
if (found)
|
|
{
|
|
#ifdef _DEBUG
|
|
if(aidec_show->value)
|
|
{
|
|
Enemy_Printf(this, "I have chosen to do a %s; priority of %d; my priority is %s\n", chosenDec, max_priority, (worldpriority)?"low":"high");
|
|
// gi.dprintf("I have chosen to do a %s; priority of %d\n", chosenDec, max_priority);
|
|
}
|
|
#endif
|
|
(*pick)->Perform(*this, monster);
|
|
|
|
NextAction(monster);
|
|
|
|
}
|
|
}
|
|
|
|
void ai_c::EvaluateSenses(edict_t &monster)
|
|
{
|
|
list<sense_c_ptr>::iterator is,t_is;
|
|
sense_c *ptr = NULL;
|
|
int i;
|
|
|
|
sensedEntInfo_t temp;
|
|
|
|
if((worldpriority == PRIORITY_LOW)&&(lastCheckTime > level.time - 2.0))
|
|
{ // if yer not important, yer not allowed to look around much
|
|
return;
|
|
}
|
|
|
|
for (is=senses.begin(),i=senses.size();i>0;i--)
|
|
{
|
|
if ((*is)->Evaluate(sense_mask,*this, monster))//sense resolved, ditch it
|
|
{
|
|
(*is)->UpdateSensedClient(sense_mask, temp);
|
|
if (temp.ent && (body->GetBestWeapon(monster) != ATK_NOTHING))
|
|
{
|
|
int n = 1;
|
|
ptr = (*is);
|
|
}
|
|
t_is = is;
|
|
(*is).Destroy();
|
|
is++;
|
|
senses.erase(t_is);
|
|
}
|
|
else
|
|
{
|
|
(*is)->UpdateSensedClient(sense_mask, temp);
|
|
if (temp.ent && (body->GetBestWeapon(monster) != ATK_NOTHING))
|
|
{
|
|
int n = 1;
|
|
ptr = (*is);
|
|
}
|
|
is++;
|
|
}
|
|
}
|
|
|
|
lastCheckTime = level.time + 1.0;
|
|
|
|
sensedEntInfo_t sensed_client, sensed_monster;
|
|
GetSensedClientInfo(smask_all, sensed_client);
|
|
GetSensedMonsterInfo(smask_all, sensed_monster);
|
|
|
|
//as the sensed_client always gets preference
|
|
if (sensed_client.ent && sensed_client.ent->health>0)
|
|
{
|
|
if(sensed_client.time < level.time - 1.0)
|
|
{
|
|
firstTargetTime = level.time + gi.flrand(-.2,.2);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void ai_c::MuteSenses(unsigned mask, float degree, smute_recovery recovery_code, float recovery_time)
|
|
{
|
|
list<sense_c_ptr>::iterator is;
|
|
|
|
for (is=senses.begin();is!=senses.end();is++)
|
|
{
|
|
(*is)->Mute(mask,degree,recovery_code,recovery_time);
|
|
}
|
|
}
|
|
|
|
void ai_c::RegisterSenseEvent(unsigned mask, vec3_t event_origin, float event_time, edict_t *event_edict, ai_sensetype_e event_code)
|
|
{
|
|
list<sense_c_ptr>::iterator is;
|
|
|
|
for (is=senses.begin();is!=senses.end();is++)
|
|
{
|
|
assert(event_edict);
|
|
(*is)->RegisterSenseEvent(mask,event_origin,event_time,event_edict,event_code);
|
|
}
|
|
}
|
|
|
|
float ai_c::GetSenseMutedLevel(unsigned mask)
|
|
{
|
|
list<sense_c_ptr>::iterator is;
|
|
float cur, best=0;
|
|
|
|
for (is=senses.begin();is!=senses.end();is++)
|
|
{
|
|
cur = (*is)->GetMutedLevel(mask);
|
|
if (cur>best)
|
|
{
|
|
best = cur;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
void ai_c::GetSensedClientInfo(unsigned mask, sensedEntInfo_t &sensedEnt)
|
|
{
|
|
list<sense_c_ptr>::iterator is;
|
|
|
|
sensedEnt.ent=NULL;
|
|
VectorClear(sensedEnt.pos);
|
|
sensedEnt.time=-99999999;
|
|
sensedEnt.senseType=AI_SENSETYPE_UNKNOWN;
|
|
/*
|
|
if(getTarget())
|
|
{
|
|
if(getTarget() == level.sight_client)
|
|
{
|
|
//just to see
|
|
sensedEnt.time = level.time - .1;
|
|
sensedEnt.ent = level.sight_client;
|
|
lastTargetTime = level.time - .1;
|
|
VectorCopy(level.sight_client->s.origin, sensedEnt.pos);
|
|
return;
|
|
}
|
|
}
|
|
*/
|
|
for (is=senses.begin();is!=senses.end();is++)
|
|
{
|
|
(*is)->UpdateSensedClient(mask,sensedEnt);
|
|
|
|
if (sensedEnt.senseType==AI_SENSETYPE_SOUND_WAKEUP)
|
|
{
|
|
nextWakeReactTime=sensedEnt.time+12.0F;
|
|
sensedEnt.senseType=AI_SENSETYPE_SOUND_INVESTIGATE;
|
|
(*is)->ChangeClientSenseType(AI_SENSETYPE_SOUND_INVESTIGATE);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void ai_c::GetSensedMonsterInfo(unsigned mask, sensedEntInfo_t &sensedEnt)
|
|
{
|
|
list<sense_c_ptr>::iterator is;
|
|
|
|
sensedEnt.ent=NULL;
|
|
VectorClear(sensedEnt.pos);
|
|
sensedEnt.time=-99999999;
|
|
|
|
for (is=senses.begin();is!=senses.end();is++)
|
|
{
|
|
(*is)->UpdateSensedMonster(mask,sensedEnt);
|
|
}
|
|
}
|
|
|
|
qboolean ai_c::getTargetPos(vec3_t putPosHere)
|
|
{
|
|
sensedEntInfo_t sensedEnt;
|
|
|
|
if (curTarget)
|
|
{
|
|
if (curTarget->client)
|
|
{
|
|
GetSensedClientInfo(smask_all, sensedEnt);
|
|
}
|
|
else
|
|
{
|
|
GetSensedMonsterInfo(smask_all, sensedEnt);
|
|
}
|
|
if (sensedEnt.ent==curTarget)
|
|
{
|
|
VectorCopy(sensedEnt.pos, putPosHere);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//couldn't find the guy...
|
|
// VectorClear(putPosHere);
|
|
VectorCopy(ent->s.origin, putPosHere);
|
|
return false;
|
|
}
|
|
|
|
// kef
|
|
#include "m_heliai.h"
|
|
|
|
void ai_c::Activate(edict_t &monster)
|
|
{
|
|
if (!isActive)
|
|
{
|
|
isActive=true;
|
|
monster.movetype = MOVETYPE_STEP;
|
|
monster.spawnflags&=~SPAWNFLAG_TRIGGER_SPAWN;
|
|
monster.solid = SOLID_BBOX;
|
|
attentionLevel = ATTENTION_ALERT;
|
|
|
|
//grr, stupid dekker...
|
|
if (GetClassCode()==AI_MESO_RAIDERBOSS2)
|
|
{
|
|
trace_t tr;
|
|
gi.trace(monster.s.origin, monster.mins, monster.maxs, monster.s.origin, &monster, MASK_MONSTERSOLID, &tr);
|
|
if (tr.allsolid)
|
|
{
|
|
monster.s.origin[0]+=32.0;
|
|
gi.trace(monster.s.origin, monster.mins, monster.maxs, monster.s.origin, &monster, MASK_MONSTERSOLID, &tr);
|
|
if (tr.allsolid)
|
|
{
|
|
monster.s.origin[0]-=64.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
gi.linkentity(&monster);
|
|
}
|
|
}
|
|
|
|
void ai_c::Think(edict_t &monster)
|
|
{
|
|
// vec3_t testpos;
|
|
// VectorCopy(monster.s.origin,testpos);
|
|
// testpos[2]+=monster.maxs[2];
|
|
// FX_MakeDustPuff(testpos);
|
|
monster.nextthink = level.time + FRAMETIME;
|
|
|
|
if(BeingWatched())
|
|
{
|
|
int asdf = 9;
|
|
}
|
|
|
|
// if this joker is fleeing and he has the infamous NO_WOUND spawnflag, make
|
|
//sure he fires his killtarget
|
|
if (monster.spawnflags & SPAWNFLAG_NO_WOUND)
|
|
{
|
|
bool bFleeing = false;
|
|
decision_c *curDecision = current_action?current_action->GetOwnerDecision():NULL;
|
|
|
|
bFleeing = (curDecision && (curDecision->GetClassCode() == RETREAT_DECISION));
|
|
if (bFleeing)
|
|
{
|
|
monster.spawnflags &= ~SPAWNFLAG_NO_WOUND;
|
|
edict_t *t = NULL;
|
|
if (t = G_Find (t, FOFS(targetname), monster.killtarget))
|
|
{
|
|
t->use(t, &monster, &monster);
|
|
monster.killtarget = NULL;
|
|
}
|
|
}
|
|
}
|
|
if ( (monster.spawnflags & SPAWNFLAG_FACE_PLAYER) &&
|
|
(1 == monster.wait) )
|
|
{ // if wait == 1, face the player
|
|
vec3_t vPlayer, vTemp;
|
|
VectorCopy(g_edicts[1].s.origin, vPlayer);
|
|
VectorSubtract(vPlayer, monster.s.origin, vTemp);
|
|
vTemp[2] = 0;
|
|
vectoangles(vTemp, monster.s.angles);
|
|
}
|
|
|
|
|
|
// kef
|
|
if (game.cinematicfreeze)
|
|
{ // if we're in a cinematic, only let appropriate people think
|
|
|
|
// using 255 here as a flag. poor, I know, but if you hate it that much, change it yourself.
|
|
if ( (monster.count != 255) &&
|
|
(HasHadTarget() || (monster.flags & FL_SPAWNED_IN)) )
|
|
{ // this guy is not being used by the current cinematic so he doesn't get to think
|
|
monster.flags |= FL_CINEMATIC_CULL;
|
|
monster.nextthink = -(monster.nextthink - level.time);
|
|
// neither does he get to interact with the world
|
|
monster.solid = SOLID_NOT;
|
|
// he shouldn't even be visible
|
|
if (monster.ghoulInst)
|
|
{
|
|
monster.ghoulInst->SetOnOff(false, level.time);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
if(ai_dumb->value)
|
|
{
|
|
if(monster.spawnflags & SPAWNFLAG_BLIND)
|
|
{
|
|
fxRunner.exec("cantsee", &monster);
|
|
}
|
|
if(monster.spawnflags & SPAWNFLAG_DEAF)
|
|
{
|
|
fxRunner.exec("canthear", &monster);
|
|
}
|
|
/*if(monster.spawnflags & SPAWNFLAG_HOLD_POSITION)
|
|
{
|
|
fxRunner.exec("environ/firelarge2", &monster);
|
|
}*/
|
|
}
|
|
|
|
|
|
// debug_drawbox(&monster, NULL, NULL, NULL, 0);
|
|
/* if(worldpriority == PRIORITY_LOW)
|
|
{
|
|
monster.ghoulInst->TurnMatrixCallBacksOff();
|
|
}
|
|
else
|
|
{
|
|
monster.ghoulInst->TurnMatrixCallBacksOn(level.time);
|
|
}*/ // this doesn't seem to work so well :(
|
|
|
|
if (ai_freeze&&ai_freeze->value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(ai_goretest&&ai_goretest->value&&!(monster.spawnflags&SPAWNFLAG_TRIGGER_SPAWN))
|
|
{
|
|
Activate(monster);
|
|
}
|
|
|
|
if (!isActive)
|
|
{
|
|
if (monster.spawnflags&SPAWNFLAG_START_ACTIVE)
|
|
{
|
|
Activate(monster);
|
|
}
|
|
else
|
|
{
|
|
if((lastCorpseCheck < level.time) && !(monster.spawnflags&SPAWNFLAG_TRIGGER_SPAWN))
|
|
{
|
|
if(CheckForCorpseActivate(monster))
|
|
{
|
|
Activate(monster);
|
|
}
|
|
|
|
lastCorpseCheck = level.time + 1.0;
|
|
}
|
|
|
|
if(!isActive)
|
|
{
|
|
vec3_t tdist;
|
|
if (level.sight_client && !(monster.spawnflags&SPAWNFLAG_TRIGGER_SPAWN))
|
|
{
|
|
VectorSubtract(monster.s.origin, level.sight_client->s.origin, tdist);
|
|
if (VectorLengthSquared(tdist)<640000)
|
|
{
|
|
Activate(monster);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
EvaluateSenses(monster);
|
|
|
|
if (!current_action)
|
|
{ // do some thinking - this is not correct
|
|
if (actions.size())
|
|
{
|
|
NextAction(monster);
|
|
}
|
|
else // aren't any more actions
|
|
{
|
|
EvaluateDecisions(monster);
|
|
}
|
|
}
|
|
|
|
if (current_action)
|
|
{
|
|
if ((nextWakeReactTime >= level.time-1.0F) && (nextWakeReactTime < level.time)
|
|
&& body)
|
|
{
|
|
// body->VoiceSound("react", monster, 0);
|
|
nextWakeReactTime=level.time-20.0F;
|
|
}
|
|
|
|
AICurrentlyThinking=this;
|
|
int val = current_action->Think(*this, monster);
|
|
AICurrentlyThinking=0;
|
|
|
|
if (val)
|
|
{
|
|
NextAction(monster);
|
|
}
|
|
}
|
|
|
|
if (monster.linkcount != linkcount)
|
|
{
|
|
linkcount = monster.linkcount;
|
|
gmonster.CheckGround (&monster);
|
|
}
|
|
gmonster.CatagorizePosition (&monster);
|
|
gmonster.WorldEffects (&monster);//leaving this in for drowning, lava damage, etc., but it should prolly be handled in ai class somewhere
|
|
|
|
//camera stuff for debugging
|
|
watchState = false;
|
|
|
|
if ((worldpriority == PRIORITY_LOW)&&(monster.flags & FL_SPAWNED_IN))
|
|
{ // guys who are spawned in but too far away are silly and worth nothing
|
|
Escape(monster);
|
|
}
|
|
|
|
if (attentionLevel == ATTENTION_ESCAPED)
|
|
{ // yeah - makes no sense, huh. Fun.
|
|
Escape(monster);
|
|
}
|
|
|
|
//if monster is dead, consider removing it
|
|
if (monster.health <= 0)
|
|
{ // kef -- moved to body_human_c
|
|
//DealWithArmor(monster, GetBody());
|
|
//get ai to poll actions, decisions, & senses to check if removal is ok
|
|
if (SafeToRemove(monster))
|
|
{
|
|
// if we do this too quickly we may wind up removing him before and bolted-on knives
|
|
//can be turned into knife pickups. so if he still has any sticking out of him,
|
|
//free them now.
|
|
if (GetBody())
|
|
{
|
|
((bodyorganic_c*)GetBody())->FreeKnives(monster);
|
|
}
|
|
G_FreeEdict (&monster);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
qboolean ai_c::Damage (edict_t &monster, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t origin, int damage, int knockback, int dflags, int mod, float penetrate, float absorb)
|
|
{
|
|
// fixme: centralize skill level stuff
|
|
int take;
|
|
|
|
// friendly fire avoidance--allowing possibility of non-clients on your team
|
|
// if enabled you can't hurt teammates (but you can hurt yourself)
|
|
// knockback still occurs
|
|
if (&monster != attacker)
|
|
{
|
|
//fixme: come up with method of determining whether to take damage or not
|
|
if (OnSameTeam (&monster, attacker))
|
|
{
|
|
if ((attacker->client) && (mod != MOD_TELEFRAG))
|
|
{
|
|
SetAbusedByTeam(true);
|
|
}
|
|
else
|
|
{
|
|
|
|
// if(dm->dmRule_NO_FRIENDLY_FIRE())
|
|
// {
|
|
// damage = 0; dk disabled this because we want friendly fire on for demo
|
|
// }
|
|
// else
|
|
// {
|
|
// mod |= MOD_FRIENDLY_FIRE;
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
|
|
//HACKITY HACK!
|
|
if (mod == MOD_TELEFRAG)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// if (!attacker)// || !attacker->client)
|
|
// {
|
|
// damage = 0;
|
|
// }
|
|
|
|
VectorNormalize(dir);
|
|
|
|
// bonus damage for suprising a monster
|
|
if (!(dflags & DAMAGE_RADIUS) && (attacker->client) && (!monster.enemy) && (monster.health > 0))
|
|
{
|
|
damage *= 2;
|
|
}
|
|
|
|
if (monster.flags & FL_NO_KNOCKBACK)
|
|
{
|
|
knockback = 0;
|
|
}
|
|
|
|
|
|
take = damage;
|
|
|
|
// if (take)
|
|
// {
|
|
//throw up visible signs o' damage-gorezores, bloodspray, etc.
|
|
//notice that i've been shot, here
|
|
RegisterSenseEvent(smask_all, attacker->s.origin, level.time, attacker, AI_SENSETYPE_SOUND_WEAPON);
|
|
if (body && take)
|
|
{
|
|
hasHadTarget=true;
|
|
take=damage=body->ShowDamage(monster, inflictor, attacker, dir, point, origin, damage, knockback, dflags, mod, penetrate, absorb);
|
|
}
|
|
// }
|
|
|
|
//this used to be only if i took damage; now, knockback applies regardless.--ss
|
|
// do the damage
|
|
// figure momentum add
|
|
if (!(dflags & DAMAGE_NO_KNOCKBACK))
|
|
{
|
|
if ((knockback) &&
|
|
(monster.movetype != MOVETYPE_NONE) &&
|
|
(monster.movetype != MOVETYPE_BOUNCE) &&
|
|
(monster.movetype != MOVETYPE_PUSH) &&
|
|
(monster.movetype != MOVETYPE_NOCLIP) &&
|
|
(monster.movetype != MOVETYPE_STOP))
|
|
{
|
|
vec3_t kvel;
|
|
float mass;
|
|
vec3_t kbdir;
|
|
|
|
if (monster.mass < 50)
|
|
{
|
|
mass = 50;
|
|
}
|
|
else
|
|
{
|
|
mass = monster.mass;
|
|
}
|
|
|
|
// Bit of Knockback Fakery here.
|
|
// dir[2] += 0.3; // might this work?
|
|
|
|
VectorCopy(dir, kbdir); // NOTE: WE MUST DO THIS TO AVOID MODIFYING THE VECTOR WE PASSED HERE BY REFERENCE.
|
|
if (dflags&DAMAGE_ALL_KNOCKBACK)
|
|
{
|
|
kbdir[2] *= 0.2;
|
|
}
|
|
else
|
|
{
|
|
kbdir[2] += 0.3; // might this work?
|
|
}
|
|
VectorNormalize(kbdir);
|
|
VectorScale (kbdir, 500.0 * ((float)knockback) / mass, kvel);
|
|
|
|
// Com_Printf("dir: X: %f Y: %f Z: %f \n", dir[0], dir[1], dir[2]);
|
|
// Com_Printf("Knockback: X: %f Y: %f Z: %f \n", kvel[0], kvel[1], kvel[2]);
|
|
|
|
VectorAdd (monster.velocity, kvel, monster.velocity);
|
|
// dk monster.groundentity = NULL;
|
|
}
|
|
}
|
|
|
|
if (take)
|
|
{
|
|
if (attacker && attacker->client && monster.health > 0 && IsHostage())
|
|
{
|
|
gi.SP_Print(attacker, SINGLEPLR_HOSTAGE_HIT);
|
|
}
|
|
|
|
monster.health = monster.health - take;
|
|
|
|
}
|
|
|
|
//even if i didn't do damage this time, count this as a new kill time if guy is dead--so guys with armor will keep twitching when they're dead --ss
|
|
if (monster.health <= 0)
|
|
{
|
|
|
|
monster.flags |= FL_NO_KNOCKBACK;
|
|
Die (monster, inflictor, attacker, take, point, dflags);
|
|
return true;
|
|
}
|
|
|
|
if(body->GetArmorCode() != ARMOR_FULL || take)
|
|
{ //armor guys should not wince unless they are hurt
|
|
Pain (monster, attacker, point, knockback, take);
|
|
}
|
|
|
|
if (take)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;//no hit!
|
|
}
|
|
|
|
void ai_c::Die(edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point, int dflags)
|
|
{
|
|
if (AICurrentlyThinking==this)
|
|
{
|
|
// cannot die while thinking....::thinks are on the stack and still executing.
|
|
monster.health = 1;
|
|
return;
|
|
}
|
|
if (monster.health > 0)
|
|
{
|
|
monster.health = 0;
|
|
}
|
|
|
|
if (monster.health < -999)
|
|
{
|
|
monster.health = -999;
|
|
}
|
|
|
|
|
|
PlayerNoise(attacker, monster.s.origin, AI_SENSETYPE_SOUND_INVESTIGATE, &monster, 600, nodeData.curNode, dflags & (DT_STEALTHY), 1);
|
|
|
|
if(level.sight_client)
|
|
{
|
|
if(!OnSameTeam(level.sight_client, &monster))
|
|
{
|
|
if(monster.deadflag != DEAD_DEAD)
|
|
{
|
|
gmonster.SpookEnemies(monster.s.origin, 128, SPOOK_DEAD_FRIEND);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(monster.deadflag != DEAD_DEAD)
|
|
{
|
|
if(!game.cinematicfreeze)
|
|
{
|
|
if(!OnSameTeam(level.sight_client, &monster))
|
|
{
|
|
level.guysKilled++;
|
|
level.cashEarned += mySkills.getCashValue() * game.playerSkills.getMoney();
|
|
}
|
|
else
|
|
{
|
|
level.friendliesKilled++;
|
|
//level.cashEarned -= mySkills.getCashValue() * 10 * game.playerSkills.getMoney();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (monster.killtarget && (monster.deadflag != DEAD_DEAD))
|
|
{
|
|
qboolean useThatTarget=true;
|
|
|
|
//if killfacing set, i might Not want to use that target!
|
|
if (monster.killfacing)
|
|
{
|
|
vec3_t forward, tokillpoint;
|
|
edict_t *target = NULL;
|
|
useThatTarget=false;
|
|
|
|
AngleVectors(monster.s.angles, forward, NULL, NULL);
|
|
target = G_Find(target, FOFS(targetname), monster.killfacing);
|
|
if (target)
|
|
{
|
|
VectorSubtract(target->s.origin, monster.s.origin, tokillpoint);
|
|
tokillpoint[2] = 0;
|
|
forward[2] = 0;
|
|
VectorNormalize(tokillpoint);
|
|
VectorNormalize(forward);
|
|
if (DotProduct(tokillpoint, forward) > .96) // within 15 degrees either way
|
|
{
|
|
useThatTarget=true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gi.dprintf("Couldn't find killfacing ent %s--will use killtarget anyway!\n",monster.killfacing);
|
|
}
|
|
}
|
|
|
|
if (useThatTarget)
|
|
{
|
|
edict_t *t = NULL;
|
|
qboolean foundScript=false;
|
|
qboolean foundTarget=false;
|
|
while ((t = G_Find (t, FOFS(targetname), monster.killtarget)))
|
|
{
|
|
foundTarget=true;
|
|
t->use(t, &monster, &monster);
|
|
if (!strcmp(t->classname, "script_runner") && (t->spawnflags&1))
|
|
{
|
|
foundScript=true;
|
|
gi.dprintf("%s is running a WILL_KILL_USER script, and won't die right now!\n", monster.classname);
|
|
monster.health=monster.max_health;
|
|
|
|
//clear out all actions, so the script stuff happens immediately
|
|
list<action_c_ptr>::iterator ia;
|
|
|
|
for (ia=actions.begin();ia!=actions.end();ia++)
|
|
{
|
|
(*ia).Destroy();
|
|
}
|
|
while (actions.size())
|
|
{
|
|
actions.pop_front();
|
|
}
|
|
if (current_action)
|
|
{
|
|
current_action.Destroy();
|
|
}
|
|
if (recycle_action)
|
|
{
|
|
recycle_action.Destroy();
|
|
}
|
|
}
|
|
}
|
|
if (foundScript)
|
|
{
|
|
VectorClear(monster.velocity);
|
|
return;
|
|
}
|
|
else if (!foundTarget)
|
|
{
|
|
gi.dprintf("Couldn't find killtarget %s!\n",monster.killtarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if(!(monster.svflags & SVF_DEADMONSTER))
|
|
{
|
|
if(gi.irand(0, 3) == 1)
|
|
{ // drop a bit of armor every so often
|
|
//item_equip_kevlar
|
|
edict_t *armor = MakeItem("item_equip_kevlar", monster.s.origin);
|
|
armor->count = gi.irand(10, 40);//hmm... hardcodedness is mine - we've really got to improve this end up stuff
|
|
|
|
armor->movetype = MOVETYPE_DAN;
|
|
armor->friction = 1.0;
|
|
armor->gravity = 1.0;
|
|
armor->airresistance = 0.0;
|
|
armor->bouyancy = .7;
|
|
armor->elasticity = 0.3;
|
|
|
|
armor->velocity[2] = 600;// hmm...
|
|
}
|
|
}*/
|
|
|
|
// monster.solid = SOLID_BBOX;
|
|
monster.solid = SOLID_CORPSE;
|
|
|
|
monster.clipmask = CONTENTS_DEADMONSTER|MASK_DEADSOLID;
|
|
monster.svflags |= SVF_DEADMONSTER;
|
|
monster.movetype = MOVETYPE_STEP; // in case a guy is killed while NO_CLIP in scripting
|
|
monster.friction = .5;
|
|
|
|
monster.enemy = attacker;//for awarding credit?
|
|
// CHECKME possible issues:
|
|
// If monster is killed by barrel, should we chain to find who was the barrel's killer?
|
|
// what about being clever and using architecture to kill monsters (like 16 ton weights)
|
|
|
|
if (GetBody())
|
|
{
|
|
if (!GetBody()->GetInitialKilledTime())
|
|
{
|
|
GetBody()->SetInitialKilledTime(level.time);
|
|
}
|
|
GetBody()->SetLastKilledTime(level.time);
|
|
GetBody()->SetLastDFlags(dflags);
|
|
}
|
|
if ( (monster.deadflag != DEAD_DEAD) || ((monster.spawnflags & SPAWNFLAG_ARMOR_PICKUP) && (current_action && current_action->GetClassCode()== DEATHARMOR_ACTION)))
|
|
{
|
|
vec3_t facedir;
|
|
list<action_c_ptr>::iterator ia;
|
|
list<sense_c_ptr>::iterator is;
|
|
list<decision_c_ptr>::iterator id;
|
|
|
|
|
|
// kef -- if this chunk of code gets called more than once per entity, that's bad
|
|
if ( (strstr(monster.classname, "swat")) &&
|
|
(attacker->client) &&
|
|
!(monster.spawnflags & SPAWNFLAG_ARMOR_PICKUP) )
|
|
{ // if the player kills a cop, game over.
|
|
if (level.maxDeadHostages==-1)
|
|
{
|
|
level.maxDeadHostages=0;
|
|
}
|
|
level.deadHostages=level.maxDeadHostages+1;
|
|
}
|
|
else if (IsHostage())
|
|
{ // keep track of number of dead hostages in this level
|
|
level.deadHostages++;
|
|
}
|
|
else if (IsSpecialBuddy()&&attacker->client)
|
|
{
|
|
//yick. well, it's an easy way to fail.
|
|
if (level.maxDeadHostages==-1)
|
|
{
|
|
level.maxDeadHostages=0;
|
|
}
|
|
level.deadHostages=level.maxDeadHostages+1;
|
|
}
|
|
|
|
/* if (game.GameStats->CanBuy(attacker))
|
|
{
|
|
//changed this to be conditional so it wouldn't crash --ss
|
|
if(CMonsterStats *MonsterStats = game.GameStats->GetMonsterStats(&monster))
|
|
{
|
|
GetPlayerStats(attacker)->AdjustCashToBeAwarded(MonsterStats->GetKilledValue());
|
|
}
|
|
else
|
|
{
|
|
gi.dprintf("Error: no stats for monster!\n");
|
|
}
|
|
// this stuff gets put into bank account at "end" of mission
|
|
}*/
|
|
|
|
//fixme: do need the npc check, but do it a good way
|
|
// if (!(monster.monsterinfo.aiflags & AI_GOOD_GUY))
|
|
// {
|
|
level.killed_monsters++;
|
|
/* if (coop->value && attacker->client)
|
|
{
|
|
attacker->client->resp.score++;
|
|
}*/
|
|
// }
|
|
|
|
// huh? should have list-eating separated out?
|
|
for (is=senses.begin();is != senses.end();is++)
|
|
{
|
|
(*is).Destroy();
|
|
}
|
|
|
|
for (ia=actions.begin();ia!=actions.end();ia++)
|
|
{
|
|
(*ia).Destroy();
|
|
}
|
|
|
|
for (id=decisions.begin();id!=decisions.end();id++)
|
|
{
|
|
(*id).Destroy();
|
|
}
|
|
|
|
|
|
//now ditch nodes
|
|
while (senses.size())
|
|
{
|
|
senses.pop_front();
|
|
}
|
|
|
|
while (actions.size())
|
|
{
|
|
actions.pop_front();
|
|
}
|
|
|
|
while (decisions.size())
|
|
{
|
|
decisions.pop_front();
|
|
}
|
|
|
|
if (current_action && !(monster.spawnflags & SPAWNFLAG_ARMOR_PICKUP) )
|
|
{
|
|
current_action.Destroy();
|
|
}
|
|
|
|
if (recycle_action)
|
|
{
|
|
recycle_action.Destroy();
|
|
}
|
|
|
|
PickDeathAnim(monster, inflictor, attacker, damage, point, dflags);
|
|
|
|
monster.touch = NULL;//do we need this here?
|
|
monster.deadflag = DEAD_DEAD;
|
|
|
|
// kef -- cut off any sounds he's making
|
|
gi.sound(&monster, CHAN_VOICE, 0 , .6, ATTN_NORM, 0);
|
|
|
|
VectorSubtract(point,monster.s.origin,facedir);
|
|
VectorCopy(monster.s.angles, ideal_angles);
|
|
gi.linkentity (&monster);
|
|
}
|
|
}
|
|
|
|
void ai_c::Pain(edict_t &monster, edict_t *other, vec3_t point, float kick, int damage)
|
|
{
|
|
if (monster.health > 0 && body)
|
|
{
|
|
mmove_t *newpain;
|
|
|
|
if (other->client)
|
|
{
|
|
if (IsSpecialBuddy())//if i'm a special buddy, print a special message
|
|
{
|
|
gi.SP_Print(other, SINGLEPLR_BUDDY_HIT);
|
|
}
|
|
}
|
|
|
|
if (newpain = body->GetSequenceForPain(monster, point, kick, damage))
|
|
{
|
|
NewCurrentAction(PainAction(NULL, current_action, newpain, monster, other, kick, damage, 10.0), monster);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ai_c::Escape(edict_t &monster)
|
|
{ // sort of a ::die lite - makes guys safe to toss
|
|
|
|
// make certain I'm out of view or life will be poor indeed
|
|
if(gi.inPVS(monster.s.origin, level.sight_client->s.origin))
|
|
{
|
|
return;//don't wanna see folks disappearing for no reason
|
|
}
|
|
|
|
attentionLevel = ATTENTION_DISTRACTED;//? eh, I don't care. As long as they don't get here again
|
|
|
|
monster.health = 0;
|
|
|
|
if (monster.killtarget && (monster.deadflag != DEAD_DEAD))
|
|
{
|
|
qboolean useThatTarget=true;
|
|
|
|
//if killfacing set, i might Not want to use that target!
|
|
if (monster.killfacing)
|
|
{
|
|
vec3_t forward, tokillpoint;
|
|
edict_t *target = NULL;
|
|
useThatTarget=false;
|
|
|
|
AngleVectors(monster.s.angles, forward, NULL, NULL);
|
|
target = G_Find(target, FOFS(targetname), monster.killfacing);
|
|
if (target)
|
|
{
|
|
VectorSubtract(target->s.origin, monster.s.origin, tokillpoint);
|
|
tokillpoint[2] = 0;
|
|
forward[2] = 0;
|
|
VectorNormalize(tokillpoint);
|
|
VectorNormalize(forward);
|
|
if (DotProduct(tokillpoint, forward) > .96) // within 15 degrees either way
|
|
{
|
|
useThatTarget=true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gi.dprintf("Couldn't find killfacing ent %s--will use killtarget anyway!\n",monster.killfacing);
|
|
}
|
|
}
|
|
|
|
if (useThatTarget)
|
|
{
|
|
edict_t *t = NULL;
|
|
qboolean foundScript=false;
|
|
qboolean foundTarget=false;
|
|
while ((t = G_Find (t, FOFS(targetname), monster.killtarget)))
|
|
{
|
|
foundTarget=true;
|
|
t->use(t, &monster, &monster);
|
|
if (!strcmp(t->classname, "script_runner") && (t->spawnflags&1))
|
|
{
|
|
foundScript=true;
|
|
gi.dprintf("%s is running a WILL_KILL_USER script, and won't die right now!\n", monster.classname);
|
|
monster.health=monster.max_health;
|
|
|
|
//clear out all actions, so the script stuff happens immediately
|
|
list<action_c_ptr>::iterator ia;
|
|
|
|
for (ia=actions.begin();ia!=actions.end();ia++)
|
|
{
|
|
(*ia).Destroy();
|
|
}
|
|
while (actions.size())
|
|
{
|
|
actions.pop_front();
|
|
}
|
|
if (current_action)
|
|
{
|
|
current_action.Destroy();
|
|
}
|
|
if (recycle_action)
|
|
{
|
|
recycle_action.Destroy();
|
|
}
|
|
}
|
|
}
|
|
if (foundScript)
|
|
{
|
|
VectorClear(monster.velocity);
|
|
return;
|
|
}
|
|
else if (!foundTarget)
|
|
{
|
|
gi.dprintf("Couldn't find killtarget %s!\n",monster.killtarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
monster.solid = SOLID_NOT;
|
|
monster.clipmask = 0;
|
|
monster.svflags |= SVF_DEADMONSTER|SVF_NOCLIENT;// I don't want see this
|
|
monster.movetype = MOVETYPE_NONE; // in case a guy is killed while NO_CLIP in scripting
|
|
|
|
if (GetBody())
|
|
{
|
|
GetBody()->SetInitialKilledTime(level.time - 25);//give him 5 seconds to go away
|
|
GetBody()->SetLastDFlags(0);
|
|
}
|
|
if (monster.deadflag != DEAD_DEAD)
|
|
{
|
|
list<action_c_ptr>::iterator ia;
|
|
list<sense_c_ptr>::iterator is;
|
|
list<decision_c_ptr>::iterator id;
|
|
|
|
|
|
// huh? should have list-eating separated out?
|
|
for (is=senses.begin();is != senses.end();is++)
|
|
{
|
|
(*is).Destroy();
|
|
}
|
|
|
|
for (ia=actions.begin();ia!=actions.end();ia++)
|
|
{
|
|
(*ia).Destroy();
|
|
}
|
|
|
|
for (id=decisions.begin();id!=decisions.end();id++)
|
|
{
|
|
(*id).Destroy();
|
|
}
|
|
|
|
|
|
//now ditch nodes
|
|
while (senses.size())
|
|
{
|
|
senses.pop_front();
|
|
}
|
|
|
|
while (actions.size())
|
|
{
|
|
actions.pop_front();
|
|
}
|
|
|
|
while (decisions.size())
|
|
{
|
|
decisions.pop_front();
|
|
}
|
|
|
|
if (current_action)
|
|
{
|
|
current_action.Destroy();
|
|
}
|
|
|
|
if (recycle_action)
|
|
{
|
|
recycle_action.Destroy();
|
|
}
|
|
|
|
monster.touch = NULL;//do we need this here?
|
|
monster.deadflag = DEAD_DEAD;
|
|
|
|
gi.linkentity (&monster);//? I need him around for a bit to be safe, but that is all
|
|
}
|
|
}
|
|
|
|
void ai_c::SetGround(float groundval)
|
|
{
|
|
// if (ent && ent->ghoulInst && !ent->ghoulInst->GetPlayingSequence())
|
|
// {
|
|
// return;
|
|
// }
|
|
|
|
ground=groundval;
|
|
}
|
|
|
|
void ai_c::InitBBoxPreset(bbox_preset preset_index, vec_t xMinVal, vec_t yMinVal, vec_t zMinVal,
|
|
vec_t xMaxVal, vec_t yMaxVal, vec_t zMaxVal)
|
|
{
|
|
//invalid preset index
|
|
if (preset_index < 0 || preset_index >= BBOX_PRESET_NUMBER)
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorSet(preset_mins[preset_index], xMinVal, yMinVal, zMinVal);
|
|
VectorSet(preset_maxs[preset_index], xMaxVal, yMaxVal, zMaxVal);
|
|
}
|
|
|
|
void ai_c::ConfirmBBox (edict_t &monster, mmove_t *curMove)
|
|
{
|
|
// Hmm, do we really care if a dead guy's bbox intersects architecture? I don't think so, so I'm going to give it a shot.
|
|
// But, we can only do this with x-y, lest he fall throught the world
|
|
|
|
monster.mins[0] = preset_mins[current_bbox][0];
|
|
monster.mins[1] = preset_mins[current_bbox][1];
|
|
monster.maxs[0] = preset_maxs[current_bbox][0];
|
|
monster.maxs[1] = preset_maxs[current_bbox][1];
|
|
}
|
|
|
|
qboolean ai_c::AttemptSetBBox (edict_t &monster, bbox_preset preset_index, qboolean forceZ)
|
|
{
|
|
trace_t tr;
|
|
|
|
vec3_t testpos,testmins,testmaxs;
|
|
|
|
qboolean minorAdjust = false;
|
|
|
|
//invalid preset index
|
|
if (preset_index < 0 || preset_index >= BBOX_PRESET_NUMBER)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
VectorCopy(monster.s.origin,testpos);
|
|
VectorScale(preset_mins[preset_index],scale,testmins);
|
|
// testpos[2]-=testmins[2]-monster.mins[2];
|
|
VectorScale(preset_maxs[preset_index],scale,testmaxs);
|
|
|
|
|
|
if (preset_index == current_bbox /*&& monster.ghoulInst && monster.ghoulInst->GetPlayingSequence()*/)
|
|
{
|
|
|
|
if (monster.mins[2]-ground>15.0)
|
|
{
|
|
ground = monster.mins[2] - 15;
|
|
}
|
|
if (monster.mins[2]-ground<-15.0)
|
|
{
|
|
ground = monster.mins[2] + 15;
|
|
}
|
|
|
|
float adjust2 = fabs(monster.mins[2]-ground);
|
|
if (!monster.groundentity && (monster.solid != SOLID_CORPSE))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//commented out the groundentity check to allow guys in air to twiddle with their bboxes
|
|
if (/*(!monster.groundentity&&monster.movetype==MOVETYPE_STEP) ||*/ adjust2 < 1)
|
|
{
|
|
return true;
|
|
}
|
|
// gi.dprintf("minor adjustment: %f\n",monster.mins[2]-ground);
|
|
minorAdjust = true;
|
|
// testpos[2]=monster.s.origin[2];
|
|
testpos[2]-=ground-monster.mins[2];
|
|
testmaxs[2]+=ground-testmins[2];
|
|
testmins[2]=ground;
|
|
}
|
|
else
|
|
{
|
|
float majorAdjustLen = monster.mins[2]-testmins[2];
|
|
// gi.dprintf("major adjustment: %f\n",majorAdjustLen);
|
|
// testpos[2]-=majorAdjustLen;
|
|
testmaxs[2]+=majorAdjustLen;
|
|
testmins[2]+=majorAdjustLen;
|
|
}
|
|
|
|
// set upper bound of box to the greater of the head height of the monster or the height of the
|
|
// preset bounding box.
|
|
// bool heightadjust = false;
|
|
if (((monster.viewheight) > testmaxs[2]) && (fabs(monster.viewheight - testmaxs[2]) > 4))
|
|
{
|
|
testmaxs[2] = monster.viewheight;
|
|
// heightadjust = true;
|
|
}
|
|
|
|
// if (monster.deadflag==DEAD_DEAD)
|
|
// {
|
|
// gi.trace(testpos, testmins, testmaxs, testpos, &monster, monster.clipmask, &tr);
|
|
// }
|
|
// else
|
|
if (monster.movetype == MOVETYPE_NOCLIP) // awful badness for crazy climbing animations and such
|
|
{
|
|
tr.allsolid = false;
|
|
}
|
|
else
|
|
{
|
|
gi.trace(testpos, testmins, testmaxs, testpos, &monster, MASK_MONSTERSOLID, &tr);
|
|
}
|
|
|
|
if (!tr.allsolid)
|
|
{
|
|
VectorCopy(testmins, monster.mins);
|
|
VectorCopy(testmaxs, monster.maxs);
|
|
VectorCopy(testpos, monster.s.origin);
|
|
gi.linkentity (&monster);
|
|
|
|
//in case i come back here this frame, set my ground to my testmins so i don't hop back where i was
|
|
// if (!minorAdjust)
|
|
// {
|
|
// ground=testmins[2];
|
|
// }
|
|
|
|
//early attempt at preventing spawn-sprouting
|
|
// if (monster.ghoulInst && !monster.ghoulInst->GetPlayingSequence())
|
|
// {
|
|
// ground=testmins[2];
|
|
// }
|
|
|
|
// if (!heightadjust)
|
|
{
|
|
current_bbox = preset_index;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (forceZ /*&& !minorAdjust*/)
|
|
{
|
|
testmins[0]=monster.mins[0];
|
|
testmins[1]=monster.mins[1];
|
|
testmaxs[0]=monster.maxs[0];
|
|
testmaxs[1]=monster.maxs[1];
|
|
|
|
|
|
// if (monster.deadflag==DEAD_DEAD)
|
|
// {
|
|
// gi.trace(testpos, testmins, testmaxs, testpos, &monster, monster.clipmask, &tr);
|
|
// }
|
|
// else
|
|
{
|
|
gi.trace(testpos, testmins, testmaxs, testpos, &monster, MASK_MONSTERSOLID, &tr);
|
|
}
|
|
|
|
if (!tr.allsolid && !tr.startsolid || (monster.deadflag & DEAD_DEAD))
|
|
{
|
|
VectorCopy(testmins, monster.mins);
|
|
VectorCopy(testmaxs, monster.maxs);
|
|
VectorCopy(testpos, monster.s.origin);
|
|
gi.linkentity (&monster);
|
|
|
|
// if (!heightadjust)
|
|
{
|
|
current_bbox = preset_index;//achtung! not really!
|
|
}
|
|
return true;
|
|
}
|
|
#if _DEBUG
|
|
Com_Printf("Unable to force bbox z component!\n");
|
|
#endif
|
|
}
|
|
return minorAdjust;
|
|
}
|
|
|
|
void ai_c::Emote(edict_t &monster, emotion_index new_emotion, float emotion_duration, qboolean scripted_emoting)
|
|
{
|
|
if (body)
|
|
{
|
|
body->Emote(monster,new_emotion,emotion_duration,scripted_emoting);
|
|
}
|
|
}
|
|
|
|
emotion_index ai_c::GetMood(edict_t &monster)
|
|
{
|
|
if (body)
|
|
{
|
|
return body->GetMood(monster);
|
|
}
|
|
return EMOTION_NORMAL;
|
|
}
|
|
|
|
void ai_c::FinishMove(edict_t &monster)
|
|
{
|
|
if (body)
|
|
{
|
|
body->FinishMove(monster);
|
|
}
|
|
}
|
|
|
|
void ai_c::InAirFrame(edict_t &monster, float frameTime)
|
|
{
|
|
if (body)
|
|
{
|
|
body->HoldAnimation(monster, HOLDCODE_INAIR, frameTime);
|
|
}
|
|
}
|
|
|
|
void ai_c::JumpFrame(edict_t &monster)
|
|
{
|
|
if (body)
|
|
{
|
|
body->SetFlags(monster, FRAMEFLAG_JUMP);
|
|
}
|
|
}
|
|
|
|
void ai_c::LandFrame(edict_t &monster)
|
|
{
|
|
if (body)
|
|
{
|
|
body->SetFlags(monster, FRAMEFLAG_LAND);
|
|
}
|
|
}
|
|
|
|
void ai_c::AttackFrame(edict_t &monster)
|
|
{
|
|
if (body)
|
|
{
|
|
body->SetFlags(monster, FRAMEFLAG_ATTACK);
|
|
}
|
|
}
|
|
|
|
void ai_c::ThrowFrame(edict_t &monster)
|
|
{
|
|
if (body)
|
|
{
|
|
body->SetFlags(monster, FRAMEFLAG_THROW);
|
|
}
|
|
}
|
|
|
|
void ai_c::MeleeFrame(edict_t &monster)
|
|
{
|
|
if (body)
|
|
{
|
|
body->SetFlags(monster, FRAMEFLAG_MELEE);
|
|
}
|
|
}
|
|
|
|
void ai_c::HandleCallBack(IGhoulInst *me,void *user,float now,const void *data)
|
|
{
|
|
edict_t *ent = (edict_t *)user;
|
|
|
|
if (!strcmp((char*)data,"rstep"))
|
|
{
|
|
FX_SetEvent(ent, EV_FOOTSTEPRIGHT);
|
|
}
|
|
else if (!strcmp((char*)data,"lstep"))
|
|
{
|
|
FX_SetEvent(ent, EV_FOOTSTEPLEFT);
|
|
}
|
|
else if (!strcmp((char*)data,"ooze"))
|
|
{
|
|
vec3_t curVec;
|
|
|
|
VectorCopy(ent->s.origin, curVec);
|
|
curVec[2] += 10;
|
|
|
|
FX_MakeDecalBelow(curVec,FXDECAL_BLOODPOOL,0);
|
|
}
|
|
}
|
|
|
|
mmove_t *ai_c::GetMove(void)
|
|
{
|
|
if (body)
|
|
{
|
|
return body->GetMove();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void ai_c::SetMove(mmove_t *newmove)
|
|
{
|
|
if (body)
|
|
{
|
|
body->SetMove(newmove);
|
|
}
|
|
}
|
|
|
|
//if you pass in NULL, will return any old scripted decision
|
|
scripted_decision *ai_c::FindScriptedDecision(edict_t* ScriptEntity)
|
|
{
|
|
list<decision_c_ptr>::iterator IterDecision;
|
|
|
|
for(IterDecision = decisions.begin(); IterDecision != decisions.end(); IterDecision++)
|
|
{
|
|
if ((*IterDecision)->MatchScriptEnt(ScriptEntity) || (!ScriptEntity && (*IterDecision)->GetClassCode()==SCRIPTED_DECISION))
|
|
{
|
|
return (scripted_decision*)((decision_c*)(*IterDecision));
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void ai_c::CancelScripting(edict_t* ScriptEntity)
|
|
{
|
|
list<decision_c_ptr>::iterator IterDecision;
|
|
list<action_c_ptr>::iterator IterAction, IterAction2;
|
|
|
|
//find the scripting decision
|
|
for(IterDecision = decisions.begin(); IterDecision != decisions.end(); IterDecision++)
|
|
{
|
|
//ok, got the decision...
|
|
if ((*IterDecision)->MatchScriptEnt(ScriptEntity) || (!ScriptEntity && (*IterDecision)->GetClassCode()==SCRIPTED_DECISION))
|
|
{
|
|
//get rid of all the actions in the action queue that are owned by the scripted decision
|
|
for(IterAction = actions.begin(); IterAction != actions.end(); )
|
|
{
|
|
if (ent && (*IterAction) && (*IterAction)->GetOwnerDecision()==(*IterDecision))
|
|
{
|
|
for(IterAction2 = actions.begin(); IterAction2 != actions.end(); IterAction2++)
|
|
{
|
|
if ((*IterAction2)->GetOwnerAction()==(*IterAction))
|
|
{
|
|
(*IterAction2)->SetOwnerAction(NULL);
|
|
}
|
|
}
|
|
if (current_action && current_action->GetOwnerAction()==(*IterAction))
|
|
{
|
|
current_action->SetOwnerAction(NULL);
|
|
}
|
|
(*IterAction).Destroy();
|
|
actions.erase(IterAction++);
|
|
}
|
|
else
|
|
{
|
|
IterAction++;
|
|
}
|
|
}
|
|
|
|
//if the current_action is owned by the scripted decision, get rid of it too
|
|
if (ent && current_action && current_action->GetOwnerDecision()==(*IterDecision))
|
|
{
|
|
NextAction(*ent);
|
|
}
|
|
|
|
//ok, there shouldn't be any pointers left to the scripted decision; get rid of it.
|
|
(*IterDecision).Destroy();
|
|
decisions.erase(IterDecision);
|
|
|
|
break;//this will be messy if a scriptent has more than one decision, but that shouldn't ever happen...
|
|
}
|
|
}
|
|
}
|
|
|
|
decision_c *ai_c::FindOrderDecision()
|
|
{
|
|
list<decision_c_ptr>::iterator IterDecision;
|
|
|
|
for(IterDecision = decisions.begin(); IterDecision != decisions.end(); IterDecision++)
|
|
{
|
|
if ((*IterDecision)->GetClassCode() == ORDER_DECISION)
|
|
{
|
|
return (*IterDecision);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool ai_c::GetLastActionDestination(vec3_t Destination)
|
|
{
|
|
// The function should return the goal position for the lastest action with such a position
|
|
list<action_c_ptr>::iterator IterAction;
|
|
|
|
for(IterAction = (--actions.end()); IterAction != actions.end(); IterAction--)
|
|
{
|
|
if (*IterAction)
|
|
{
|
|
(*IterAction)->GetDest(Destination);
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
if (!(VectorCompare(Destination, vec3_origin)))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// check the current action
|
|
if (current_action)
|
|
{
|
|
current_action->GetDest(Destination);
|
|
}
|
|
if (!(VectorCompare(Destination, vec3_origin)))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ai_c::PickDeathAnim(edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point, int dflags)
|
|
{
|
|
mmove_t *mydeath;
|
|
if (body)
|
|
{
|
|
mydeath = body->GetSequenceForDeath(monster, inflictor, attacker, damage, point, dflags);
|
|
if (mydeath && mydeath->suggested_action != ACTCODE_DEATH)
|
|
{
|
|
gi.dprintf("Death action %s not really a death!\n", mydeath->ghoulSeqName);
|
|
}
|
|
if (!mydeath)
|
|
{
|
|
mydeath=&generic_move_death_long;
|
|
}
|
|
}
|
|
|
|
if (monster.spawnflags & SPAWNFLAG_ARMOR_PICKUP)
|
|
{
|
|
NewCurrentAction(new deathcorpse_action(NULL, NULL, mydeath, monster, inflictor, attacker, damage, point), monster);
|
|
}
|
|
else
|
|
{
|
|
NewCurrentAction(DeathAction(NULL, NULL, mydeath, monster, inflictor, attacker, damage, point), monster);
|
|
}
|
|
}
|
|
|
|
action_c *ai_c::GetLastAction()
|
|
{
|
|
list<action_c_ptr>::iterator ActionIter;
|
|
|
|
ActionIter = actions.end();
|
|
ActionIter--;
|
|
|
|
return (*ActionIter);
|
|
}
|
|
|
|
void ai_c::RemoveOldDecisions (int code)
|
|
{
|
|
list<decision_c_ptr>::iterator DecisionIter;
|
|
|
|
for (DecisionIter = decisions.begin();DecisionIter!=decisions.end();DecisionIter++)
|
|
{
|
|
if ((*DecisionIter)->GetClassCode() == code)
|
|
{
|
|
list<action_c_ptr>::iterator ActionIter;
|
|
|
|
// eliminate old order actions
|
|
for (ActionIter=actions.begin();ActionIter!=actions.end();ActionIter++)
|
|
{
|
|
if ((*ActionIter)->GetOwnerDecision() == (*DecisionIter))
|
|
{
|
|
(*ActionIter).Destroy();
|
|
}
|
|
}
|
|
(*DecisionIter).Destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
//eek, has ballooned into a large blimpy thing...
|
|
void ai_c::SetTargetTime(float now, edict_t *target, vec3_t position)
|
|
{
|
|
//if i'm first being alerted, notify other folks!
|
|
if (!hasHadTarget && ent)
|
|
{
|
|
//HACK ALERT! ...but only if i'm close to the guy...
|
|
vec3_t topos;
|
|
VectorSubtract(position, ent->s.origin, topos);
|
|
// if (VectorLengthSquared(topos)<250000)
|
|
if (VectorLengthSquared(topos)<1000000)//this is tweaked to work on trn1
|
|
{
|
|
if (isStartleable)
|
|
{
|
|
SetStartleability(false);
|
|
|
|
//only play alert sound if perception was immediate, and enemy is client...(fixme?)
|
|
if (target->client && body)
|
|
{
|
|
body->VoiceWakeSound(*ent, 0.9);
|
|
// PlayerNoise(target, position, AI_SENSETYPE_SOUND_WAKEUP, NULL, 500);
|
|
PlayerNoise(target, ent->s.origin, AI_SENSETYPE_SOUND_WAKEUP, NULL, 500);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
hasHadTarget=true;
|
|
if (now>lastTargetTime)
|
|
{
|
|
lastTargetTime=now;
|
|
}
|
|
if (ent && GetClassCode()!=AI_MESO_RAIDERBOSS2 && GetClassCode()!=AI_MESO_SKINHEADBOSS)
|
|
{
|
|
ent->spawnflags&=~SPAWNFLAG_SENSE_MUTE;
|
|
|
|
//give me senses--i'm after somebody!
|
|
if (ent->spawnflags&SPAWNFLAG_BLIND)
|
|
{
|
|
NewSense(new normalsight_sense(), ent);
|
|
ent->spawnflags&=(~SPAWNFLAG_BLIND);
|
|
}
|
|
if (ent->spawnflags&SPAWNFLAG_DEAF)
|
|
{
|
|
NewSense(new sound_sense(), ent);
|
|
ent->spawnflags&=(~SPAWNFLAG_DEAF);
|
|
}
|
|
}
|
|
|
|
assert((!target)||(target->inuse < 10));
|
|
curTarget = target;
|
|
|
|
}
|
|
|
|
void ai_c::UpdatePathPosition(edict_t *myEnt)
|
|
{
|
|
if(aiPoints.isActive())
|
|
{
|
|
aiPoints.linkEnemyIntoPath(ent->s.origin, &nodeData);
|
|
}
|
|
}
|
|
|
|
int ai_c::CheckForCorpseActivate(edict_t &monster)
|
|
{
|
|
edict_t *curSearch = 0;
|
|
|
|
CRadiusContent rad(monster.s.origin, 200);
|
|
|
|
for(int i = 0; i < rad.getNumFound(); i++)
|
|
{
|
|
curSearch = rad.foundEdict(i);
|
|
|
|
if(curSearch->deadflag == DEAD_DEAD)
|
|
{ // is this a valid search?
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ai_c::RegisterShotForDodge(vec3_t start, vec3_t end, edict_t *shooter)
|
|
{
|
|
list<decision_c_ptr>::iterator id, pick, t_id;
|
|
int i,dec_siz;
|
|
|
|
dec_siz=decisions.size();
|
|
|
|
if (dec_siz==0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RegisterSenseEvent(sound_mask, start, level.time, shooter, AI_SENSETYPE_SOUND_WHIZ);
|
|
|
|
for (id=decisions.begin(), i=dec_siz;i>0;i--)
|
|
{
|
|
(*id)->SetInfoForDodge(start, end);
|
|
id++;
|
|
}
|
|
}
|
|
|
|
void ai_c::RegisterShotForReply(vec3_t start, vec3_t end, edict_t *shooter)
|
|
{
|
|
list<decision_c_ptr>::iterator id, pick, t_id;
|
|
int i,dec_siz;
|
|
|
|
dec_siz=decisions.size();
|
|
|
|
if (dec_siz==0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (id=decisions.begin(), i=dec_siz;i>0;i--)
|
|
{
|
|
(*id)->SetInfoForReply(start, end);
|
|
id++;
|
|
}
|
|
}
|
|
|
|
void ai_c::Spook(float amount, vec3_t center)
|
|
{
|
|
list<decision_c_ptr>::iterator id, pick, t_id;
|
|
int i,dec_siz;
|
|
|
|
dec_siz=decisions.size();
|
|
|
|
if (dec_siz==0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (id=decisions.begin(), i=dec_siz;i>0;i--)
|
|
{
|
|
(*id)->AddFear(amount, center);
|
|
id++;
|
|
}
|
|
|
|
}
|
|
|
|
action_c *ai_c::WalkAction(decision_c *od, action_c *oa, mmove_t *newanim, vec3_t destination, vec3_t facing, float timeout, qboolean fullAnimation)
|
|
{
|
|
if (recycle_action && recycle_action->GetClassCode()==WALK_ACTION)
|
|
{
|
|
walk_action *returnme=(walk_action*)(action_c*)recycle_action;
|
|
recycle_action = NULL;
|
|
returnme->Init(od, oa, newanim, destination, facing, timeout, fullAnimation);
|
|
return returnme;
|
|
}
|
|
|
|
return new walk_action(od, oa, newanim, destination, facing, timeout,fullAnimation);
|
|
}
|
|
|
|
action_c *ai_c::DefaultAction(decision_c *od, action_c *oa, mmove_t *newanim, vec3_t destination, vec3_t facing, edict_t *target, float timeout, qboolean fullAnimation)
|
|
{
|
|
if (recycle_action && recycle_action->GetClassCode()==STAND_ACTION)
|
|
{
|
|
stand_action *returnme=(stand_action*)(action_c*)recycle_action;
|
|
recycle_action = NULL;
|
|
returnme->Init(od, oa, newanim, destination, facing, target, timeout, fullAnimation);
|
|
return returnme;
|
|
}
|
|
|
|
return new stand_action(od, oa, newanim, destination, facing, target, timeout,fullAnimation);
|
|
}
|
|
|
|
action_c *ai_c::PainAction(decision_c *od, action_c *oa, mmove_t *newanim, edict_t &monster, edict_t *other, float kick, int damage, float timeout, qboolean fullAnimation)
|
|
{
|
|
return new pain_action(od, oa, newanim, monster, other, kick, damage, timeout,fullAnimation);
|
|
}
|
|
|
|
action_c *ai_c::DeathAction(decision_c *od, action_c *oa, mmove_t *newanim, edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
return new death_action(od, oa, newanim, monster, inflictor, attacker, damage, point);
|
|
}
|
|
|
|
action_c *ai_c::DeathAction(decision_c *od, action_c *oa, mmove_t *newanim1, mmove_t *newanim2, edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
return new death_action(od, oa, newanim1, newanim2, monster, inflictor, attacker, damage, point);
|
|
}
|
|
|
|
action_c *ai_c::DeathArmorAction(decision_c *od, action_c *oa, mmove_t *newanim, edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
return new deatharmor_action(od, oa, newanim, monster, inflictor, attacker, damage, point);
|
|
}
|
|
|
|
action_c *ai_c::DeathArmorAction(decision_c *od, action_c *oa, mmove_t *newanim1, mmove_t *newanim2, edict_t &monster, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
return new deatharmor_action(od, oa, newanim1, newanim2, monster, inflictor, attacker, damage, point);
|
|
}
|
|
|
|
action_c *ai_c::AttackAction(decision_c *od, action_c *oa, mmove_t *newanim, edict_t *attackTarget, vec3_t attackPos, vec3_t facing, float timeout, qboolean fullAnimation, bool shouldKill, int NullTarget)
|
|
{
|
|
return new shoot_attack_action(od, oa, newanim, attackTarget, attackPos, facing, timeout,fullAnimation, shouldKill, NullTarget);
|
|
}
|
|
|
|
action_c *ai_c::JumpAction(decision_c *od, action_c *oa, mmove_t *newanim, vec3_t destination, vec3_t facing, float timeout, qboolean fullAnimation)
|
|
{
|
|
return new jump_action(od, oa, newanim, destination, facing, timeout,fullAnimation);
|
|
}
|
|
|
|
action_c *ai_c::FallAction(decision_c *od, action_c *oa, mmove_t *newanim, mmove_t *newanim2, vec3_t destination, vec3_t facing, float timeout, qboolean fullAnimation)
|
|
{
|
|
return new fall_action(od, oa, newanim, newanim2, destination, facing, timeout,fullAnimation);
|
|
}
|
|
|
|
action_c *ai_c::SurrenderAction(decision_c *od, action_c *oa, mmove_t *newanim, edict_t *surrenderTo, vec3_t attackerPos, vec3_t facing, float timeout, qboolean fullAnimation)
|
|
{
|
|
return new surrender_action(od, oa, newanim, surrenderTo, attackerPos, facing, timeout,fullAnimation);
|
|
}
|
|
|
|
action_c *ai_c::CaptureAction(decision_c *od, action_c *oa, mmove_t *newanim, edict_t *attackTarget, vec3_t attackPos, vec3_t facing, float timeout, qboolean fullAnimation)
|
|
{
|
|
return new capture_action(od, oa, newanim, attackTarget, attackPos, facing, timeout,fullAnimation);
|
|
}
|
|
|
|
action_c *ai_c::EndScriptAction(decision_c *od)
|
|
{
|
|
return new endscript_action(od);
|
|
}
|
|
|
|
void ai_c::RequestMoveOutOfWay(vec3_t moveDir)
|
|
{
|
|
VectorCopy(moveDir, requestedMoveDir);
|
|
}
|
|
|
|
ai_c *ai_c::Create(int classcode, edict_t *monster, char *ghoulname, char *subname)
|
|
{
|
|
ai_c *new_ai = (ai_c*)NewClassForCode(classcode);
|
|
new_ai->SetEnt(monster);
|
|
new_ai->AddBody(monster);
|
|
new_ai->Init(monster, ghoulname, subname);
|
|
if((classcode >= AI_ECTO_SKINLEADER)&&(classcode <= AI_FEMALE_TAYLOR))
|
|
{
|
|
new_ai->mySkills = enemySkills[classcode - AI_ECTO_SKINLEADER];//easiest way to do all this with minimal code
|
|
if(monster)
|
|
{
|
|
float myVal = gi.RegionDistance(monster->s.origin);
|
|
if(myVal != -1)
|
|
{ //sigh... this is pretty general
|
|
if(myVal > 1200)
|
|
{
|
|
new_ai->mySkills.adjustAccuracy(.66);//?
|
|
}
|
|
else
|
|
{
|
|
new_ai->mySkills.adjustAccuracy(.33);//?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
new_ai->mySkills.InitStyles(monster);
|
|
return new_ai;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// CAISkills Stuff
|
|
|
|
tempStyle_t enemyActionStyles[] =
|
|
{
|
|
// shot at, no visibility friend killed attacked out of range too close explosive
|
|
{ESSA_ATTACK_DIRECTION, ESFK_IGNORE, ESAT_APPROACH, ESOR_ATTACK_IF_POSSIBLE, ESTC_HOLD_POSITION, ESEX_IGNORE}, //EST_AGGRESSIVE
|
|
{ESSA_RETREAT_TO_COVER, ESFK_IGNORE, ESAT_DUCK, ESOR_APPROACH, ESTC_ROLL_TO_SIDE, ESEX_DODGE}, //EST_EVASIVE
|
|
{ESSA_FLEE_TO_COVER, ESFK_RETREAT_TO_AMBUSH, ESAT_COVER_AND_AMBUSH, ESOR_APPROACH, ESTC_RETREAT, ESEX_DODGE}, //EST_SNEAKY
|
|
{ESSA_ATTACK_DIRECTION, ESFK_RETREAT_TO_COVER, ESAT_CIRCLE, ESOR_APPROACH, ESTC_CIRCLE, ESEX_DODGE}, //EST_TACTICAL
|
|
{ESSA_FLEE_TO_COVER, ESFK_RETREAT_TO_COVER, ESAT_COVER_AND_ATTACK, ESOR_HOLD_POSITION, ESTC_RETREAT, ESEX_DODGE}, //EST_COWARDLY
|
|
{ESSA_RETREAT_TO_COVER, ESFK_RETREAT_TO_ALLIES, ESAT_COVER_AND_ATTACK, ESOR_HOLD_POSITION, ESTC_RETREAT, ESEX_DODGE}, //EST_CAUTIOUS
|
|
};
|
|
|
|
CAISkills::CAISkills(void)
|
|
{
|
|
idealRangeMin = 100;
|
|
idealRangeMax = 300;
|
|
dodgeAbility = .8;
|
|
|
|
shootingAccuracy = 1.0;
|
|
|
|
hesitation = 0.0;
|
|
aimTime = 0.0f;
|
|
cashValue = 0.0f;
|
|
turnSpeed = 1.0;
|
|
|
|
flags = 0;
|
|
|
|
shotAt = 0;
|
|
friendsKilled = 0;
|
|
attacked = 0;
|
|
outOfRange = 0;
|
|
tooClose = 0;
|
|
explosive = 0;
|
|
}
|
|
|
|
void CAISkills::setSkills(float rangeMin, float rangeMax, float dodge, float accuracy,
|
|
float newHesitation, float newAimTime, float newCashValue, float newTurnSpeed, float newFlags)
|
|
{
|
|
idealRangeMin = rangeMin;
|
|
idealRangeMax = rangeMax;
|
|
dodgeAbility = dodge;
|
|
shootingAccuracy = accuracy;
|
|
hesitation = newHesitation;
|
|
aimTime = newAimTime;
|
|
cashValue = newCashValue;
|
|
flags = newFlags;
|
|
turnSpeed = newTurnSpeed;
|
|
}
|
|
|
|
void CAISkills::setStyles(int newShotAt, int newFriendsKilled, int newAttacked, int newOutOfRange,
|
|
int newTooClose, int newExplosive)
|
|
{
|
|
shotAt = newShotAt;
|
|
friendsKilled = newFriendsKilled;
|
|
attacked = newAttacked;
|
|
outOfRange = newOutOfRange;
|
|
tooClose = newTooClose;
|
|
explosive = newExplosive;
|
|
}
|
|
|
|
void CAISkills::setStyles(int styleNum)
|
|
{
|
|
if(styleNum >= 0 && styleNum < EST_NUM_STYLES)
|
|
{
|
|
setStyles(enemyActionStyles[styleNum].shotAt, enemyActionStyles[styleNum].friendsKilled,
|
|
enemyActionStyles[styleNum].attacked, enemyActionStyles[styleNum].outOfRange,
|
|
enemyActionStyles[styleNum].tooClose, enemyActionStyles[styleNum].explosive);
|
|
}
|
|
}
|
|
|
|
CAISkills::CAISkills(float rangeMin, float rangeMax, float dodge, float accuracy,
|
|
float newHesitation, float newAimTime, float newCashValue, float newTurnSpeed, float newFlags,
|
|
int newShotAt, int newFriendsKilled, int newAttacked, int newOutOfRange,
|
|
int newTooClose, int newExplosive)
|
|
{
|
|
setSkills(rangeMin, rangeMax, dodge, accuracy, newHesitation, newAimTime, newCashValue, newTurnSpeed, newFlags);
|
|
setStyles(newShotAt, newFriendsKilled, newAttacked, newOutOfRange, newTooClose, newExplosive);
|
|
}
|
|
|
|
void CAISkills::operator=(CAISkills &that)
|
|
{
|
|
idealRangeMin = that.idealRangeMin;
|
|
idealRangeMax = that.idealRangeMax;
|
|
dodgeAbility = that.dodgeAbility;
|
|
|
|
shootingAccuracy = that.shootingAccuracy;
|
|
|
|
hesitation = that.hesitation;
|
|
aimTime = that.aimTime;
|
|
cashValue = that.cashValue;
|
|
turnSpeed = that.turnSpeed;
|
|
flags = that.flags;
|
|
|
|
shotAt = that.shotAt;
|
|
friendsKilled = that.friendsKilled;
|
|
attacked = that.attacked;
|
|
outOfRange = that.outOfRange;
|
|
tooClose = that.tooClose;
|
|
explosive = that.explosive;
|
|
|
|
//Eh? BAD BAD BAD - this is naughty! People pay me to write this kind of stuff? Incredible!
|
|
if((!strncmp(level.mapname, "tsr", 3))||
|
|
(!strncmp(level.mapname, "trn", 3))||
|
|
((!(strncmp(level.mapname, "irq", 3)))&&(level.mapname[4] == 'a')))
|
|
{
|
|
hesitation += .25;
|
|
shootingAccuracy *= .75;
|
|
aimTime += 2;
|
|
cashValue *= .5;
|
|
}
|
|
}
|
|
|
|
void CAISkills::InitStyles(edict_t *monster)
|
|
{
|
|
if(!monster)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(monster->personality)
|
|
{
|
|
setStyles(monster->personality - 1);
|
|
}
|
|
else
|
|
{
|
|
//default it properly here later based on flags set in the skill stuff here
|
|
setStyles(gi.irand(0, EST_NUM_STYLES-1));
|
|
}
|
|
} |