892 lines
19 KiB
C++
892 lines
19 KiB
C++
/*
|
|
* $Header: /H3MP/hcode/newai.hc 16 10/28/97 1:01p Mgummelt $
|
|
*/
|
|
|
|
void AI_Decision_Dead()
|
|
{
|
|
if (self.health>-80)
|
|
{ // Newly dead
|
|
if ((self.ai_state != AI_DEAD) && (self.ai_state != AI_DEAD_TWITCH))
|
|
{
|
|
self.ai_new_state = AI_DEAD;
|
|
}
|
|
else // He's dead Jim
|
|
{
|
|
if (self.ai_poss_states & AI_DEAD_TWITCH )
|
|
{
|
|
self.ai_new_state = AI_DEAD_TWITCH;
|
|
self.ai_state = AI_DECIDE; // So states will change
|
|
}
|
|
else
|
|
self.ai_new_state = AI_DEAD;
|
|
}
|
|
|
|
self.ai_duration = time + 15; // Wait 15 more seconds before vanishing
|
|
}
|
|
else
|
|
self.ai_new_state = AI_DEAD_GIB;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=============
|
|
AI_face_enemy
|
|
|
|
Turn in place until within an angle to launch an attack
|
|
=============
|
|
*/
|
|
float AI_face_enemy ()
|
|
{
|
|
|
|
self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
|
|
|
|
ChangeYaw ();
|
|
|
|
if (FacingIdeal()) // Ready to go get em
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
AI_infront
|
|
|
|
returns 1 if the entity is in front (in sight) of self
|
|
=============
|
|
*/
|
|
float AI_infront (entity targ)
|
|
{
|
|
local vector vec;
|
|
local float dot;
|
|
|
|
makevectors (self.angles);
|
|
vec = normalize (targ.origin - self.origin);
|
|
dot = vec * v_forward;
|
|
|
|
if ( dot > 0.3)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
AI_visionblocked
|
|
|
|
returns 1 if the entity is visible to self
|
|
=============
|
|
*/
|
|
float AI_visionblocked (entity targ)
|
|
{
|
|
local vector spot1, spot2;
|
|
|
|
spot1 = self.origin + self.view_ofs;
|
|
spot2 = targ.origin + targ.view_ofs;
|
|
traceline (spot1, spot2, TRUE, self); // see through other monsters
|
|
|
|
if (trace_inopen && trace_inwater)
|
|
return FALSE; // sight line crossed contents
|
|
|
|
if (trace_fraction == 1)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
AI_calc_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 AI_calc_range (entity targ)
|
|
{
|
|
local vector spot1, spot2;
|
|
local float rnge;
|
|
|
|
spot1 = self.origin + self.view_ofs;
|
|
spot2 = targ.origin + targ.view_ofs;
|
|
|
|
rnge = vlen (spot1 - spot2);
|
|
|
|
if (rnge < 50)
|
|
return RANGE_MELEE;
|
|
|
|
if (rnge < 400) // Was 250
|
|
return RANGE_NEAR;
|
|
|
|
if (rnge < 1000) // Was 800
|
|
return RANGE_MID;
|
|
|
|
return RANGE_FAR;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
AI_TargetSearch
|
|
|
|
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.
|
|
============
|
|
*/
|
|
entity AI_TargetSearch ()
|
|
{
|
|
local entity client;
|
|
local float r;
|
|
|
|
|
|
// FIXME: need to get sight_entity stuff working
|
|
|
|
// spawnflags & 3 is a big hack, because zombie crucified used the first
|
|
// spawn flag prior to the ambush flag, and I forgot about it, so the second
|
|
// spawn flag works as well
|
|
/* if (sight_entity_time >= time - 0.1 && !(self.spawnflags & 3) )
|
|
{
|
|
client = sight_entity;
|
|
if (client.enemy == self.enemy)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{*/
|
|
client = checkclient ();
|
|
if (!client)
|
|
{
|
|
return FALSE; // current check entity isn't in PVS
|
|
}
|
|
// }
|
|
|
|
if (client.classname != "player")
|
|
return FALSE;
|
|
|
|
if (client.flags & FL_NOTARGET)
|
|
return FALSE;
|
|
|
|
if (client.items & IT_INVISIBILITY)
|
|
return FALSE;
|
|
|
|
return client;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
AI_Target
|
|
|
|
Some preliminary target decisions
|
|
- if just hurt go after the attacker
|
|
- if enemy is the world look to see if you can find someone else
|
|
============
|
|
*/entity AI_Target ()
|
|
{
|
|
entity targ;
|
|
|
|
// Monster was just hurt - go after the attacker
|
|
if (self.just_hurt)
|
|
targ = self.enemy; // Set in T-Damage
|
|
|
|
else if (self.enemy==world) // No enemies currently
|
|
targ=AI_TargetSearch (); // Look for a player to kill
|
|
|
|
else // Go after the current enemy
|
|
targ = self.enemy;
|
|
|
|
return targ;
|
|
}
|
|
|
|
void AI_MeleeAttack ()
|
|
{
|
|
AI_face_enemy(); // Turn to face the enemy
|
|
}
|
|
|
|
|
|
void AI_MissileAttack ()
|
|
{
|
|
AI_face_enemy(); // Turn to face the enemy
|
|
}
|
|
|
|
|
|
void AI_Stand ()
|
|
{
|
|
|
|
}
|
|
|
|
void AI_Pain ()
|
|
{
|
|
AI_face_enemy(); // Turn to face the enemy
|
|
}
|
|
|
|
void AI_Walk ()
|
|
{
|
|
movetogoal (self.move_distance);
|
|
}
|
|
|
|
void AI_Wander ()
|
|
{
|
|
movetogoal (self.move_distance);
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
AI_Charge
|
|
|
|
Charge the enemy - unless monster is too close to enemy, then attack
|
|
|
|
============
|
|
*/
|
|
void AI_Charge ()
|
|
{
|
|
float rnge;
|
|
|
|
rnge = AI_calc_range (self.enemy);
|
|
|
|
if (rnge==RANGE_MELEE) // Too close - attack enemy
|
|
self.ai_new_state=AI_DECIDE;
|
|
else
|
|
movetogoal (self.move_distance);
|
|
}
|
|
|
|
/*
|
|
===========
|
|
AI_Dead_Gib
|
|
|
|
Monster is majorly dead so make him explode
|
|
|
|
============
|
|
*/
|
|
void AI_Dead_Gib ()
|
|
{
|
|
chunk_death();
|
|
|
|
remove(self);
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
AI_Dead
|
|
|
|
Monster is dead - in a very real and legally binding sense
|
|
|
|
============
|
|
*/
|
|
// FIXME: We need to come up with a nice way of getting rid of dead bodies
|
|
void AI_Dead ()
|
|
{
|
|
if (self.ai_duration <= time)
|
|
remove(self);
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
AI_TurnLook
|
|
|
|
Turn and look for an enemy that is behind monster
|
|
|
|
============
|
|
*/
|
|
void AI_TurnLook ()
|
|
{
|
|
self.angles_y += 4;
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
AI_Change_State
|
|
|
|
Set up the variables for the new AI state
|
|
This has the final say in the enemy
|
|
|
|
============
|
|
*/
|
|
void AI_Change_State(entity targ)
|
|
{
|
|
self.ai_state_func(); // Change monster variables to match the new state
|
|
|
|
// Set up the info for the new state
|
|
if (self.ai_new_state == AI_STAND)
|
|
{
|
|
self.ai_move_func = AI_Stand;
|
|
self.goalentity = self.pathentity;
|
|
self.enemy = world;
|
|
}
|
|
else if (self.ai_new_state == AI_WALK)
|
|
{
|
|
self.ai_move_func = AI_Walk;
|
|
self.goalentity = self.pathentity;
|
|
self.enemy = world;
|
|
}
|
|
else if (self.ai_new_state==AI_CHARGE)
|
|
{
|
|
self.ai_move_func = AI_Charge;
|
|
self.goalentity = targ;
|
|
self.enemy = targ;
|
|
}
|
|
else if (self.ai_new_state==AI_MISSILE_ATTACK)
|
|
{
|
|
self.ai_move_func = AI_MissileAttack;
|
|
self.goalentity = targ;
|
|
self.enemy = targ;
|
|
self.ai_duration = time + 9999; // This action must run it course before changing
|
|
}
|
|
else if (self.ai_new_state==AI_MISSILE_REATTACK)
|
|
{
|
|
self.ai_move_func = AI_MissileAttack;
|
|
self.goalentity = targ;
|
|
self.enemy = targ;
|
|
self.ai_duration = time + 9999; // This action must run it course before changing
|
|
}
|
|
else if (self.ai_new_state==AI_MELEE_ATTACK)
|
|
{
|
|
self.ai_move_func = AI_MeleeAttack;
|
|
self.goalentity = targ;
|
|
self.enemy = targ;
|
|
self.ai_duration = time + 9999; // This action must run it course before changing
|
|
}
|
|
else if ((self.ai_new_state==AI_PAIN) || (self.ai_new_state==AI_PAIN_CLOSE) || (self.ai_new_state==AI_PAIN_FAR))
|
|
{
|
|
self.ai_move_func = AI_Pain;
|
|
self.goalentity = self.enemy;
|
|
self.ai_duration = time + 9999; // This action must run it course before changing
|
|
}
|
|
else if (self.ai_new_state==AI_DEAD)
|
|
{
|
|
self.ai_move_func = AI_Dead;
|
|
// self.goalentity = world;
|
|
// self.enemy = world;
|
|
self.ai_duration = time + 15; // Body will lay there for a while before disappearing
|
|
}
|
|
else if (self.ai_new_state==AI_DEAD_TWITCH)
|
|
{
|
|
self.ai_move_func = AI_Dead;
|
|
self.goalentity = world;
|
|
self.enemy = world;
|
|
}
|
|
else if (self.ai_new_state==AI_DEAD_GIB)
|
|
{
|
|
self.ai_move_func = AI_Dead_Gib;
|
|
self.goalentity = world;
|
|
self.enemy = world;
|
|
}
|
|
else if (self.ai_new_state==AI_TURNLOOK)
|
|
{
|
|
self.ai_move_func = AI_TurnLook;
|
|
self.goalentity = world;
|
|
self.enemy = world;
|
|
}
|
|
else if (self.ai_new_state==AI_WANDER)
|
|
{
|
|
self.ai_move_func = AI_Wander;
|
|
self.goalentity = world;
|
|
self.enemy = world;
|
|
}
|
|
else
|
|
{
|
|
self.ai_move_func = AI_Walk;
|
|
self.goalentity = self.pathentity;
|
|
}
|
|
|
|
self.ai_state = self.ai_new_state;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
AI_Decision_Melee
|
|
|
|
At MELEE distance from the enemy - decide what state to take
|
|
|
|
============
|
|
*/
|
|
entity AI_Decision_Melee(float in_sight,float in_front,entity targ)
|
|
{
|
|
float chance;
|
|
entity newtarg;
|
|
|
|
if (self.just_hurt)
|
|
{ // What types of pain can it have???
|
|
|
|
if (self.ai_poss_states & AI_PAIN_CLOSE )
|
|
self.ai_new_state = AI_PAIN_CLOSE;
|
|
else
|
|
self.ai_new_state = AI_PAIN;
|
|
|
|
self.just_hurt = FALSE;
|
|
newtarg = targ;
|
|
}
|
|
else
|
|
{
|
|
chance = random(); // 90% chance of a melee attack
|
|
// 20% chance of wandering
|
|
// if health is below 10 there is an
|
|
// 90% chance he will throw a missile (he's almost dead so take him out with a bang)
|
|
// 20% chance of wandering
|
|
|
|
if (chance < 0.90)
|
|
{
|
|
if (self.health > 10)
|
|
self.ai_new_state = AI_MELEE_ATTACK;
|
|
else
|
|
self.ai_new_state = AI_MISSILE_ATTACK;
|
|
|
|
self.ai_state = AI_DECIDE; // make it so states change
|
|
}
|
|
else
|
|
{
|
|
self.ai_new_state = AI_WANDER;
|
|
self.angles_y += random() * 40;
|
|
self.ai_duration = time + (random() * 2) + 2;
|
|
}
|
|
newtarg = targ;
|
|
}
|
|
|
|
return newtarg;
|
|
}
|
|
/*
|
|
===========
|
|
AI_Decision_Near
|
|
|
|
At NEAR distance from the enemy - decide what state to take
|
|
|
|
============
|
|
*/
|
|
entity AI_Decision_Near(float in_sight,float in_front,entity targ)
|
|
{
|
|
float chance;
|
|
entity newtarg;
|
|
|
|
if (self.just_hurt)
|
|
{
|
|
if (self.ai_poss_states & AI_PAIN_CLOSE )
|
|
self.ai_new_state = AI_PAIN_CLOSE;
|
|
else
|
|
self.ai_new_state = AI_PAIN;
|
|
self.just_hurt = FALSE;
|
|
newtarg = targ;
|
|
}
|
|
else
|
|
{
|
|
chance = random(); // 100% chance of a charging
|
|
|
|
if (chance <= 1) // Yes I know, I will add other states soon
|
|
{
|
|
self.ai_new_state = AI_CHARGE;
|
|
self.ai_state = AI_DECIDE; // make it so states change
|
|
}
|
|
else
|
|
{
|
|
self.ai_new_state = AI_WANDER;
|
|
self.angles_y += random() * 40;
|
|
self.ai_duration = time + (random() * 2) + 2;
|
|
}
|
|
newtarg = targ;
|
|
}
|
|
|
|
return newtarg;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
AI_Decision_Mid
|
|
|
|
At MID distance from the enemy - decide what state to take
|
|
|
|
============
|
|
*/
|
|
entity AI_Decision_Mid(float in_sight,float in_front,entity targ)
|
|
{
|
|
float chance;
|
|
entity newtarg;
|
|
|
|
// He was just hurt make him go through pain
|
|
if (self.just_hurt)
|
|
{
|
|
if (self.ai_poss_states & AI_PAIN_CLOSE )
|
|
self.ai_new_state = AI_PAIN_CLOSE;
|
|
else
|
|
self.ai_new_state = AI_PAIN;
|
|
self.just_hurt = FALSE;
|
|
newtarg = targ;
|
|
}
|
|
|
|
// No walls are blocking enemy and enemy's right in front of monster
|
|
else if ((in_sight) && (in_front))
|
|
{ // Can monster throw missiles
|
|
if (self.ai_poss_states & AI_MISSILE_ATTACK )
|
|
{
|
|
chance = random(); // 70% chance of throwing a missile
|
|
// 30% chance of charging
|
|
if (chance <= 0.70)
|
|
{
|
|
// Not missile attacking yet
|
|
if ((self.ai_state != AI_MISSILE_ATTACK) && (self.ai_state != AI_MISSILE_REATTACK))
|
|
self.ai_new_state = AI_MISSILE_ATTACK;
|
|
else
|
|
{
|
|
self.ai_new_state = AI_MISSILE_REATTACK;
|
|
self.ai_state = AI_DECIDE; // make it so reattacks can repeat
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self.ai_new_state = AI_CHARGE;
|
|
self.ai_duration = time + (random() * 2) + 1;
|
|
}
|
|
}
|
|
else // No missile attack so charge enemy
|
|
{
|
|
self.ai_new_state = AI_CHARGE;
|
|
self.ai_duration = time + (random() * 2) + 1;
|
|
}
|
|
newtarg = targ;
|
|
}
|
|
|
|
// Enemy is close but he is not right in front of monster
|
|
// so stand and turn for a minute
|
|
else if (in_sight)
|
|
{
|
|
chance = random(); // 80% chance he'll keep walking
|
|
// 20% chance he'll turn looking for the player
|
|
|
|
if ((chance <= 0.80) || (self.ai_state != AI_WALK)) // Keep walking
|
|
{
|
|
self.ai_new_state = AI_WALK;
|
|
self.ai_duration = time + random() * 4;
|
|
}
|
|
else // Stop and turn
|
|
{
|
|
self.ai_new_state = AI_TURNLOOK;
|
|
self.ai_duration = time + random() + 1;
|
|
}
|
|
newtarg = world;
|
|
}
|
|
// A wall is in the way so continue do what you're doing
|
|
else
|
|
{
|
|
if (self.ai_state == AI_DECIDE)
|
|
self.ai_new_state = AI_STAND;
|
|
else
|
|
{
|
|
self.ai_new_state = self.ai_state;
|
|
}
|
|
newtarg = world; // Cause we haven't seen him yet
|
|
}
|
|
|
|
return newtarg;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
AI_Decision_Far
|
|
|
|
At FAR distance from the enemy - decide what state to take
|
|
|
|
============
|
|
*/
|
|
entity AI_Decision_Far(float in_sight,float in_front,entity targ)
|
|
{
|
|
float chance;
|
|
entity newtarg;
|
|
|
|
// He was just hurt make him go through pain
|
|
if (self.just_hurt) // Just given pain
|
|
{
|
|
if (self.ai_poss_states & AI_PAIN_FAR )
|
|
self.ai_new_state = AI_PAIN_FAR;
|
|
else
|
|
self.ai_new_state = AI_PAIN;
|
|
|
|
self.just_hurt = FALSE;
|
|
newtarg = targ;
|
|
|
|
}
|
|
|
|
// He just finished his pain now he should charge
|
|
else if ((self.ai_state==AI_PAIN_FAR) || (self.ai_state==AI_PAIN))
|
|
{
|
|
chance = random(); // 45% chance he'll charge
|
|
// 45% chance he'll fire a missile (if he can)
|
|
// 10% chance of wandering to get away
|
|
|
|
|
|
if (chance <= 0.10)
|
|
{
|
|
self.ai_new_state = AI_WANDER;
|
|
self.ai_duration = time + (random() * 2) + 2;
|
|
}
|
|
else if (chance <= 55)
|
|
{
|
|
if (self.ai_state & AI_MISSILE_ATTACK)
|
|
{
|
|
self.ai_new_state = AI_MISSILE_ATTACK;
|
|
newtarg = targ;
|
|
}
|
|
else
|
|
{
|
|
self.ai_new_state = AI_CHARGE;
|
|
self.ai_duration = time + (random() * 4) + 4;
|
|
newtarg = targ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self.ai_new_state = AI_CHARGE;
|
|
self.ai_duration = time + (random() * 4) + 4;
|
|
newtarg = targ;
|
|
}
|
|
}
|
|
|
|
// Not in pain and everyone is too far away and monster has a path to walk
|
|
else if (self.pathentity)
|
|
{
|
|
self.ai_new_state = AI_WALK;
|
|
self.goalentity=self.pathentity;
|
|
newtarg = world;
|
|
}
|
|
|
|
// Not in pain and everyone is too far away and no path to walk
|
|
else
|
|
{
|
|
self.ai_new_state = AI_STAND;
|
|
newtarg = world;
|
|
}
|
|
|
|
return newtarg;
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
AI_Decision_Tree
|
|
|
|
Have monster look for enemys
|
|
|
|
Based on range from enemy decide what state to go to
|
|
|
|
Place new state in ai_new_state
|
|
|
|
============
|
|
*/
|
|
void AI_Decision_Tree(float state_demanded)
|
|
{
|
|
float rnge,in_sight,in_front;
|
|
local entity targ,finaltarg;
|
|
|
|
if ((state_demanded==FALSE) || (self.ai_new_state == AI_DECIDE)) // Monster is not forcing a state
|
|
{
|
|
if ((self.ai_duration > time) && (self.ai_new_state != AI_DECIDE)) // Shouldn't change states just yet
|
|
return;
|
|
|
|
if (self.health > 0) // Still alive
|
|
{
|
|
|
|
if (self.ai_new_state == AI_DECIDE) // Reset time for the DECIDE state
|
|
self.ai_duration = time - 1;
|
|
|
|
targ=AI_Target(); // Find a target if there is one
|
|
|
|
rnge = AI_calc_range (targ); // Get target range
|
|
|
|
in_sight=AI_visionblocked (targ); // Is a wall between monster and target?
|
|
|
|
in_front=AI_infront(targ); // Is target in front of monster
|
|
|
|
|
|
// Decide on a new state for the monster based on range to enemy
|
|
if (rnge == RANGE_FAR)
|
|
finaltarg=AI_Decision_Far(in_sight,in_front,targ);
|
|
|
|
else if (rnge == RANGE_MID)
|
|
finaltarg=AI_Decision_Mid(in_sight,in_front,targ);
|
|
|
|
else if (rnge == RANGE_NEAR)
|
|
finaltarg=AI_Decision_Near(in_sight,in_front,targ);
|
|
|
|
else if (rnge == RANGE_MELEE) // He's very very close
|
|
finaltarg=AI_Decision_Melee(in_sight,in_front,targ);
|
|
}
|
|
else
|
|
AI_Decision_Dead();
|
|
}
|
|
else // Monster is forcing a state
|
|
finaltarg=self.enemy; // Keep it's current enemy
|
|
|
|
|
|
if (self.ai_new_state!=self.ai_state) // Did we just change states??
|
|
AI_Change_State(finaltarg);
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
AI_Main
|
|
|
|
Main loop for monster AI
|
|
|
|
- decide on state of monster
|
|
- use monster's own AI function
|
|
- use main movement function
|
|
|
|
============
|
|
*/
|
|
void AI_Main()
|
|
{
|
|
float demand_state;
|
|
|
|
if (!comamode) // Freeze monsters
|
|
{
|
|
self.ai_frame_time=0;
|
|
|
|
demand_state=FALSE;
|
|
|
|
do
|
|
{
|
|
AI_Decision_Tree(demand_state); // Should monster change it's current state
|
|
|
|
self.ai_self_func(); // AI particular to this type of monster (Archer_Walk Archer_Stand...)
|
|
|
|
// Did monster force a change in ai state? Change it before running ai_move_func
|
|
if (self.ai_state != self.ai_new_state)
|
|
demand_state = TRUE;
|
|
else
|
|
{
|
|
demand_state = FALSE;
|
|
|
|
self.ai_move_func(); // General monster AI AI_Walk, AI_Stand ...
|
|
|
|
if (self.ai_state != self.ai_new_state) // was a change in AI forced??
|
|
demand_state = TRUE;
|
|
else
|
|
demand_state = FALSE;
|
|
}
|
|
|
|
} while (demand_state==TRUE);
|
|
}
|
|
else
|
|
AI_TargetSearch ();
|
|
|
|
if (!self.ai_frame_time)
|
|
self.nextthink = time + HX_FRAME_TIME;
|
|
else
|
|
{
|
|
self.nextthink = time + self.ai_frame_time;
|
|
}
|
|
}
|
|
|
|
void AI_start ()
|
|
{
|
|
|
|
self.origin_z += 1; // raise off floor a bit
|
|
droptofloor();
|
|
|
|
if (!walkmove(0,0, FALSE))
|
|
{
|
|
dprint ("walkmonster in wall at: ");
|
|
dprint (vtos(self.origin));
|
|
dprint ("\n");
|
|
}
|
|
|
|
if (self.target)
|
|
{
|
|
self.goalentity = self.pathentity = find(world, targetname, self.target);
|
|
self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
|
|
if (!self.pathentity)
|
|
{
|
|
dprint ("Monster can't find target at ");
|
|
dprint (vtos(self.origin));
|
|
dprint ("\n");
|
|
}
|
|
}
|
|
|
|
self.think = AI_Main;
|
|
// spread think times so they don't all happen at same time
|
|
self.nextthink = self.nextthink + random()*0.5;
|
|
|
|
|
|
}
|
|
|
|
void AI_Land_Init ()
|
|
{
|
|
|
|
self.takedamage = DAMAGE_YES;
|
|
self.flags2+=FL_ALIVE;
|
|
self.ideal_yaw = self.angles * '0 1 0';
|
|
if (!self.yaw_speed)
|
|
self.yaw_speed = 20;
|
|
self.view_ofs = '0 0 25';
|
|
self.use = monster_use;
|
|
self.flags = self.flags | FL_MONSTER;
|
|
self.enemy = world;
|
|
self.ai_state = AI_DECIDE;
|
|
|
|
// delay drop to floor to make sure all doors have been spawned
|
|
// spread think times so they don't all happen at same time
|
|
self.nextthink = self.nextthink + random()*0.5;
|
|
self.think = AI_start;
|
|
total_monsters += 1;
|
|
}
|
|
|
|
/*
|
|
* $Log: /H3MP/hcode/newai.hc $
|
|
*
|
|
* 16 10/28/97 1:01p Mgummelt
|
|
* Massive replacement, rewrote entire code... just kidding. Added
|
|
* support for 5th class.
|
|
*
|
|
* 14 5/07/97 3:40p Mgummelt
|
|
*
|
|
* 13 5/07/97 11:12a Rjohnson
|
|
* Added a new field to walkmove and movestep to allow for setting the
|
|
* traceline info
|
|
*
|
|
* 12 4/18/97 5:24p Mgummelt
|
|
*
|
|
* 11 4/14/97 3:28p Rlove
|
|
* A few changes to the mummy, he still needs tweaking but ...
|
|
*
|
|
* 10 3/31/97 4:11p Rlove
|
|
* New mummy ai
|
|
*
|
|
* 9 3/31/97 6:38a Rlove
|
|
* New mummy ai
|
|
*
|
|
* 8 3/21/97 10:24a Rlove
|
|
* Left in a few dprints
|
|
*
|
|
* 7 3/21/97 9:38a Rlove
|
|
* Created CHUNK.HC and MATH.HC, moved brush_die to chunk_death so others
|
|
* can use it.
|
|
*
|
|
* 6 3/20/97 12:31p Rlove
|
|
* More updates to monster AI
|
|
*
|
|
* 5 3/19/97 3:09p Rlove
|
|
* Added CreateEntity and ScaleBoundingBox
|
|
*
|
|
* 4 3/15/97 3:08p Rlove
|
|
* Added COMA console command
|
|
*
|
|
* 3 3/13/97 9:57a Rlove
|
|
* Changed constant DAMAGE_AIM to DAMAGE_YES and the old DAMAGE_YES to
|
|
* DAMAGE_NO_GRENADE
|
|
*/
|