#include "g_local.h"
bool FindTarget(edict_t *self);
extern cvar_t *maxclients;
bool ai_checkattack(edict_t *self, float dist);
bool enemy_vis;
bool enemy_infront;
int enemy_range;
float enemy_yaw;
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_read_health(ent)->value > 0 && !(ent->flags & FL_NOTARGET)) {
level.sight_client = ent;
return; }
if(check == start) {
level.sight_client = NULL;
return; }
}
}
void ai_move(edict_t *self, float dist) { M_walkmove(self, self->s.angles[YAW], dist); }
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;
}
}
}
void ai_walk(edict_t *self, float dist) {
M_MoveToGoal(self, dist);
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;
}
}
}
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);
}
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);
}
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;
}
bool visible(edict_t *self, edict_t *other) {
vec3_t spot1;
vec3_t spot2;
trace_t trace;
if(self->s.cmodel_index != other->s.cmodel_index)
return false;
VectorCopy(self->s.origin, spot1);
spot1[2] += self->viewheight;
VectorCopy(other->s.origin, spot2);
spot2[2] += other->viewheight;
trace = gi.trace(self->s.cmodel_index, spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
if(trace.fraction == 1.0)
return true;
return false;
}
bool 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);
if(!(self->monsterinfo.aiflags & AI_STAND_GROUND))
AttackFinished(self, 1);
}
void FoundTarget(edict_t *self) {
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;
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->s.cmodel_index, 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;
}
self->combattarget = NULL;
self->monsterinfo.aiflags |= AI_COMBAT_POINT;
self->movetarget->targetname = NULL;
self->monsterinfo.pausetime = 0;
self->monsterinfo.run(self);
}
bool FindTarget(edict_t *self) {
edict_t *client;
bool 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;
}
return false;
}
if(self->monsterinfo.aiflags & AI_COMBAT_POINT)
return false;
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; }
if(!client->inuse)
return false;
if(client == self->enemy)
return true;
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;
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 {
vec3_t temp;
if(self->spawnflags & 1) {
if(!visible(self, client))
return false;
} else {
if(!gi.inPHS(self->s.cmodel_index, self->s.origin, client->s.origin))
return false;
}
VectorSubtract(client->s.origin, self->s.origin, temp);
if(VectorLength(temp) > 1000) {
return false;
}
if(client->areanum != self->areanum)
if(!gi.AreasConnected(self->s.cmodel_index, self->areanum, client->areanum))
return false;
self->ideal_yaw = vectoyaw(temp);
M_ChangeYaw(self);
self->monsterinfo.aiflags |= AI_SOUND_TARGET;
self->enemy = client;
}
FoundTarget(self);
if(!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight))
self->monsterinfo.sight(self, self->enemy);
return true;
}
bool FacingIdeal(edict_t *self) {
float delta;
delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
if(delta > 45 && delta < 315)
return false;
return true;
}
bool M_CheckAttack(edict_t *self) {
vec3_t spot1, spot2;
float chance;
trace_t tr;
if(ent_read_health(self->enemy)->value > 0) {
VectorCopy(self->s.origin, spot1);
spot1[2] += self->viewheight;
VectorCopy(self->enemy->s.origin, spot2);
spot2[2] += self->enemy->viewheight;
tr = gi.trace(self->s.cmodel_index, spot1, NULL, NULL, spot2, self,
CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA | CONTENTS_WINDOW);
if(tr.ent != self->enemy)
return false;
}
if(enemy_range == RANGE_MELEE) {
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;
}
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;
}
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;
}
}
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;
}
};
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);
}
bool ai_checkattack(edict_t *self, float dist) {
vec3_t temp;
bool hesDeadJim;
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;
hesDeadJim = false;
if((!self->enemy) || (!self->enemy->inuse)) {
hesDeadJim = true;
} else if(self->monsterinfo.aiflags & AI_MEDIC) {
if(ent_read_health(self->enemy)->value > 0) {
hesDeadJim = true;
self->monsterinfo.aiflags &= ~AI_MEDIC;
}
} else {
if(self->monsterinfo.aiflags & AI_BRUTAL) {
if(ent_read_health(self->enemy)->value <= -80)
hesDeadJim = true;
} else {
if(ent_read_health(self->enemy)->value <= 0)
hesDeadJim = true;
}
}
if(hesDeadJim) {
self->enemy = NULL;
if(self->oldenemy && ent_read_health(self->oldenemy)->value > 0) {
self->enemy = self->oldenemy;
self->oldenemy = NULL;
HuntTarget(self);
} else {
if(self->movetarget) {
self->goalentity = self->movetarget;
self->monsterinfo.walk(self);
} else {
self->monsterinfo.pausetime = level.time + 100000000;
self->monsterinfo.stand(self);
}
return true;
}
}
self->show_hostile = level.time + 1;
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);
}
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);
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_vis)
return false;
return self->monsterinfo.checkattack(self);
}
void ai_run(edict_t *self, float dist) {
vec3_t v;
edict_t *tempgoal;
edict_t *save;
bool 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(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) {
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;
}
if(coop->value) { 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;
return;
}
save = self->goalentity;
tempgoal = G_Spawn(self->s.cmodel_index);
self->goalentity = tempgoal;
new = false;
if(!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) {
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;
self->monsterinfo.search_time = level.time + 5;
if(self->monsterinfo.aiflags & AI_PURSUE_TEMP) {
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];
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) {
tr = gi.trace(self->s.cmodel_index, 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.cmodel_index, 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.cmodel_index, 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);
}
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);
} 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);
}
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);
}
}
}
M_MoveToGoal(self, dist);
G_FreeEdict(tempgoal);
if(self)
self->goalentity = save;
}