12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118 |
- /*
- Copyright (C) 1997-2001 Id Software, Inc.
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License
- as published by the Free Software Foundation; either version 2
- of the License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
- // g_ai.c
- #include "g_local.h"
- qboolean FindTarget (edict_t *self);
- extern cvar_t *maxclients;
- qboolean ai_checkattack (edict_t *self, float dist);
- qboolean enemy_vis;
- qboolean enemy_infront;
- int enemy_range;
- float enemy_yaw;
- //============================================================================
- /*
- =================
- AI_SetSightClient
- Called once each frame to set level.sight_client to the
- player to be checked for in findtarget.
- If all clients are either dead or in notarget, sight_client
- will be null.
- In coop games, sight_client will cycle between the clients.
- =================
- */
- void AI_SetSightClient (void)
- {
- edict_t *ent;
- int start, check;
- if (level.sight_client == NULL)
- start = 1;
- else
- start = level.sight_client - g_edicts;
- check = start;
- while (1)
- {
- check++;
- if (check > game.maxclients)
- check = 1;
- ent = &g_edicts[check];
- if (ent->inuse
- && ent->health > 0
- && !(ent->flags & FL_NOTARGET) )
- {
- level.sight_client = ent;
- return; // got one
- }
- if (check == start)
- {
- level.sight_client = NULL;
- return; // nobody to see
- }
- }
- }
- //============================================================================
- /*
- =============
- ai_move
- Move the specified distance at current facing.
- This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward
- ==============
- */
- void ai_move (edict_t *self, float dist)
- {
- M_walkmove (self, self->s.angles[YAW], dist);
- }
- /*
- =============
- ai_stand
- Used for standing around and looking for players
- Distance is for slight position adjustments needed by the animations
- ==============
- */
- void ai_stand (edict_t *self, float dist)
- {
- vec3_t v;
- if (dist)
- M_walkmove (self, self->s.angles[YAW], dist);
- if (self->monsterinfo.aiflags & AI_STAND_GROUND)
- {
- if (self->enemy)
- {
- VectorSubtract (self->enemy->s.origin, self->s.origin, v);
- self->ideal_yaw = vectoyaw(v);
- if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
- {
- self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
- self->monsterinfo.run (self);
- }
- M_ChangeYaw (self);
- ai_checkattack (self, 0);
- }
- else
- FindTarget (self);
- return;
- }
- if (FindTarget (self))
- return;
-
- if (level.time > self->monsterinfo.pausetime)
- {
- self->monsterinfo.walk (self);
- return;
- }
- if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time))
- {
- if (self->monsterinfo.idle_time)
- {
- self->monsterinfo.idle (self);
- self->monsterinfo.idle_time = level.time + 15 + random() * 15;
- }
- else
- {
- self->monsterinfo.idle_time = level.time + random() * 15;
- }
- }
- }
- /*
- =============
- ai_walk
- The monster is walking it's beat
- =============
- */
- void ai_walk (edict_t *self, float dist)
- {
- M_MoveToGoal (self, dist);
- // check for noticing a player
- if (FindTarget (self))
- return;
- if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time))
- {
- if (self->monsterinfo.idle_time)
- {
- self->monsterinfo.search (self);
- self->monsterinfo.idle_time = level.time + 15 + random() * 15;
- }
- else
- {
- self->monsterinfo.idle_time = level.time + random() * 15;
- }
- }
- }
- /*
- =============
- ai_charge
- Turns towards target and advances
- Use this call with a distnace of 0 to replace ai_face
- ==============
- */
- void ai_charge (edict_t *self, float dist)
- {
- vec3_t v;
- VectorSubtract (self->enemy->s.origin, self->s.origin, v);
- self->ideal_yaw = vectoyaw(v);
- M_ChangeYaw (self);
- if (dist)
- M_walkmove (self, self->s.angles[YAW], dist);
- }
- /*
- =============
- ai_turn
- don't move, but turn towards ideal_yaw
- Distance is for slight position adjustments needed by the animations
- =============
- */
- void ai_turn (edict_t *self, float dist)
- {
- if (dist)
- M_walkmove (self, self->s.angles[YAW], dist);
- if (FindTarget (self))
- return;
-
- M_ChangeYaw (self);
- }
- /*
- .enemy
- Will be world if not currently angry at anyone.
- .movetarget
- The next path spot to walk toward. If .enemy, ignore .movetarget.
- When an enemy is killed, the monster will try to return to it's path.
- .hunt_time
- Set to time + something when the player is in sight, but movement straight for
- him is blocked. This causes the monster to use wall following code for
- movement direction instead of sighting on the player.
- .ideal_yaw
- A yaw angle of the intended direction, which will be turned towards at up
- to 45 deg / state. If the enemy is in view and hunt_time is not active,
- this will be the exact line towards the enemy.
- .pausetime
- A monster will leave it's stand state and head towards it's .movetarget when
- time > .pausetime.
- walkmove(angle, speed) primitive is all or nothing
- */
- /*
- =============
- range
- returns the range catagorization of an entity reletive to self
- 0 melee range, will become hostile even if back is turned
- 1 visibility and infront, or visibility and show hostile
- 2 infront and show hostile
- 3 only triggered by damage
- =============
- */
- int range (edict_t *self, edict_t *other)
- {
- vec3_t v;
- float len;
- VectorSubtract (self->s.origin, other->s.origin, v);
- len = VectorLength (v);
- if (len < MELEE_DISTANCE)
- return RANGE_MELEE;
- if (len < 500)
- return RANGE_NEAR;
- if (len < 1000)
- return RANGE_MID;
- return RANGE_FAR;
- }
- /*
- =============
- visible
- returns 1 if the entity is visible to self, even if not infront ()
- =============
- */
- qboolean visible (edict_t *self, edict_t *other)
- {
- vec3_t spot1;
- vec3_t spot2;
- trace_t trace;
- VectorCopy (self->s.origin, spot1);
- spot1[2] += self->viewheight;
- VectorCopy (other->s.origin, spot2);
- spot2[2] += other->viewheight;
- trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
-
- if (trace.fraction == 1.0)
- return true;
- return false;
- }
- /*
- =============
- infront
- returns 1 if the entity is in front (in sight) of self
- =============
- */
- qboolean infront (edict_t *self, edict_t *other)
- {
- vec3_t vec;
- float dot;
- vec3_t forward;
-
- AngleVectors (self->s.angles, forward, NULL, NULL);
- VectorSubtract (other->s.origin, self->s.origin, vec);
- VectorNormalize (vec);
- dot = DotProduct (vec, forward);
-
- if (dot > 0.3)
- return true;
- return false;
- }
- //============================================================================
- void HuntTarget (edict_t *self)
- {
- vec3_t vec;
- self->goalentity = self->enemy;
- if (self->monsterinfo.aiflags & AI_STAND_GROUND)
- self->monsterinfo.stand (self);
- else
- self->monsterinfo.run (self);
- VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
- self->ideal_yaw = vectoyaw(vec);
- // wait a while before first attack
- if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
- AttackFinished (self, 1);
- }
- void FoundTarget (edict_t *self)
- {
- // let other monsters see this monster for a while
- if (self->enemy->client)
- {
- level.sight_entity = self;
- level.sight_entity_framenum = level.framenum;
- level.sight_entity->light_level = 128;
- }
- self->show_hostile = level.time + 1; // wake up other monsters
- VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
- self->monsterinfo.trail_time = level.time;
- if (!self->combattarget)
- {
- HuntTarget (self);
- return;
- }
- self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
- if (!self->movetarget)
- {
- self->goalentity = self->movetarget = self->enemy;
- HuntTarget (self);
- gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget);
- return;
- }
- // clear out our combattarget, these are a one shot deal
- self->combattarget = NULL;
- self->monsterinfo.aiflags |= AI_COMBAT_POINT;
- // clear the targetname, that point is ours!
- self->movetarget->targetname = NULL;
- self->monsterinfo.pausetime = 0;
- // run for it
- self->monsterinfo.run (self);
- }
- /*
- ===========
- FindTarget
- Self is currently not attacking anything, so try to find a target
- Returns TRUE if an enemy was sighted
- When a player fires a missile, the point of impact becomes a fakeplayer so
- that monsters that see the impact will respond as if they had seen the
- player.
- To avoid spending too much time, only a single client (or fakeclient) is
- checked each frame. This means multi player games will have slightly
- slower noticing monsters.
- ============
- */
- qboolean FindTarget (edict_t *self)
- {
- edict_t *client;
- qboolean heardit;
- int r;
- if (self->monsterinfo.aiflags & AI_GOOD_GUY)
- {
- if (self->goalentity && self->goalentity->inuse && self->goalentity->classname)
- {
- if (strcmp(self->goalentity->classname, "target_actor") == 0)
- return false;
- }
- //FIXME look for monsters?
- return false;
- }
- // if we're going to a combat point, just proceed
- if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
- return false;
- // if the first spawnflag bit is set, the monster will only wake up on
- // really seeing the player, not another monster getting angry or hearing
- // something
- // revised behavior so they will wake up if they "see" a player make a noise
- // but not weapon impact/explosion noises
- heardit = false;
- if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
- {
- client = level.sight_entity;
- if (client->enemy == self->enemy)
- {
- return false;
- }
- }
- else if (level.sound_entity_framenum >= (level.framenum - 1))
- {
- client = level.sound_entity;
- heardit = true;
- }
- else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
- {
- client = level.sound2_entity;
- heardit = true;
- }
- else
- {
- client = level.sight_client;
- if (!client)
- return false; // no clients to get mad at
- }
- // if the entity went away, forget it
- if (!client->inuse)
- return false;
- if (client == self->enemy)
- return true; // JDC false;
- if (client->client)
- {
- if (client->flags & FL_NOTARGET)
- return false;
- }
- else if (client->svflags & SVF_MONSTER)
- {
- if (!client->enemy)
- return false;
- if (client->enemy->flags & FL_NOTARGET)
- return false;
- }
- else if (heardit)
- {
- if (client->owner->flags & FL_NOTARGET)
- return false;
- }
- else
- return false;
- if (!heardit)
- {
- r = range (self, client);
- if (r == RANGE_FAR)
- return false;
- // this is where we would check invisibility
- // is client in an spot too dark to be seen?
- if (client->light_level <= 5)
- return false;
- if (!visible (self, client))
- {
- return false;
- }
- if (r == RANGE_NEAR)
- {
- if (client->show_hostile < level.time && !infront (self, client))
- {
- return false;
- }
- }
- else if (r == RANGE_MID)
- {
- if (!infront (self, client))
- {
- return false;
- }
- }
- self->enemy = client;
- if (strcmp(self->enemy->classname, "player_noise") != 0)
- {
- self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
- if (!self->enemy->client)
- {
- self->enemy = self->enemy->enemy;
- if (!self->enemy->client)
- {
- self->enemy = NULL;
- return false;
- }
- }
- }
- }
- else // heardit
- {
- vec3_t temp;
- if (self->spawnflags & 1)
- {
- if (!visible (self, client))
- return false;
- }
- else
- {
- if (!gi.inPHS(self->s.origin, client->s.origin))
- return false;
- }
- VectorSubtract (client->s.origin, self->s.origin, temp);
- if (VectorLength(temp) > 1000) // too far to hear
- {
- return false;
- }
- // check area portals - if they are different and not connected then we can't hear it
- if (client->areanum != self->areanum)
- if (!gi.AreasConnected(self->areanum, client->areanum))
- return false;
- self->ideal_yaw = vectoyaw(temp);
- M_ChangeYaw (self);
- // hunt the sound for a bit; hopefully find the real player
- self->monsterinfo.aiflags |= AI_SOUND_TARGET;
- self->enemy = client;
- }
- //
- // got one
- //
- FoundTarget (self);
- if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight))
- self->monsterinfo.sight (self, self->enemy);
- return true;
- }
- //=============================================================================
- /*
- ============
- FacingIdeal
- ============
- */
- qboolean FacingIdeal(edict_t *self)
- {
- float delta;
- delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
- if (delta > 45 && delta < 315)
- return false;
- return true;
- }
- //=============================================================================
- qboolean M_CheckAttack (edict_t *self)
- {
- vec3_t spot1, spot2;
- float chance;
- trace_t tr;
- if (self->enemy->health > 0)
- {
- // see if any entities are in the way of the shot
- VectorCopy (self->s.origin, spot1);
- spot1[2] += self->viewheight;
- VectorCopy (self->enemy->s.origin, spot2);
- spot2[2] += self->enemy->viewheight;
- tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
- // do we have a clear shot?
- if (tr.ent != self->enemy)
- return false;
- }
-
- // melee attack
- if (enemy_range == RANGE_MELEE)
- {
- // don't always melee in easy mode
- if (skill->value == 0 && (rand()&3) )
- return false;
- if (self->monsterinfo.melee)
- self->monsterinfo.attack_state = AS_MELEE;
- else
- self->monsterinfo.attack_state = AS_MISSILE;
- return true;
- }
-
- // missile attack
- if (!self->monsterinfo.attack)
- return false;
-
- if (level.time < self->monsterinfo.attack_finished)
- return false;
-
- if (enemy_range == RANGE_FAR)
- return false;
- if (self->monsterinfo.aiflags & AI_STAND_GROUND)
- {
- chance = 0.4;
- }
- else if (enemy_range == RANGE_MELEE)
- {
- chance = 0.2;
- }
- else if (enemy_range == RANGE_NEAR)
- {
- chance = 0.1;
- }
- else if (enemy_range == RANGE_MID)
- {
- chance = 0.02;
- }
- else
- {
- return false;
- }
- if (skill->value == 0)
- chance *= 0.5;
- else if (skill->value >= 2)
- chance *= 2;
- if (random () < chance)
- {
- self->monsterinfo.attack_state = AS_MISSILE;
- self->monsterinfo.attack_finished = level.time + 2*random();
- return true;
- }
- if (self->flags & FL_FLY)
- {
- if (random() < 0.3)
- self->monsterinfo.attack_state = AS_SLIDING;
- else
- self->monsterinfo.attack_state = AS_STRAIGHT;
- }
- return false;
- }
- /*
- =============
- ai_run_melee
- Turn and close until within an angle to launch a melee attack
- =============
- */
- void ai_run_melee(edict_t *self)
- {
- self->ideal_yaw = enemy_yaw;
- M_ChangeYaw (self);
- if (FacingIdeal(self))
- {
- self->monsterinfo.melee (self);
- self->monsterinfo.attack_state = AS_STRAIGHT;
- }
- }
- /*
- =============
- ai_run_missile
- Turn in place until within an angle to launch a missile attack
- =============
- */
- void ai_run_missile(edict_t *self)
- {
- self->ideal_yaw = enemy_yaw;
- M_ChangeYaw (self);
- if (FacingIdeal(self))
- {
- self->monsterinfo.attack (self);
- self->monsterinfo.attack_state = AS_STRAIGHT;
- }
- };
- /*
- =============
- ai_run_slide
- Strafe sideways, but stay at aproximately the same range
- =============
- */
- void ai_run_slide(edict_t *self, float distance)
- {
- float ofs;
-
- self->ideal_yaw = enemy_yaw;
- M_ChangeYaw (self);
- if (self->monsterinfo.lefty)
- ofs = 90;
- else
- ofs = -90;
-
- if (M_walkmove (self, self->ideal_yaw + ofs, distance))
- return;
-
- self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
- M_walkmove (self, self->ideal_yaw - ofs, distance);
- }
- /*
- =============
- ai_checkattack
- Decides if we're going to attack or do something else
- used by ai_run and ai_stand
- =============
- */
- qboolean ai_checkattack (edict_t *self, float dist)
- {
- vec3_t temp;
- qboolean hesDeadJim;
- // this causes monsters to run blindly to the combat point w/o firing
- if (self->goalentity)
- {
- if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
- return false;
- if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
- {
- if ((level.time - self->enemy->teleport_time) > 5.0)
- {
- if (self->goalentity == self->enemy)
- if (self->movetarget)
- self->goalentity = self->movetarget;
- else
- self->goalentity = NULL;
- self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
- if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
- self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
- }
- else
- {
- self->show_hostile = level.time + 1;
- return false;
- }
- }
- }
- enemy_vis = false;
- // see if the enemy is dead
- hesDeadJim = false;
- if ((!self->enemy) || (!self->enemy->inuse))
- {
- hesDeadJim = true;
- }
- else if (self->monsterinfo.aiflags & AI_MEDIC)
- {
- if (self->enemy->health > 0)
- {
- hesDeadJim = true;
- self->monsterinfo.aiflags &= ~AI_MEDIC;
- }
- }
- else
- {
- if (self->monsterinfo.aiflags & AI_BRUTAL)
- {
- if (self->enemy->health <= -80)
- hesDeadJim = true;
- }
- else
- {
- if (self->enemy->health <= 0)
- hesDeadJim = true;
- }
- }
- if (hesDeadJim)
- {
- self->enemy = NULL;
- // FIXME: look all around for other targets
- if (self->oldenemy && self->oldenemy->health > 0)
- {
- self->enemy = self->oldenemy;
- self->oldenemy = NULL;
- HuntTarget (self);
- }
- else
- {
- if (self->movetarget)
- {
- self->goalentity = self->movetarget;
- self->monsterinfo.walk (self);
- }
- else
- {
- // we need the pausetime otherwise the stand code
- // will just revert to walking with no target and
- // the monsters will wonder around aimlessly trying
- // to hunt the world entity
- self->monsterinfo.pausetime = level.time + 100000000;
- self->monsterinfo.stand (self);
- }
- return true;
- }
- }
- self->show_hostile = level.time + 1; // wake up other monsters
- // check knowledge of enemy
- enemy_vis = visible(self, self->enemy);
- if (enemy_vis)
- {
- self->monsterinfo.search_time = level.time + 5;
- VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
- }
- // look for other coop players here
- // if (coop && self->monsterinfo.search_time < level.time)
- // {
- // if (FindTarget (self))
- // return true;
- // }
- enemy_infront = infront(self, self->enemy);
- enemy_range = range(self, self->enemy);
- VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
- enemy_yaw = vectoyaw(temp);
- // JDC self->ideal_yaw = enemy_yaw;
- if (self->monsterinfo.attack_state == AS_MISSILE)
- {
- ai_run_missile (self);
- return true;
- }
- if (self->monsterinfo.attack_state == AS_MELEE)
- {
- ai_run_melee (self);
- return true;
- }
- // if enemy is not currently visible, we will never attack
- if (!enemy_vis)
- return false;
- return self->monsterinfo.checkattack (self);
- }
- /*
- =============
- ai_run
- The monster has an enemy it is trying to kill
- =============
- */
- void ai_run (edict_t *self, float dist)
- {
- vec3_t v;
- edict_t *tempgoal;
- edict_t *save;
- qboolean new;
- edict_t *marker;
- float d1, d2;
- trace_t tr;
- vec3_t v_forward, v_right;
- float left, center, right;
- vec3_t left_target, right_target;
- // if we're going to a combat point, just proceed
- if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
- {
- M_MoveToGoal (self, dist);
- return;
- }
- if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
- {
- VectorSubtract (self->s.origin, self->enemy->s.origin, v);
- if (VectorLength(v) < 64)
- {
- self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
- self->monsterinfo.stand (self);
- return;
- }
- M_MoveToGoal (self, dist);
- if (!FindTarget (self))
- return;
- }
- if (ai_checkattack (self, dist))
- return;
- if (self->monsterinfo.attack_state == AS_SLIDING)
- {
- ai_run_slide (self, dist);
- return;
- }
- if (enemy_vis)
- {
- // if (self.aiflags & AI_LOST_SIGHT)
- // dprint("regained sight\n");
- M_MoveToGoal (self, dist);
- self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
- VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
- self->monsterinfo.trail_time = level.time;
- return;
- }
- // coop will change to another enemy if visible
- if (coop->value)
- { // FIXME: insane guys get mad with this, which causes crashes!
- if (FindTarget (self))
- return;
- }
- if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
- {
- M_MoveToGoal (self, dist);
- self->monsterinfo.search_time = 0;
- // dprint("search timeout\n");
- return;
- }
- save = self->goalentity;
- tempgoal = G_Spawn();
- self->goalentity = tempgoal;
- new = false;
- if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
- {
- // just lost sight of the player, decide where to go first
- // dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n");
- self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
- self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
- new = true;
- }
- if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
- {
- self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
- // dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n");
- // give ourself more time since we got this far
- self->monsterinfo.search_time = level.time + 5;
- if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
- {
- // dprint("was temp goal; retrying original\n");
- self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
- marker = NULL;
- VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
- new = true;
- }
- else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
- {
- self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
- marker = PlayerTrail_PickFirst (self);
- }
- else
- {
- marker = PlayerTrail_PickNext (self);
- }
- if (marker)
- {
- VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
- self->monsterinfo.trail_time = marker->timestamp;
- self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
- // dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n");
- // debug_drawline(self.origin, self.last_sighting, 52);
- new = true;
- }
- }
- VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
- d1 = VectorLength(v);
- if (d1 <= dist)
- {
- self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
- dist = d1;
- }
- VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
- if (new)
- {
- // gi.dprintf("checking for course correction\n");
- tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
- if (tr.fraction < 1)
- {
- VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
- d1 = VectorLength(v);
- center = tr.fraction;
- d2 = d1 * ((center+1)/2);
- self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
- AngleVectors(self->s.angles, v_forward, v_right, NULL);
- VectorSet(v, d2, -16, 0);
- G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
- tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
- left = tr.fraction;
- VectorSet(v, d2, 16, 0);
- G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
- tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
- right = tr.fraction;
- center = (d1*center)/d2;
- if (left >= center && left > right)
- {
- if (left < 1)
- {
- VectorSet(v, d2 * left * 0.5, -16, 0);
- G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
- // gi.dprintf("incomplete path, go part way and adjust again\n");
- }
- VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
- self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
- VectorCopy (left_target, self->goalentity->s.origin);
- VectorCopy (left_target, self->monsterinfo.last_sighting);
- VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
- self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
- // gi.dprintf("adjusted left\n");
- // debug_drawline(self.origin, self.last_sighting, 152);
- }
- else if (right >= center && right > left)
- {
- if (right < 1)
- {
- VectorSet(v, d2 * right * 0.5, 16, 0);
- G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
- // gi.dprintf("incomplete path, go part way and adjust again\n");
- }
- VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
- self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
- VectorCopy (right_target, self->goalentity->s.origin);
- VectorCopy (right_target, self->monsterinfo.last_sighting);
- VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
- self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
- // gi.dprintf("adjusted right\n");
- // debug_drawline(self.origin, self.last_sighting, 152);
- }
- }
- // else gi.dprintf("course was fine\n");
- }
- M_MoveToGoal (self, dist);
- G_FreeEdict(tempgoal);
- if (self)
- self->goalentity = save;
- }
|