#include "g_local.h" #include "ai_private.h" sense_c aSenseC; sound_sense aSoundSenseC; sight_sense aSightSenseC; alarm_sense aAlarmSenseC; sense_c *sense_c::NewClassForCode(int code) { switch (code) { default: gi.dprintf("ERROR: invalid sense class code: %d\n",code); case SENSE: return new sense_c; case ALARM_SENSE: return new alarm_sense; case SIGHT_SENSE: return new sight_sense; case NORMALSIGHT_SENSE: return new normalsight_sense; case SOUND_SENSE: return new sound_sense; case OMNISCIENCE_SENSE: return new omniscience_sense; case MAGICSIGHT_SENSE: return new magicsight_sense; } } sense_c::sense_c(vec3_t new_origin, float new_time) { VectorCopy(new_origin, origin); time = new_time; mute_starttime = level.time-1; mute_endtime = level.time-1; mute_degree = 0; mute_recovercode = smute_recov_instant; sensed_client.time = - 10000.0; sensed_client.ent = NULL; sensed_client.senseType = AI_SENSETYPE_UNKNOWN; sensed_monster.time = - 10000.0; sensed_monster.ent = NULL; sensed_monster.senseType = AI_SENSETYPE_UNKNOWN; } sense_c::sense_c(void) { VectorClear(origin); time = level.time; mute_starttime = level.time-1; mute_endtime = level.time-1; mute_degree = 0; mute_recovercode = smute_recov_instant; sensed_client.time = - 10000.0; VectorClear(sensed_client.pos); sensed_client.ent = NULL; sensed_client.senseType = AI_SENSETYPE_UNKNOWN; sensed_monster.time = - 10000.0; VectorClear(sensed_monster.pos); sensed_monster.ent = NULL; sensed_monster.senseType = AI_SENSETYPE_UNKNOWN; } float sense_c::CurrentMutedLevel(void) { float timeVal; //if the muting has timed out, then forget it if ((mute_recovercode != smute_recov_none) && (mute_endtime <= level.time || mute_starttime >= level.time)) { return 0; } switch(mute_recovercode) { default: gi.dprintf("unknown sense muting recovery code: %d!\n", mute_recovercode); case smute_recov_none: return mute_degree; case smute_recov_instant: return mute_degree; case smute_recov_linear: return mute_degree*(mute_endtime-level.time)/(mute_endtime-mute_starttime); case smute_recov_exp: timeVal=(mute_endtime-level.time)/(mute_endtime-mute_starttime); return mute_degree*timeVal*timeVal; } } int sense_c::Range(edict_t *monster, edict_t *other) { vec3_t to_other; float dist_sq,mutetemp; VectorSubtract(other->s.origin, monster->s.origin, to_other); dist_sq = VectorLengthSquared(to_other); mutetemp = (1+CurrentMutedLevel()); mutetemp *= mutetemp; dist_sq *= mutetemp; return gmonster.Range(monster,other,dist_sq); } int sense_c::Range(edict_t *monster, vec3_t where) { vec3_t to_other; float dist_sq,mutetemp; VectorSubtract(where, monster->s.origin, to_other); dist_sq = VectorLengthSquared(to_other); mutetemp = (1+CurrentMutedLevel()); mutetemp *= mutetemp; dist_sq *= mutetemp; return gmonster.Range(monster,monster,dist_sq); } void sense_c::UpdateSensedClient(unsigned mask, sensedEntInfo_t &best_ent) { if (!MaskOK(mask)) { return; } if (best_ent.time > sensed_client.time) { return; } best_ent.ent = sensed_client.ent; VectorCopy(sensed_client.pos, best_ent.pos); best_ent.time = sensed_client.time; best_ent.senseType = sensed_client.senseType; } void sense_c::UpdateSensedMonster(unsigned mask, sensedEntInfo_t &best_ent) { if (!MaskOK(mask)) { return; } if (best_ent.time > sensed_monster.time) { return; } best_ent.ent = sensed_monster.ent; VectorCopy(sensed_monster.pos, best_ent.pos); best_ent.time = sensed_monster.time; best_ent.senseType = sensed_monster.senseType; } sense_c::sense_c(sense_c *orig) { VectorCopy(orig->origin, origin); time = orig->time; sensed_monster = orig->sensed_monster; sensed_client = orig->sensed_client; mute_recovercode = orig->mute_recovercode; mute_starttime = orig->mute_starttime; mute_endtime = orig->mute_endtime; mute_degree = orig->mute_degree; *(int *)&sensed_monster.ent = GetEdictNum(orig->sensed_monster.ent); *(int *)&sensed_client.ent = GetEdictNum(orig->sensed_client.ent); } void sense_c::Evaluate(sense_c *orig) { VectorCopy(orig->origin, origin); time = orig->time; sensed_monster = orig->sensed_monster; sensed_client = orig->sensed_client; mute_recovercode = orig->mute_recovercode; mute_starttime = orig->mute_starttime; mute_endtime = orig->mute_endtime; mute_degree = orig->mute_degree; sensed_monster.ent = GetEdictPtr((int)sensed_monster.ent); sensed_client.ent = GetEdictPtr((int)sensed_client.ent); } /********************************************************************************** **********************************************************************************/ alarm_sense::alarm_sense(vec3_t new_origin, float new_time, float new_radius) :sense_c(new_origin, new_time) { radius = new_radius; } qboolean alarm_sense::Evaluate(unsigned mask, ai_c &owner_ai, edict_t &monster) { if (!(mask & alarm_mask)) { return false; } return true; } alarm_sense::alarm_sense(alarm_sense *orig) : sense_c(orig) { radius = orig->radius; } void alarm_sense::Evaluate(alarm_sense *orig) { radius = orig->radius; sense_c::Evaluate(orig); } void alarm_sense::Read() { char loaded[sizeof(alarm_sense)]; gi.ReadFromSavegame('AIAS', loaded, sizeof(alarm_sense)); Evaluate((alarm_sense *)loaded); } void alarm_sense::Write() { alarm_sense *savable; savable = new alarm_sense(this); gi.AppendToSavegame('AIAS', savable, sizeof(*savable)); delete savable; } /********************************************************************************** **********************************************************************************/ //general fixme for sense evaluation: need a solid set of criteria for when to //replace the enemy in monster's ai, and when to just leave what's there. //whether this should be in senses or ai or what remains to be seen. //visibility is poor, but to simulate monsters anticipating player movement, allow position update-- //but no new sighting, and no updating of sight time void sight_sense::PoorVisibilityUpdate(ai_c &owner_ai, edict_t *monster, edict_t *sight_ent, float leeway_time) { //do for both clients and monsters if (sensed_client.ent == sight_ent && level.time - sensed_client.time < leeway_time) { VectorCopy(sight_ent->s.origin, sensed_client.pos); sensed_client.senseType = AI_SENSETYPE_SIGHT_OBSTRUCTED; } else if (sensed_monster.ent == sight_ent && level.time - sensed_monster.time < leeway_time) { VectorCopy(sight_ent->s.origin, sensed_monster.pos); sensed_monster.senseType = AI_SENSETYPE_SIGHT_OBSTRUCTED; } } void sight_sense::Look(ai_c &owner_ai, edict_t *monster, edict_t *sight_ent) { edict_t *client=sight_ent; int r; if (!client) return; // no clients to get mad at // if the entity went away, forget it if (!client->inuse) return; //i see myself??? this is a thing that makes no sense. if (client == monster) { return; } if (client->client) { if (client->flags & FL_NOTARGET || (OnSameTeam(monster,client) && !owner_ai.GetAbusedByTeam()))// i don't play with cheaters. return; } else if (!client->ai) { return; } if((client->spawnflags & SPAWNFLAG_HOSTAGE)&&(monster->flags & FL_SPAWNED_IN)) { // spawned guys have no interest in hostages - just in nosy players return; } if((!strcmp(client->classname, "m_x_mmerc"))) { // people don't target Hawk - they're too afraid of him. return; } r = Range (monster, client); if (r == RANGE_FAR) return; // this is where we would check invisibility if (r != RANGE_MELEE)//if e's close enuff for melee, i just Know where e is, ok? { // is client in an spot too dark to be seen? //no technical reason for commenting this, but test levels aren't generally lit well--gets in way //addendum--monsters don't have their light level set validly, i don't think--gotten from client //fixme: need to do something similar for monsters /* if (client->client && client->light_level <= 5) { PoorVisibilityUpdate(owner_ai, monster, client, 6.0); return; } */ //take care of stealth biz if (client->svflags & SVF_ISHIDING) { PoorVisibilityUpdate(owner_ai, monster, client, 4.0); return; } if (!gmonster.Infront (monster, client)) { PoorVisibilityUpdate(owner_ai, monster, client, 4.0); return; } } //took this out of non-melee checks--can't allow superclose range bypass visibility check, have guys shootin through walls if (!gmonster.Visible (monster, client)) { PoorVisibilityUpdate(owner_ai, monster, client, 0.25); return; } //update appropriate slot for monster if (client->client) { sensed_client.ent = client; sensed_client.time = level.time; VectorCopy(client->s.origin, sensed_client.pos); sensed_client.senseType = AI_SENSETYPE_SIGHT_CLEAR; } else { sensed_monster.ent = client; sensed_monster.time = level.time; VectorCopy(client->s.origin, sensed_monster.pos); sensed_monster.senseType = AI_SENSETYPE_SIGHT_CLEAR; } } qboolean sight_sense::Evaluate(unsigned mask, ai_c &owner_ai, edict_t &monster) { if (!(mask & sight_mask)) { return false; } Look(owner_ai,&monster,level.sight_client); // level will continually update who we can be mad at Look(owner_ai,&monster,level.sight_monster); // level will continually update who we can be mad at return false; } void sight_sense::Mute(unsigned mask, float degree, smute_recovery recovery_code, float recovery_time) { if (!(mask & sight_mask)) { return; } mute_starttime=level.time; mute_endtime=level.time+recovery_time; mute_degree=degree; mute_recovercode=recovery_code; } sight_sense::sight_sense(sight_sense *orig) : sense_c(orig) { } void sight_sense::Evaluate(sight_sense *orig) { sense_c::Evaluate(orig); } void sight_sense::Read() { char loaded[sizeof(sight_sense)]; gi.ReadFromSavegame('AISS', loaded, sizeof(sight_sense)); Evaluate((sight_sense *)loaded); } void sight_sense::Write() { sight_sense *savable; savable = new sight_sense(this); gi.AppendToSavegame('AISS', savable, sizeof(*savable)); delete savable; } /********************************************************************************** **********************************************************************************/ normalsight_sense::normalsight_sense(normalsight_sense *orig) : sight_sense(orig) { } void normalsight_sense::Evaluate(normalsight_sense *orig) { sight_sense::Evaluate(orig); } void normalsight_sense::Write() { normalsight_sense *savable; savable = new normalsight_sense(this); gi.AppendToSavegame('AINS', savable, sizeof(*this)); delete savable; } void normalsight_sense::Read() { char loaded[sizeof(normalsight_sense)]; gi.ReadFromSavegame('AINS', loaded, sizeof(normalsight_sense)); Evaluate((normalsight_sense *)loaded); } /********************************************************************************** **********************************************************************************/ void sound_sense::RegisterEvent(vec3_t event_origin, float event_time, edict_t *event_edict, ai_sensetype_e event_code) { if (numsounds >= MAX_AISOUNDS) { return; } VectorCopy(event_origin, sounds[numsounds].heardLocation); VectorCopy(event_edict->s.origin, sounds[numsounds].origin); sounds[numsounds].ent = event_edict; sounds[numsounds].code = event_code; sounds[numsounds].time = event_time; numsounds++; } void sound_sense::Mute(unsigned mask, float degree, smute_recovery recovery_code, float recovery_time) { if (!(mask & sound_mask)) { return; } mute_starttime=level.time+0.3; mute_endtime=level.time+recovery_time+0.3; mute_degree=degree; mute_recovercode=recovery_code; } qboolean sound_sense::Evaluate(unsigned mask, ai_c &owner_ai, edict_t &monster) { int r,i; //these fellas were here to test more realistic reactions, but i ended up focusing more on Getting a reaction, thus they're commented out but they should be fine vec3_t toHeardLocation; float toHeardLocationDistSq; if (!(mask & sound_mask)) { return false; } for (i=0;iflags & FL_NOTARGET) { continue; } //always hear magical sounds... if (sounds[i].code == AI_SENSETYPE_SOUND_MAGICAL) { if (sounds[i].ent->client) { sensed_client.time = level.time; VectorCopy(sounds[i].origin, sensed_client.pos); sensed_client.ent = sounds[i].ent; sensed_client.senseType = sounds[i].code; } else { sensed_monster.time = level.time; VectorCopy(sounds[i].origin, sensed_monster.pos); sensed_monster.ent = sounds[i].ent; sensed_monster.senseType = sounds[i].code; } continue; } r = Range (&monster, sounds[i].heardLocation); //din't hear it if (r==RANGE_FAR) { continue; } //wretch! clean me up! fixme! cleaner i say! //update appropriate info depending on whether sound is coming from player or monster if (sounds[i].ent) { VectorSubtract(sounds[i].heardLocation, monster.s.origin, toHeardLocation); toHeardLocationDistSq=VectorLengthSquared(toHeardLocation); //if it's a wake-up that's really close, it was probably me--ignore it if (sounds[i].code==AI_SENSETYPE_SOUND_WAKEUP && toHeardLocationDistSq < 400) { continue; } if (sounds[i].ent->client) { //fixme: if i don't have an enemy, should update position, but differently if (!sensed_client.ent || level.time - sensed_client.time > 10.0 || sensed_client.ent == sounds[i].ent) { if (sounds[i].code == AI_SENSETYPE_SOUND_SELF || sounds[i].code == AI_SENSETYPE_SOUND_WEAPON) { sensed_client.time = level.time-5.0; // if (toHeardLocationDistSq<40000) // { VectorCopy(sounds[i].origin, sensed_client.pos); // } // else // { // VectorCopy(sounds[i].heardLocation, sensed_client.pos); // } sensed_client.ent = sounds[i].ent; sensed_client.senseType = sounds[i].code; } else if (!sensed_client.ent || level.time - sensed_client.time > 20.0)//weapon impact or whiz { sensed_client.time = level.time-10.0; // if (toHeardLocationDistSq<40000) // { VectorCopy(sounds[i].origin, sensed_client.pos); // } // else // { // VectorCopy(sounds[i].heardLocation, sensed_client.pos); // } sensed_client.ent = sounds[i].ent; sensed_client.senseType = sounds[i].code; } } } else { //fixme: if i don't have an enemy, should update position, but differently if (!sensed_monster.ent || level.time - sensed_monster.time > 10.0) { if (sounds[i].code == AI_SENSETYPE_SOUND_SELF || sounds[i].code == AI_SENSETYPE_SOUND_WEAPON) { sensed_monster.time = level.time-5.0; // if (toHeardLocationDistSq<40000) // { VectorCopy(sounds[i].origin, sensed_monster.pos); // } // else // { // VectorCopy(sounds[i].heardLocation, sensed_monster.pos); // } sensed_monster.ent = sounds[i].ent; sensed_client.senseType = sounds[i].code; } else if (!sensed_monster.ent || level.time - sensed_monster.time > 20.0)//weapon impact or whiz { sensed_monster.time = level.time-10.0; // if (toHeardLocationDistSq<40000) // { VectorCopy(sounds[i].origin, sensed_monster.pos); // } // else // { // VectorCopy(sounds[i].heardLocation, sensed_monster.pos); // } sensed_monster.ent = sounds[i].ent; sensed_monster.senseType = sounds[i].code; } } } } } numsounds=0; return false; } sound_sense::sound_sense(sound_sense *orig) : sense_c(orig) { int i; numsounds = orig->numsounds; for(i = 0; i < numsounds; i++) { sounds[i] = orig->sounds[i]; *(int *)&sounds[i].ent = GetEdictNum(orig->sounds[i].ent); } for( ; i < MAX_AISOUNDS; i++) { VectorClear(sounds[i].heardLocation); VectorClear(sounds[i].origin); sounds[i].ent = NULL; sounds[i].code = AI_SENSETYPE_UNKNOWN; sounds[i].time = 0.0F; } } void sound_sense::Evaluate(sound_sense *orig) { int i; numsounds = orig->numsounds; for(i = 0; i < numsounds; i++) { sounds[i] = orig->sounds[i]; sounds[i].ent = GetEdictPtr((int)sounds[i].ent); } sense_c::Evaluate(orig); } void sound_sense::Read() { char loaded[sizeof(sound_sense)]; gi.ReadFromSavegame('AISO', loaded, sizeof(sound_sense)); Evaluate((sound_sense *)loaded); } void sound_sense::Write() { sound_sense *savable; savable = new sound_sense(this); gi.AppendToSavegame('AISO', savable, sizeof(*savable)); delete savable; } ///////////////////////////////////////////////////////////////////////////////// void omniscience_sense::Look(ai_c &owner_ai, edict_t *monster, edict_t *sight_ent) { edict_t *client=sight_ent; if (!client) return; // no clients to get mad at // if the entity went away, forget it if (!client->inuse) return; //i see myself??? this is a thing that makes no sense. if (client == monster) { return; } if (client->client) { if (client->flags & FL_NOTARGET || (OnSameTeam(monster,client) && !owner_ai.GetAbusedByTeam()))// i don't play with cheaters. return; } else if (!client->ai) { return; } //no checks--i see all //update appropriate slot for monster if (client->client) { sensed_client.ent = client; sensed_client.time = level.time; VectorCopy(client->s.origin, sensed_client.pos); sensed_client.senseType = AI_SENSETYPE_MAGIC; } else { sensed_monster.ent = client; sensed_monster.time = level.time; VectorCopy(client->s.origin, sensed_monster.pos); sensed_monster.senseType = AI_SENSETYPE_MAGIC; } } omniscience_sense::omniscience_sense(omniscience_sense *orig) : sight_sense(orig) { } void omniscience_sense::Evaluate(omniscience_sense *orig) { sight_sense::Evaluate(orig); } void omniscience_sense::Read() { char loaded[sizeof(omniscience_sense)]; gi.ReadFromSavegame('AINS', loaded, sizeof(omniscience_sense)); Evaluate((omniscience_sense *)loaded); } void omniscience_sense::Write() { omniscience_sense *savable; savable = new omniscience_sense(this); gi.AppendToSavegame('AINS', savable, sizeof(*this)); delete savable; } void omniscience_sense::Mute(unsigned mask, float degree, smute_recovery recovery_code, float recovery_time) { return; } ///////////////////////////////////////////////////////////////////////////////// //geh. this could prolly be smooshed into regular sight, but this is the quick way to get it working for blind guys void magicsight_sense::Look(ai_c &owner_ai, edict_t *monster, edict_t *sight_ent) { edict_t *client=sight_ent; if (!client) return; // no clients to get mad at // if the entity went away, forget it if (!client->inuse) return; //i see myself??? this is a thing that makes no sense. if (client == monster) { return; } if (client->client) { if (client->flags & FL_NOTARGET || (OnSameTeam(monster,client) && !owner_ai.GetAbusedByTeam()))// i don't play with cheaters. return; } else// if (!client->ai)//no magically seeing other monsters! { return; } //simple dist check--i see all vec3_t to_seen; float toSeenDistSq; VectorSubtract(client->s.origin, monster->s.origin, to_seen); toSeenDistSq=VectorLengthSquared(to_seen); if (toSeenDistSq>distSq) { return; } //update appropriate slot for monster if (client->client) { sensed_client.ent = client; sensed_client.time = level.time; VectorCopy(client->s.origin, sensed_client.pos); } else { sensed_monster.ent = client; sensed_monster.time = level.time; VectorCopy(client->s.origin, sensed_monster.pos); } } magicsight_sense::magicsight_sense(magicsight_sense *orig) : sight_sense(orig) { distSq = orig->distSq; } void magicsight_sense::Evaluate(magicsight_sense *orig) { distSq = orig->distSq; sight_sense::Evaluate(orig); } void magicsight_sense::Read() { char loaded[sizeof(magicsight_sense)]; gi.ReadFromSavegame('AINS', loaded, sizeof(loaded)); Evaluate((magicsight_sense *)loaded); } void magicsight_sense::Write() { magicsight_sense *savable; savable = new magicsight_sense(this); gi.AppendToSavegame('AINS', savable, sizeof(*this)); delete savable; } void magicsight_sense::Mute(unsigned mask, float degree, smute_recovery recovery_code, float recovery_time) { return; } //end