first commit
This commit is contained in:
912
mod_slipgate/source/server/ai.qc
Normal file
912
mod_slipgate/source/server/ai.qc
Normal file
@@ -0,0 +1,912 @@
|
||||
/*QUAKED path_corner (0.5 0.3 0) (-8 -8 -8) (8 8 8) X X X X X X X X NOT_IN_EASY NOT_IN_NORMAL NOT_IN_HARD NOT_IN_DM
|
||||
If a monster targets a path_corner, they will move towards it.
|
||||
Once a monster hits a path_corner, they will move to the next targeted path_corner.
|
||||
You can set "pausetime" on a path_corner to enforce a delay before the monster moves
|
||||
to the next target.
|
||||
*/
|
||||
void() path_corner =
|
||||
{
|
||||
if (!self.targetname)
|
||||
{
|
||||
objerror ("path_corner: no targetname");
|
||||
remove(self);
|
||||
return;
|
||||
}
|
||||
|
||||
self.movetype = MOVETYPE_NONE;
|
||||
self.solid = SOLID_TRIGGER;
|
||||
setsize (self, '-8 -8 -8', '8 8 8');
|
||||
};
|
||||
|
||||
//============================================================================
|
||||
|
||||
/*
|
||||
=============
|
||||
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
|
||||
=============
|
||||
*/
|
||||
float(entity targ) range =
|
||||
{
|
||||
float r = vlen (self.origin + self.view_ofs - targ.origin + targ.view_ofs);
|
||||
if (r < 500)
|
||||
{
|
||||
if (r < 120)
|
||||
return RANGE_MELEE;
|
||||
else
|
||||
return RANGE_NEAR;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r < 1000)
|
||||
return RANGE_MID;
|
||||
else
|
||||
return RANGE_FAR;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
=============
|
||||
visible
|
||||
|
||||
returns 1 if the entity is visible to self, even if not infront ()
|
||||
=============
|
||||
*/
|
||||
float (entity targ) visible =
|
||||
{
|
||||
traceline (self.origin + self.view_ofs, targ.origin + targ.view_ofs, TRUE, self);
|
||||
if (trace_inwater)
|
||||
{
|
||||
if (trace_inopen)
|
||||
return FALSE;
|
||||
}
|
||||
if (trace_fraction == 1)
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
=============
|
||||
infront
|
||||
|
||||
returns 1 if the entity is in front (in sight) of self
|
||||
=============
|
||||
*/
|
||||
float(entity targ) infront =
|
||||
{
|
||||
makevectors (self.angles);
|
||||
vector vec = normalize (targ.origin - self.origin);
|
||||
float dot = vec * v_forward;
|
||||
if (dot > 0.3)
|
||||
return TRUE;
|
||||
else
|
||||
return FALSE;
|
||||
};
|
||||
|
||||
void() HuntTarget =
|
||||
{
|
||||
self.goalentity = self.enemy;
|
||||
self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
|
||||
self.think = self.th_run;
|
||||
self.nextthink = time + 0.1;
|
||||
SUB_AttackFinished(1); // wait a while before first attack
|
||||
};
|
||||
|
||||
void() SightSound =
|
||||
{
|
||||
if (self.classname == "monster_ogre")
|
||||
sound (self, CHAN_BODY, "ogre/ogwake.wav", 1, ATTN_NORM);
|
||||
else if (self.classname == "monster_knight")
|
||||
sound (self, CHAN_BODY, "knight/ksight.wav", 1, ATTN_NORM);
|
||||
else if (self.classname == "monster_shambler")
|
||||
sound (self, CHAN_BODY, "shambler/ssight.wav", 1, ATTN_NORM);
|
||||
else if (self.classname == "monster_demon1")
|
||||
sound (self, CHAN_BODY, "demon/sight2.wav", 1, ATTN_NORM);
|
||||
else if (self.classname == "monster_wizard")
|
||||
sound (self, CHAN_BODY, "wizard/wsight.wav", 1, ATTN_NORM);
|
||||
else if (self.classname == "monster_zombie")
|
||||
sound (self, CHAN_BODY, "zombie/z_idle.wav", 1, ATTN_NORM);
|
||||
else if (self.classname == "monster_dog")
|
||||
sound (self, CHAN_BODY, "dog/dsight.wav", 1, ATTN_NORM);
|
||||
else if (self.classname == "monster_hell_knight")
|
||||
sound (self, CHAN_BODY, "hknight/sight1.wav", 1, ATTN_NORM);
|
||||
else if (self.classname == "monster_tarbaby")
|
||||
sound (self, CHAN_BODY, "blob/sight1.wav", 1, ATTN_NORM);
|
||||
else if (self.classname == "monster_enforcer")
|
||||
{
|
||||
float r = randomrange(4);
|
||||
if (r == 0)
|
||||
sound (self, CHAN_BODY, "enforcer/sight1.wav", 1, ATTN_NORM);
|
||||
else if (r == 1)
|
||||
sound (self, CHAN_BODY, "enforcer/sight2.wav", 1, ATTN_NORM);
|
||||
else if (r == 2)
|
||||
sound (self, CHAN_BODY, "enforcer/sight3.wav", 1, ATTN_NORM);
|
||||
else
|
||||
sound (self, CHAN_BODY, "enforcer/sight4.wav", 1, ATTN_NORM);
|
||||
}
|
||||
else if (self.classname == "monster_army")
|
||||
sound (self, CHAN_BODY, "soldier/sight1.wav", 1, ATTN_NORM);
|
||||
else if (self.classname == "monster_shalrath")
|
||||
sound (self, CHAN_BODY, "shalrath/sight.wav", 1, ATTN_NORM);
|
||||
};
|
||||
|
||||
void() FoundTarget =
|
||||
{
|
||||
if (self.enemy.classname == "player")
|
||||
{
|
||||
sight_entity = self;
|
||||
sight_entity_time = time;
|
||||
}
|
||||
|
||||
self.show_hostile = time + 1; // wake up other monsters
|
||||
|
||||
SightSound ();
|
||||
HuntTarget ();
|
||||
};
|
||||
|
||||
/*==========
|
||||
FindTarget
|
||||
==========*/
|
||||
float() FindTarget =
|
||||
{
|
||||
if (intermission_running)
|
||||
return FALSE;
|
||||
|
||||
if (sight_entity_time >= time - 0.1 && !(self.spawnflags & MONSTER_AMBUSH) )
|
||||
{
|
||||
entity client = sight_entity;
|
||||
if (client.enemy == self.enemy)
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
client = checkclient ();
|
||||
if (!client)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (client == self.enemy)
|
||||
return FALSE;
|
||||
|
||||
if (client.flags & FL_NOTARGET)
|
||||
return FALSE;
|
||||
if (client.items & IT_INVISIBILITY)
|
||||
return FALSE;
|
||||
|
||||
float r = range(client);
|
||||
if (r == RANGE_FAR)
|
||||
return FALSE;
|
||||
|
||||
if (!visible (client))
|
||||
return FALSE;
|
||||
|
||||
if (r == RANGE_NEAR)
|
||||
{
|
||||
if (client.show_hostile < time && !infront (client))
|
||||
return FALSE;
|
||||
}
|
||||
else if (r == RANGE_MID)
|
||||
{
|
||||
if (!infront (client))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
//
|
||||
// got one
|
||||
//
|
||||
self.enemy = client;
|
||||
if (self.enemy.classname != "player")
|
||||
{
|
||||
self.enemy = self.enemy.enemy;
|
||||
if (self.enemy.classname != "player")
|
||||
{
|
||||
self.enemy = world;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
FoundTarget ();
|
||||
|
||||
return TRUE;
|
||||
};
|
||||
|
||||
|
||||
//=============================================================================
|
||||
|
||||
/*==========
|
||||
ai_movetogoal
|
||||
==========*/
|
||||
|
||||
float (float dist) SV_CloseEnough =
|
||||
{
|
||||
if (self.goalentity.absmin.x > self.absmax.x + dist)
|
||||
return FALSE;
|
||||
if (self.goalentity.absmax.x < self.absmin.x - dist)
|
||||
return FALSE;
|
||||
|
||||
if (self.goalentity.absmin.y > self.absmax.y + dist)
|
||||
return FALSE;
|
||||
if (self.goalentity.absmax.y < self.absmin.y - dist)
|
||||
return FALSE;
|
||||
|
||||
if (self.goalentity.absmin.z > self.absmax.z + dist)
|
||||
return FALSE;
|
||||
if (self.goalentity.absmax.z < self.absmin.z - dist)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
};
|
||||
|
||||
|
||||
//.float blocked_time;
|
||||
//.float blocked_count;
|
||||
|
||||
void(entity goal, float dist) ai_movetogoal =
|
||||
{
|
||||
// new target
|
||||
if (self.goalentity != goal)
|
||||
{
|
||||
self.goalentity = goal;
|
||||
self.ideal_yaw = vectoyaw(goal.origin - self.origin);
|
||||
}
|
||||
|
||||
vector oldorigin = self.origin;
|
||||
movetogoal(dist);
|
||||
if (self.origin != oldorigin)
|
||||
return;
|
||||
|
||||
if (SV_CloseEnough(dist))
|
||||
{
|
||||
//dprint("SV_CloseEnough\n");
|
||||
self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
|
||||
changeyaw();
|
||||
if (walkmove(self.angles_y, dist))
|
||||
return;
|
||||
float ofs;
|
||||
if (self.lefty)
|
||||
ofs = 45;
|
||||
else
|
||||
ofs = -45;
|
||||
|
||||
if (walkmove(self.angles_y + ofs, dist))
|
||||
return;
|
||||
self.lefty = 1 - self.lefty;
|
||||
walkmove(self.angles_y - ofs, dist);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
if (self.blocked_count > 10)
|
||||
{
|
||||
self.blocked_count = 0;
|
||||
self.blocked_time = time + 5;
|
||||
}
|
||||
if (self.blocked_time > time)
|
||||
{
|
||||
movetogoal(dist);
|
||||
return;
|
||||
}
|
||||
|
||||
self.ideal_yaw = vectoyaw(goal.origin - self.origin);
|
||||
changeyaw();
|
||||
if (walkmove(self.angles_y, dist))
|
||||
return;
|
||||
|
||||
self.blocked_count = self.blocked_count + 1;
|
||||
float ofs;
|
||||
if (self.lefty)
|
||||
ofs = 45;
|
||||
else
|
||||
ofs = -45;
|
||||
|
||||
if (walkmove(self.angles_y + ofs, dist))
|
||||
return;
|
||||
|
||||
self.lefty = 1 - self.lefty;
|
||||
walkmove(self.angles_y - ofs, dist);*/
|
||||
};
|
||||
|
||||
|
||||
void(float dist) ai_forward =
|
||||
{
|
||||
walkmove(self.angles_y, dist);
|
||||
};
|
||||
|
||||
void(float dist) ai_back =
|
||||
{
|
||||
walkmove(self.angles_y + 180, dist);
|
||||
};
|
||||
|
||||
void(float dist) ai_pain =
|
||||
{
|
||||
ai_back(dist);
|
||||
};
|
||||
|
||||
void(float dist) ai_painforward =
|
||||
{
|
||||
ai_forward(dist);
|
||||
};
|
||||
|
||||
void(float dist) ai_walk =
|
||||
{
|
||||
if (FindTarget())
|
||||
return;
|
||||
|
||||
ai_movetogoal(self.movetarget, dist);
|
||||
if (vlen(self.origin - self.movetarget.origin) < 32)
|
||||
{
|
||||
entity targ = find(world, targetname, self.movetarget.target);
|
||||
if (targ.classname == "path_corner")
|
||||
{
|
||||
self.movetarget = self.goalentity = targ;
|
||||
self.ideal_yaw = vectoyaw(targ.origin - self.origin);
|
||||
if (targ.pausetime > 0)
|
||||
{
|
||||
self.think = self.th_stand;
|
||||
self.pausetime = time + targ.pausetime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self.think = self.th_stand;
|
||||
self.pausetime = -1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void() ai_stand =
|
||||
{
|
||||
if (FindTarget())
|
||||
return;
|
||||
|
||||
if (time > self.pausetime && self.pausetime > 0)
|
||||
{
|
||||
if (self.movetarget)
|
||||
{
|
||||
self.th_walk();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// random angle turns
|
||||
if (time > self.search_time)
|
||||
{
|
||||
self.search_time = time + 1 + random() * 2;
|
||||
|
||||
if (self.angles_y != self.ideal_yaw)
|
||||
self.angles_y = self.ideal_yaw;
|
||||
else
|
||||
{
|
||||
if (random() < 0.5)
|
||||
self.angles_y = self.ideal_yaw + random() * 30;
|
||||
else
|
||||
self.angles_y = self.ideal_yaw - random() * 30;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void() ai_turn =
|
||||
{
|
||||
if (FindTarget())
|
||||
return;
|
||||
changeyaw ();
|
||||
};
|
||||
|
||||
float(float ang) FacingIdeal =
|
||||
{
|
||||
float delta = angcomp(self.ideal_yaw, self.angles_y);
|
||||
if (delta > ang)
|
||||
return FALSE;
|
||||
if (delta < -ang)
|
||||
return FALSE;
|
||||
|
||||
/*float delta = anglemod(self.angles_y - self.ideal_yaw);
|
||||
if (delta > 45 && delta < 315)
|
||||
return FALSE;
|
||||
*/
|
||||
return TRUE;
|
||||
};
|
||||
|
||||
|
||||
//=============================================================================
|
||||
|
||||
float() WizardCheckAttack;
|
||||
float() DogCheckAttack;
|
||||
|
||||
float() CheckAnyAttack =
|
||||
{
|
||||
if (self.classname == "monster_ogre")
|
||||
return OgreCheckAttack ();
|
||||
if (!enemy_vis)
|
||||
return FALSE;
|
||||
if (self.classname == "monster_army")
|
||||
return SoldierCheckAttack ();
|
||||
if (self.classname == "monster_shambler")
|
||||
return ShamCheckAttack ();
|
||||
if (self.classname == "monster_demon1")
|
||||
return DemonCheckAttack ();
|
||||
if (self.classname == "monster_dog")
|
||||
return DogCheckAttack ();
|
||||
if (self.classname == "monster_wizard")
|
||||
return WizardCheckAttack ();
|
||||
return CheckAttack ();
|
||||
};
|
||||
|
||||
|
||||
/*==========
|
||||
ai_run_melee
|
||||
|
||||
Turn until facing enemy then launch melee attack
|
||||
==========
|
||||
*/
|
||||
void() ai_run_melee =
|
||||
{
|
||||
self.ideal_yaw = enemy_yaw;
|
||||
changeyaw();
|
||||
if (FacingIdeal(90))
|
||||
{
|
||||
self.th_melee ();
|
||||
self.attack_state = AS_STRAIGHT;
|
||||
}
|
||||
};
|
||||
|
||||
/*==============
|
||||
ai_run_missile
|
||||
|
||||
Turn until facing enemy then launch missile attack
|
||||
==========*/
|
||||
void() ai_run_missile =
|
||||
{
|
||||
if (self.attack_state == AS_SPAM)
|
||||
{
|
||||
self.ideal_yaw = vectoyaw(self.enemy_last - self.origin);
|
||||
changeyaw();
|
||||
}
|
||||
else
|
||||
{
|
||||
self.ideal_yaw = enemy_yaw;
|
||||
changeyaw();
|
||||
// if the enemy has dropped out of sight, cancel the attack
|
||||
if (!enemy_vis)
|
||||
{
|
||||
dprint("ai_run_missile: not visible\n");
|
||||
self.attack_state = AS_STRAIGHT;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (FacingIdeal(90))
|
||||
{
|
||||
if (self.attack_state == AS_SPAM)
|
||||
self.th_spam();
|
||||
else
|
||||
self.th_missile();
|
||||
self.attack_state = AS_STRAIGHT;
|
||||
}
|
||||
};
|
||||
|
||||
/*==========
|
||||
ai_run_slide
|
||||
|
||||
Face the enemy and strafe sideways
|
||||
==========*/
|
||||
void(float dist) ai_run_slide =
|
||||
{
|
||||
self.ideal_yaw = enemy_yaw;
|
||||
changeyaw();
|
||||
|
||||
float ofs;
|
||||
if (self.lefty)
|
||||
ofs = 90;
|
||||
else
|
||||
ofs = -90;
|
||||
|
||||
if (walkmove (self.ideal_yaw + ofs, dist))
|
||||
return;
|
||||
self.lefty = 1 - self.lefty;
|
||||
walkmove (self.ideal_yaw - ofs, dist);
|
||||
};
|
||||
|
||||
#define ROUTE_MAX 256
|
||||
float route_busy;
|
||||
.float route;
|
||||
.entity route_table[ROUTE_MAX];
|
||||
.float distance;
|
||||
.float route_time;
|
||||
|
||||
/*============
|
||||
ClearRoute
|
||||
|
||||
clear the monsters current route
|
||||
============*/
|
||||
void() ClearRoute =
|
||||
{
|
||||
self.route = -1;
|
||||
for (float i = 0; i < ROUTE_MAX; i = i + 1)
|
||||
self.route_table[i] = world;
|
||||
};
|
||||
|
||||
/*============
|
||||
ClearWayTable
|
||||
|
||||
clear the waypoint table (which is shared between all monsters)
|
||||
marks all waypoints as not visited and infinite distance
|
||||
============*/
|
||||
void() ClearWayTable =
|
||||
{
|
||||
entity e = way_head;
|
||||
while (e)
|
||||
{
|
||||
e.state = -1;
|
||||
e.distance = -1;
|
||||
e.last_way = world;
|
||||
e = e._next;
|
||||
}
|
||||
};
|
||||
|
||||
/*==========
|
||||
CheckDoors
|
||||
|
||||
check if a door is in the way of two waypoints while checking a route
|
||||
==========*/
|
||||
float(entity e) CheckDoors =
|
||||
{
|
||||
traceline(self.enemy.origin, e.origin, TRUE, self.enemy);
|
||||
if (trace_fraction == 1)
|
||||
return TRUE;
|
||||
if (trace_ent == e)
|
||||
return TRUE;
|
||||
|
||||
if (trace_ent.classname == "door")
|
||||
{
|
||||
// we check .owner because it's the master door
|
||||
if (trace_ent.owner.items) // key door
|
||||
return FALSE;
|
||||
if (trace_ent.owner.health) // shootable door
|
||||
return FALSE;
|
||||
if (trace_ent.owner.targetname != "") // triggered door
|
||||
return FALSE;
|
||||
|
||||
return TRUE; // regular door with a trigger field
|
||||
}
|
||||
|
||||
dprint("CheckDoors: \n");
|
||||
dprint(trace_ent.classname);
|
||||
dprint("\n");
|
||||
|
||||
return FALSE; // something else in the way
|
||||
};
|
||||
|
||||
/*==========
|
||||
CheckRoute
|
||||
|
||||
called from RouteThink to update the distances between the waypoints
|
||||
==========*/
|
||||
void(entity e) CheckRoute =
|
||||
{
|
||||
if (!e)
|
||||
return;
|
||||
if (e.state > 0)
|
||||
return;
|
||||
if (!CheckDoors(e))
|
||||
return;
|
||||
|
||||
float dist = self.enemy.distance;
|
||||
dist = dist + vlen(self.enemy.origin - e.origin);
|
||||
dist = dist + random() * 32; // add some fuzzyness
|
||||
|
||||
if ((e.distance == -1) || (dist < e.distance))
|
||||
{
|
||||
e.distance = dist;
|
||||
e.last_way = self.enemy;
|
||||
}
|
||||
};
|
||||
|
||||
/*==========
|
||||
RouteThink
|
||||
|
||||
return the shortest path to the goal waypoint
|
||||
==========*/
|
||||
void() RouteThink =
|
||||
{
|
||||
entity e;
|
||||
|
||||
// the monster died while calculating a route - free up the table
|
||||
if (self.owner.health <= 0)
|
||||
{
|
||||
route_busy = 0;
|
||||
remove(self);
|
||||
fixer = world;
|
||||
return;
|
||||
}
|
||||
|
||||
// visit all waypoints linking to current waypoint and update their distance
|
||||
CheckRoute(self.enemy.target1);
|
||||
CheckRoute(self.enemy.target2);
|
||||
CheckRoute(self.enemy.target3);
|
||||
CheckRoute(self.enemy.target4);
|
||||
|
||||
// mark this waypoint as visited
|
||||
self.enemy.state = 1;
|
||||
|
||||
// found route
|
||||
if (self.goalentity.state == 1)
|
||||
{
|
||||
//dprint("RouteThink: found route\n");
|
||||
|
||||
e = self.goalentity;
|
||||
float i = 0;
|
||||
while (e)
|
||||
{
|
||||
self.owner.route_table[i] = e;
|
||||
e = e.last_way;
|
||||
if (e) // don't count the final waypoint, as it's the one the monster is already at
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
self.owner.route = i - 1; // because route_table is a zero indexed array
|
||||
self.owner.route_time = time + 5;
|
||||
route_busy = FALSE;
|
||||
remove(self);
|
||||
fixer = world;
|
||||
return;
|
||||
}
|
||||
|
||||
// find the nearest, unvisited waypoint
|
||||
entity best = world;
|
||||
float bdist = -1;
|
||||
e = way_head;
|
||||
while (e)
|
||||
{
|
||||
if (e.state == -1) // not visited
|
||||
{
|
||||
if (e.distance > 0) // we use -1 for "infinite distance"
|
||||
{
|
||||
if (e.distance < bdist || bdist == -1)
|
||||
{
|
||||
best = e;
|
||||
bdist = e.distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
e = e._next;
|
||||
}
|
||||
|
||||
// no valid route exists between the two waypoints
|
||||
if (best == world)
|
||||
{
|
||||
//dprint("RouteThink: no valid route exists\n");
|
||||
route_busy = FALSE;
|
||||
remove(self);
|
||||
fixer = world;
|
||||
return;
|
||||
}
|
||||
|
||||
// update the next waypoint
|
||||
self.enemy = best;
|
||||
self.think = RouteThink;
|
||||
self.nextthink = time;
|
||||
};
|
||||
|
||||
/*============
|
||||
StartRoute
|
||||
|
||||
called by monsters when they want to find a path between waypoint A and B
|
||||
uses thinks to avoid tripping the runaway counter (RouteThink)
|
||||
============*/
|
||||
void(entity e1, entity e2) StartRoute =
|
||||
{
|
||||
// no waypoints exist in map
|
||||
if (!waypoints)
|
||||
return;
|
||||
// another monster is already calculating a route
|
||||
if (route_busy)
|
||||
return;
|
||||
// sanity check
|
||||
if (e1 == world || e2 == world || e1 == e2)
|
||||
return;
|
||||
|
||||
//dprint("StartRoute\n");
|
||||
|
||||
// mark the table as busy and clear it for use
|
||||
route_busy = TRUE;
|
||||
ClearWayTable();
|
||||
|
||||
// the first waypoint is always distance zero
|
||||
e1.distance = 0;
|
||||
|
||||
if (!fixer)
|
||||
fixer = spawn();
|
||||
fixer.owner = self;
|
||||
fixer.enemy = e1; // start waypoint
|
||||
fixer.goalentity = e2; // end waypoint
|
||||
fixer.nextthink = time;
|
||||
fixer.think = RouteThink;
|
||||
};
|
||||
|
||||
/*==========
|
||||
FindWaypoint
|
||||
==========*/
|
||||
entity(entity monster, entity start) FindWaypoint =
|
||||
{
|
||||
if (!waypoints)
|
||||
return world;
|
||||
|
||||
if (start != world)
|
||||
{
|
||||
float best_dist = vlen(start.origin - monster.origin);
|
||||
entity best = start;
|
||||
}
|
||||
else
|
||||
{
|
||||
best_dist = -1;
|
||||
best = world;
|
||||
}
|
||||
|
||||
if (best_dist < 20)
|
||||
return best;
|
||||
|
||||
entity w = way_head;
|
||||
while(w)
|
||||
{
|
||||
float way_dist = vlen(w.origin - monster.origin);
|
||||
if (way_dist < best_dist || best_dist == -1)
|
||||
{
|
||||
if (wisible(monster, w))
|
||||
{
|
||||
best_dist = way_dist;
|
||||
best = w;
|
||||
}
|
||||
}
|
||||
w = w._next;
|
||||
}
|
||||
|
||||
if (best_dist < 256)
|
||||
{
|
||||
//dprint("FindWaypoint: found\n");
|
||||
return best;
|
||||
}
|
||||
else
|
||||
{
|
||||
//dprint("FindWaypoint: world\n");
|
||||
return world;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
=============
|
||||
ai_run
|
||||
|
||||
The monster has an enemy it is trying to kill
|
||||
=============
|
||||
*/
|
||||
void(float dist) ai_run =
|
||||
{
|
||||
// stop all attacks during the intermission
|
||||
if (intermission_running)
|
||||
{
|
||||
ClearRoute();
|
||||
self.enemy = self.goalentity = world;
|
||||
self.th_stand();
|
||||
return;
|
||||
}
|
||||
// our enemy has died, so look for a new one
|
||||
if (self.enemy.health <= 0)
|
||||
{
|
||||
ClearRoute();
|
||||
self.enemy = self.goalentity = world;
|
||||
// look for previous enemy
|
||||
if (self.oldenemy.health > 0)
|
||||
{
|
||||
self.enemy = self.oldenemy;
|
||||
HuntTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
self.th_stand();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: get rid of globals
|
||||
enemy_vis = visible(self.enemy);
|
||||
enemy_infront = infront(self.enemy);
|
||||
enemy_range = range(self.enemy);
|
||||
enemy_yaw = vectoyaw(self.enemy.origin - self.origin);
|
||||
if (enemy_vis)
|
||||
{
|
||||
self.enemy_last = self.enemy.origin + self.enemy.view_ofs;
|
||||
self.search_time = time + 5;
|
||||
}
|
||||
|
||||
// look for other players in co-op
|
||||
if (coop && self.search_time < time)
|
||||
{
|
||||
if (FindTarget())
|
||||
return;
|
||||
}
|
||||
|
||||
// we've started an attack, so make sure we're facing the right way before launching
|
||||
if (self.attack_state == AS_MISSILE || self.attack_state == AS_SPAM)
|
||||
{
|
||||
ai_run_missile();
|
||||
return;
|
||||
}
|
||||
if (self.attack_state == AS_MELEE)
|
||||
{
|
||||
ai_run_melee();
|
||||
return;
|
||||
}
|
||||
|
||||
// check for beginning an attack
|
||||
if (CheckAnyAttack())
|
||||
return;
|
||||
|
||||
// used by scrags
|
||||
if (self.attack_state == AS_SLIDING)
|
||||
{
|
||||
ai_run_slide(dist);
|
||||
return;
|
||||
}
|
||||
|
||||
// no waypoints in map, so just use regular movement
|
||||
if (waypoint_mode < WM_LOADED || !waypoints)
|
||||
{
|
||||
ai_movetogoal(self.enemy, dist);
|
||||
return;
|
||||
}
|
||||
|
||||
// we have an enemy, but no route, so find a route to the enemy
|
||||
if (self.route < 0)
|
||||
{
|
||||
//dprint("Finding route\n");
|
||||
self.current_way = FindWaypoint(self, self.current_way);
|
||||
self.enemy.current_way = FindWaypoint(self.enemy, self.enemy.current_way);
|
||||
StartRoute(self.current_way, self.enemy.current_way);
|
||||
|
||||
ai_movetogoal(self.enemy, dist);
|
||||
return;
|
||||
}
|
||||
|
||||
// monster has failed to move to the next waypoint, so drop the route
|
||||
if (time > self.route_time)
|
||||
{
|
||||
//dprint("Dropping route\n");
|
||||
ClearRoute();
|
||||
ai_movetogoal(self.enemy, dist);
|
||||
return;
|
||||
}
|
||||
|
||||
// periodically refresh enemy waypoint
|
||||
if (!route_busy)
|
||||
{
|
||||
self.enemy.current_way = FindWaypoint(self.enemy, self.enemy.current_way);
|
||||
if (self.route_table[0] != self.enemy.current_way)
|
||||
StartRoute(self.current_way, self.enemy.current_way);
|
||||
}
|
||||
|
||||
// move to the next waypoint
|
||||
entity w = self.route_table[self.route];
|
||||
ai_movetogoal(w, dist);
|
||||
|
||||
// hit next waypoint
|
||||
if (vlen(self.origin - w.origin) < 64)
|
||||
{
|
||||
self.route_time = time + 5;
|
||||
self.current_way = self.route_table[self.route];
|
||||
self.route = self.route - 1;
|
||||
|
||||
// hit last waypoint
|
||||
if (self.route < 0)
|
||||
{
|
||||
dprint("Hit last waypoint\n");
|
||||
ClearRoute();
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user