2022-02-08 18:42:28 +00:00
/*
server / ai / ai_core . qc
ai stuff
2024-01-07 23:24:48 +00:00
Copyright ( C ) 2021 - 2024 NZ : P Team
2022-02-08 18:42:28 +00:00
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 :
Free Software Foundation , Inc .
59 Temple Place - Suite 330
Boston , MA 02111 - 1307 , USA
*/
void ( float what ) play_sound_z ;
2022-12-21 00:50:56 +00:00
void ( ) LinkZombiesHitbox ;
2023-11-21 17:18:38 +00:00
entity ( ) Dog_FindEnemy ;
2022-02-08 18:42:28 +00:00
void ( ) path_corner_touch =
{
self . origin_z = self . origin_z + 32 ;
setorigin ( self , self . origin ) ;
self . classname = " path_corner " ;
self . movetype = MOVETYPE_NONE ;
self . solid = SOLID_NOT ;
self . touch = SUB_Null ;
setsize ( self , ' 0 0 0 ' , ' 0 0 0 ' ) ;
if ( ! self . target )
{
if ( self . spawnflags & 1 )
return ;
bprint ( PRINT_HIGH , " path_corner with name: " ) ;
bprint ( PRINT_HIGH , self . targetname ) ;
bprint ( PRINT_HIGH , " has no target! \n " ) ;
}
}
//We want the path corner to drop to the ground and then we set it up 32 units so it's exact
void ( ) path_corner =
{
self . classname = " path_corner_set " ;
self . movetype = MOVETYPE_BOUNCE ;
self . solid = SOLID_BBOX ;
self . touch = path_corner_touch ;
setsize ( self , ' 0 0 0 ' , ' 0 0 0 ' ) ;
} ;
void ( ) Respawn =
{
2023-12-20 16:38:50 +00:00
// cypress (20 dec 2023) -- whoever wrote this originally wasn't considering
// many factors.. if a zombie respawns there's a chance it'll be in a
// completely different playspace, where the initial window isn't visible
// at all, and removeZombie() does not clear that field (rightfully so).
// so i've replaced this with the same function i use to respawn zombies
// inside of the playable area, which calls self.th_die() and fibs to the
// zombie counter a little better, fixes that issue.
// Kill it.
self . th_die ( ) ;
// This is a pseudo-hack that just tells the rounds core that we should
// spawn another.
2022-02-08 18:42:28 +00:00
Current_Zombies - - ;
2023-12-20 16:38:50 +00:00
Remaining_Zombies + + ;
2022-02-08 18:42:28 +00:00
} ;
entity ( entity blarg ) find_new_enemy =
{
2022-12-22 16:44:13 +00:00
entity targets ;
entity best_target ;
float best_distance ;
float distance ;
best_distance = 10000 ;
best_target = world ;
if ( blarg . classname = = " ai_zombie " | | blarg . classname = = " ai_dog " ) {
// Monkey Bomb (TODO -- if multiple, target first one thrown)
targets = find ( world , classname , " monkey_bomb " ) ;
if ( targets ! = world & & blarg . classname ! = " ai_dog " ) {
best_target = targets ;
return best_target ;
2022-02-08 18:42:28 +00:00
}
2022-12-22 16:44:13 +00:00
// Now, try and find a viable player
targets = find ( world , classname , " player " ) ;
while ( targets ! = world ) {
// Don't target downed players.
2022-12-29 00:32:21 +00:00
if ( targets . downed = = true | | targets . isspec = = true ) {
2022-12-22 16:44:13 +00:00
targets = find ( targets , classname , " player " ) ;
2022-10-25 16:49:02 +00:00
continue ;
}
2022-12-22 16:44:13 +00:00
// Found one, let's see if it's closer than our last ideal target.
distance = vlen ( blarg . origin - targets . origin ) ;
if ( distance < best_distance ) {
best_target = targets ;
best_distance = distance ;
2022-02-08 18:42:28 +00:00
}
2022-12-22 16:44:13 +00:00
// Continue iterating
targets = find ( targets , classname , " player " ) ;
2022-02-08 18:42:28 +00:00
}
2022-12-22 16:44:13 +00:00
// Return a good player if we found one.
if ( best_target ! = world )
return best_target ;
// We couldn't find a good player. How about a horde point?
targets = find ( world , classname , " zombie_horde_point " ) ;
while ( targets ! = world ) {
// Found one, let's see if it's closer than our last ideal target.
distance = vlen ( blarg . origin - targets . origin ) ;
if ( distance < best_distance ) {
best_target = targets ;
best_distance = distance ;
}
// Continue iterating
targets = find ( targets , classname , " zombie_horde_point " ) ;
}
2023-11-07 17:56:56 +00:00
// Last chance -- try a player spawn point.
targets = find ( world , classname , " info_player_1_spawn " ) ;
while ( targets ! = world ) {
// Found one, let's see if it's closer than our last ideal target.
distance = vlen ( blarg . origin - targets . origin ) ;
if ( distance < best_distance ) {
best_target = targets ;
best_distance = distance ;
}
// Continue iterating
targets = find ( targets , classname , " info_player_1_spawn " ) ;
}
2022-12-22 16:44:13 +00:00
// Return a horde point if we found one.
if ( best_target ! = world )
return best_target ;
2022-02-08 18:42:28 +00:00
}
2022-12-22 16:44:13 +00:00
// We didn't have much luck, just return the world.
return best_target ;
2022-02-08 18:42:28 +00:00
} ;
float ( ) avoid_zombies =
{
local entity ent ;
ent = findradius ( self . origin , 23 ) ; //22.67
makevectors ( self . angles ) ;
float left_amount , right_amount ;
left_amount = right_amount = 0 ;
while ( ent )
{
if ( ent . classname = = " ai_zombie " & & ent ! = self )
{
local vector vec_b ;
local float dot ;
//vec_b = normalize(self.origin - ent.origin);
//dot = v_right * vec_b;
//dot = self.angles_y - (dot > 0.5) ? 90 : 270;
vec_b = ( self . origin - ent . origin ) ;
dot = ( v_right_x * vec_b_x ) + ( v_right_y * vec_b_y ) ; //dot product
if ( dot > 0 ) // on right
right_amount + + ;
else // on left
left_amount + + ;
}
ent = ent . chain ;
}
if ( left_amount + right_amount = = 0 )
return 0 ;
return ( left_amount > right_amount ) ? 15 : - 15 ;
} ;
float ( ) push_away_zombies =
{
local entity ent ;
ent = findradius ( self . origin , 11 ) ;
float return_value ;
return_value = 0 ;
while ( ent )
{
2023-11-21 16:49:51 +00:00
if ( ent . aistatus = = " 1 " & & ent ! = self )
2022-02-08 18:42:28 +00:00
{
vector push ;
push = ent . origin - self . origin ;
push_z = 0 ;
push = normalize ( push ) * 10 ;
ent . velocity + = push ;
return_value = 1 ;
}
ent = ent . chain ;
}
return return_value ;
}
void ( float dist , vector vec ) do_walk_to_vec =
{
if ( dist = = 0 )
return ;
self . ideal_yaw = vectoyaw ( vec - self . origin ) ;
if ( self . outside = = false )
{
2023-03-04 18:46:39 +00:00
// this is a performance net negative for like 0 gain in horde space.
2023-10-31 17:33:43 +00:00
push_away_zombies ( ) ;
2022-02-08 18:42:28 +00:00
}
ChangeYaw ( ) ;
vector new_velocity ;
float len ;
len = vlen ( self . origin - vec ) ;
if ( dist > len ) //if we're moving past our goal position
{
dist = len ;
}
//This movement method is moving directly towards the goal, regardless of where our yaw is facing (fixes several issues)
new_velocity = normalize ( vec - self . origin ) * dist * 10 ;
new_velocity_z = self . velocity_z ;
self . velocity = new_velocity ;
} ;
void ( float dist ) do_walk =
{
do_walk_to_vec ( dist , self . goalentity . origin ) ;
} ;
void ( float dist ) walk_to_window =
{
do_walk_to_vec ( dist , self . goalorigin ) ;
} ;
// unused
void ( vector org , float scale ) interpolateToVector =
{
self . origin_x + = ( org_x - self . origin_x ) * scale ;
self . origin_y + = ( org_y - self . origin_y ) * scale ;
setorigin ( self , self . origin ) ;
self . zoom = 1 ; //value to let engine know to not check for collisions
}
float ( vector where ) nearby =
{
2023-01-13 17:29:08 +00:00
if ( self . classname = = " ai_zombie " | | self . classname = = " ai_dog " )
2022-02-08 18:42:28 +00:00
{
float xdist ;
float ydist ;
float zdist ;
xdist = fabs ( self . origin_x - where_x ) ;
ydist = fabs ( self . origin_y - where_y ) ;
zdist = fabs ( self . origin_z - where_z ) ;
if ( xdist < 4 & & ydist < 4 ) //horizontal distance is fairly close
{
if ( zdist < 50 ) //vertical distance just has to be arbitrarily close
{
return 1 ;
}
}
return 0 ;
}
return 0 ;
} ;
void ( float dist ) Window_Walk =
{
if ( self . reload_delay < time )
Respawn ( ) ;
if ( self . hop_step = = 0 ) //following path corners
{
if ( self . goalentity = = world )
{
if ( ( ! self . target ) & & ( self . outside = = TRUE ) )
{
bprint ( PRINT_HIGH , " Error: Outside zombie spawn point has no target. \n " ) ;
Respawn ( ) ;
}
self . goalentity = find ( world , targetname , self . target ) ;
if ( ! self . goalentity )
{
bprint ( PRINT_HIGH , " Error: Outside zombie spawn point target does not exist. \n " ) ;
Respawn ( ) ;
}
}
if ( self . goalentity . classname = = " path_corner " & & nearby ( self . goalentity . origin ) )
{
if ( self . goalentity . spawnflags & 1 ) //this path corner sets zombie on inside.
{
self . outside = FALSE ;
self . goalentity = world ;
self . enemy = find_new_enemy ( self ) ;
self . th_walk ( ) ;
return ;
}
self . reload_delay = time + 30 ;
self . goalentity = find ( world , targetname , self . goalentity . target ) ;
//Assumption is that when the zombie is outside, we can always walk from one path_corner to the next in a straight line, any devation should be corrected by the mapper
}
2023-12-20 16:38:50 +00:00
2022-02-08 18:42:28 +00:00
do_walk ( dist ) ;
if ( self . goalentity . classname = = " window " )
{
2023-12-20 16:38:50 +00:00
// cypress (17 dec 2023) -- fixed a nasty race condition here.
// essentially, if a new zombie spawns in fast enough after
// a zombie is currently/just finished hopping a spot, they will
// end up always taking box1owner (the hoppable zombie)'s place,
// regardless of whether or not there was someone else waiting.
// apparently this is an issue i've created rather recently,
// which does not make sense to me considering this is a pretty
// glaring oversight that's been here since 2014. not my whoops?
if ( ! self . goalentity . box1owner & & ! self . goalentity . box2owner & & ! self . goalentity . box3owner )
2022-02-08 18:42:28 +00:00
{
//self.used = WBOX1;
self . goalentity . box1owner = self ;
self . goalorigin = self . goalentity . box1 ;
self . hop_step = 3 ;
self . reload_delay = time + 30 ;
}
2023-12-20 16:38:50 +00:00
else if ( ! self . goalentity . box2owner )
2022-02-08 18:42:28 +00:00
{
//self.used = WBOX2;
self . goalentity . box2owner = self ;
self . goalorigin = self . goalentity . box2 ;
self . hop_step = 3 ;
self . reload_delay = time + 30 ;
}
2023-12-20 16:38:50 +00:00
else if ( ! self . goalentity . box3owner )
2022-02-08 18:42:28 +00:00
{
//self.used = WBOX3;
self . goalentity . box3owner = self ;
self . goalorigin = self . goalentity . box3 ;
self . hop_step = 3 ;
self . reload_delay = time + 30 ;
}
else if ( vlen ( self . origin - self . goalentity . origin ) < 150 )
{
//we don't claim the idlebox
//self.used = WIDLEBOX;
self . goalorigin = self . goalentity . idlebox ;
self . hop_step = 1 ;
self . reload_delay = time + 30 ;
}
//else we continue walking to window until we either find one that's good, or we are close enough to chase idle_spot
}
}
else if ( self . hop_step = = 1 ) //walking to the window's idle location
{
if ( nearby ( self . goalorigin ) )
{
self . hop_step = 2 ;
self . reload_delay = time + 30 ;
self . th_idle ( ) ;
}
else
{
walk_to_window ( dist ) ;
}
}
else if ( self . hop_step = = 2 ) //we're at idle box, waiting for a window attack box to be free...
{
2023-12-20 16:38:50 +00:00
if ( self . goalentity . box1owner = = world )
2022-02-08 18:42:28 +00:00
{
//self.used = WBOX1;
self . goalentity . box1owner = self ;
self . goalorigin = self . goalentity . box1 ;
self . hop_step = 3 ;
self . reload_delay = time + 30 ;
self . th_walk ( ) ;
}
2023-12-20 16:38:50 +00:00
else if ( self . goalentity . box2owner = = world )
2022-02-08 18:42:28 +00:00
{
//self.used = WBOX2;
self . goalentity . box2owner = self ;
self . goalorigin = self . goalentity . box2 ;
self . hop_step = 3 ;
self . reload_delay = time + 30 ;
self . th_walk ( ) ;
}
2023-12-20 16:38:50 +00:00
else if ( self . goalentity . box3owner = = world )
2022-02-08 18:42:28 +00:00
{
//self.used = WBOX3;
self . goalentity . box3owner = self ;
self . goalorigin = self . goalentity . box3 ;
self . hop_step = 3 ;
self . reload_delay = time + 30 ;
self . th_walk ( ) ;
}
}
else if ( self . hop_step = = 3 ) //walking to window attack box
{
2023-12-20 16:38:50 +00:00
// sometimes, we've assigned a zombie a waiting-position
// while another zombie is actively hopping. this can cause
// a bit of gameplay slowdown since the zombie has to walk
// to that waiting position, then to the hop spot (approx 1sec),
// so let's do continuous checks here to see if box1 is free,
// and claim it if so, in order to smooth the path and claim
// that time. only do this if there isn't anyone else in the queue,
// though.
if ( ! self . goalentity . box1owner ) {
// Claim it as ours mid-walk, if there isn't someone else waiting.
if ( ( self . goalentity . box2owner = = self & & ! self . goalentity . box3owner ) | |
( self . goalentity . box3owner = = self & & ! self . goalentity . box2owner ) ) {
self . goalentity . box1owner = self ;
self . goalorigin = self . goalentity . box1 ;
// Free up the spot we were walking to before.
if ( self . goalentity . box2owner = = self ) self . goalentity . box2owner = world ;
if ( self . goalentity . box3owner = = self ) self . goalentity . box3owner = world ;
}
}
2022-02-08 18:42:28 +00:00
if ( nearby ( self . goalorigin ) )
{
self . hop_step = 4 ;
self . reload_delay = time + 30 ;
self . th_idle ( ) ;
}
else
{
walk_to_window ( dist ) ;
}
}
else if ( self . hop_step = = 4 ) //attacking box
{
if ( self . chase_time < time )
{
if ( self . angles_z ! = self . goalentity . angles_z )
{
self . ideal_yaw = self . goalentity . angles_z ;
ChangeYaw ( ) ;
return ;
}
2023-11-03 17:34:59 +00:00
if ( self . goalentity . health > 0 & & ! self . goalentity . owner )
2022-02-08 18:42:28 +00:00
{
self . reload_delay = time + 30 ;
self . th_melee ( ) ;
if ( rounds < = 5 )
self . chase_time = time + 1.5 ;
else
self . chase_time = time + 0.75 ;
return ;
2023-11-03 17:34:59 +00:00
} else if ( self . goalentity . owner ) {
2023-12-20 16:38:50 +00:00
self . chase_time = time + 0.05 ;
2023-11-03 17:34:59 +00:00
return ;
2022-02-08 18:42:28 +00:00
}
}
2023-11-03 17:34:59 +00:00
if ( self . goalentity . health < = 0 & & ! self . goalentity . owner )
2022-02-08 18:42:28 +00:00
{
self . outside = 2 ;
self . chase_time = 0 ;
self . hop_step = 0 ;
}
else return ;
}
} ;
void ( float dist ) Window_Hop =
{
if ( self . hop_step = = 0 ) {
if ( self . goalentity . box1owner = = self ) { //we're at center box.
self . hop_step = 4 ;
} else {
self . hop_step = 1 ; //wait for box1 to be free so we can claim it and walk to it.
self . th_idle ( ) ;
}
}
if ( self . hop_step = = 1 ) { //waiting idly for box1 to be free, when free, we will claim it.
if ( ! self . goalentity . box1owner | | self . goalentity . box1owner = = self ) {
self . goalentity . box1owner = self ;
if ( self . goalentity . box2owner = = self )
self . goalentity . box2owner = world ;
if ( self . goalentity . box3owner = = self )
self . goalentity . box3owner = world ;
//self.used = WBOX1;
self . goalorigin = self . goalentity . box1 ;
self . hop_step = 2 ;
self . th_walk ( ) ;
}
}
if ( self . hop_step = = 2 ) { //we've claimed it, walk to box1
if ( nearby ( self . goalorigin ) ) {
self . hop_step = 4 ;
self . angles = self . goalentity . angles ;
} else {
walk_to_window ( dist ) ;
}
}
if ( self . hop_step = = 4 & & self . chase_time < time ) { //we're at this step because we already own box1, so don't even check if window is busy...
if ( ! self . goalentity . usedent ) {
self . hop_step = 5 ;
self . angles = self . goalentity . angles ;
self . goalentity . box1owner = world ; //free box1
self . goalentity . usedent = self ; //we own the window
//don't need to set goalorigin here
//self.used = WWINDOW;
self . chase_time = 0 ;
self . th_windowhop ( ) ;
return ;
} else {
self . tries + + ;
self . chase_time = time + 0.2 ;
if ( self . tries > 10 ) {
2023-12-20 16:38:50 +00:00
if ( ! self . goalentity . owner )
self . goalentity . usedent = world ; //free up the window if we've been waiting to hop
2022-02-08 18:42:28 +00:00
}
}
}
if ( self . hop_step = = 6 ) {
self . outside = FALSE ;
2023-12-20 16:38:50 +00:00
self . goalentity . usedent = world ; //free up the window, we're done hopping it
2022-02-08 18:42:28 +00:00
self . goalentity = world ;
self . enemy = find_new_enemy ( self ) ;
//self.th_die();
self . th_walk ( ) ;
2022-12-28 02:50:52 +00:00
//LinkZombiesHitbox();
2022-12-21 00:50:56 +00:00
//bprint (PRINT_HIGH, "Linked hitboxes");
2022-02-08 18:42:28 +00:00
}
}
2023-11-28 18:51:05 +00:00
2023-11-28 20:00:56 +00:00
//
// FTE's custom "tracemove" -- no way in hell I'm reimplementing SV_Move
// in QuakeC. So this is just a really bad traceline hack. We can't even
// use tracebox since that's limited by BSP hulls,
//
2023-11-28 18:51:05 +00:00
# ifdef FTE
2023-11-28 20:00:56 +00:00
inline float ( vector start , vector min , vector max , vector end , float nomonsters , entity forent ) tracemove =
# else
float ( vector start , vector min , vector max , vector end , float nomonsters , entity forent ) tracemove_fake =
# endif // FTE
{
makevectors ( forent . angles ) ;
// Top left of the box
traceline ( start + ' 0 0 32 ' + v_right * - 18 , end , nomonsters , forent ) ;
// Results Check
if ( trace_ent ! = forent & & trace_endpos ! = end )
return 0 ;
// Top right of the box
traceline ( start + ' 0 0 32 ' + v_right * 18 , end , nomonsters , forent ) ;
// Results Check
if ( trace_ent ! = forent & & trace_endpos ! = end )
return 0 ;
// Bottom left of the box
traceline ( start - ' 0 0 24 ' + v_right * - 18 , end , nomonsters , forent ) ;
// Results Check
if ( trace_ent ! = forent & & trace_endpos ! = end )
return 0 ;
// Bottom right of the box
traceline ( start - ' 0 0 24 ' + v_right * 18 , end , nomonsters , forent ) ;
// Results Check
if ( trace_ent ! = forent & & trace_endpos ! = end )
return 0 ;
return 1 ;
}
float ( ) TryWalkToEnemy =
{
2023-11-28 18:51:05 +00:00
// Early out hack for FTE -- if there's tons of z-diff, GTFO!
2023-10-30 18:08:49 +00:00
float z_axis_diff = fabs ( self . origin_z - self . enemy . origin_z ) ;
2023-11-28 18:51:05 +00:00
if ( z_axis_diff > = 30 )
return 0 ;
// This has been a headache...
// TryWalkToEnemy is a system that attempts to ignore waypoints from a
// certain distance to simulate proper player-targeting. It does this
// using the custom builtin tracemove, which calls SV_Move to determine
// if it's possible for the enemy to walk directly to the target. This
// is problematic, however -- in that FTE does not feature this builtin
// since it's non-standard and was written by blubs. This means there
// needs to be improvisation, and as a result there is disparity here.
// See the custom tracemove() function for details on that.
// -- cypress (28 Nov 2023)
2023-11-28 20:00:56 +00:00
# ifdef FTE
2023-11-28 18:51:05 +00:00
float TraceResult = tracemove ( self . origin , VEC_HULL_MIN , VEC_HULL_MAX , self . enemy . origin , TRUE , self ) ;
2023-11-28 20:00:56 +00:00
# else
float TraceResult = tracemove_fake ( self . origin , VEC_HULL_MIN , VEC_HULL_MAX , self . enemy . origin , TRUE , self ) ;
# endif // FTE
2023-11-28 18:51:05 +00:00
if ( TraceResult = = 1 ) {
2022-02-08 18:42:28 +00:00
self . goalentity = self . enemy ;
self . chase_time = time + 7 ;
return 1 ;
2023-11-28 18:51:05 +00:00
} else {
return 0 ;
}
2022-02-08 18:42:28 +00:00
} ;
void ( ) PathfindToEnemy =
{
float path_result ;
float path_failure ;
//just to stop any warns.
path_failure = 0 ;
2023-02-05 21:03:57 +00:00
# ifndef FTE
2022-02-08 18:42:28 +00:00
path_result = Do_Pathfind_psp ( self , self . enemy ) ;
2023-02-05 21:03:57 +00:00
# else
2022-02-08 18:42:28 +00:00
path_result = Do_Pathfind ( self , self . enemy ) ;
2023-02-05 21:03:57 +00:00
# endif // FTE
2022-02-08 18:42:28 +00:00
if ( path_result > = 1 ) {
2023-02-05 21:03:57 +00:00
# ifndef FTE
2022-02-08 18:42:28 +00:00
self . goaldummy . origin = Get_First_Waypoint ( self , self . origin , VEC_HULL_MIN , VEC_HULL_MAX ) ;
setorigin ( self . goaldummy , self . goaldummy . origin ) ;
path_failure = path_result ;
2023-02-05 21:03:57 +00:00
# else
2022-02-08 18:42:28 +00:00
self . goalway = path_result ;
setorigin ( self . goaldummy , waypoints [ self . goalway ] . org ) ;
path_failure = self . goalway ;
2023-02-05 21:03:57 +00:00
# endif // FTE
2022-02-08 18:42:28 +00:00
self . goalentity = self . goaldummy ;
self . chase_time = time + 7 ;
} else if ( path_failure = = - 1 ) {
self . goalentity = self . enemy ;
self . chase_time = time + 6 ;
} else {
2023-02-05 21:03:57 +00:00
# ifdef FTE
2024-01-12 19:40:38 +00:00
//bprint(PRINT_HIGH, "FirstPathfind Failure\n");
2023-02-05 21:03:57 +00:00
# endif // FTE
2022-02-08 18:42:28 +00:00
}
}
void ( ) NextPathfindToEnemy {
// same as PathfindToEnemy on non-FTE platforms
2023-02-05 21:03:57 +00:00
# ifndef FTE
2022-02-08 18:42:28 +00:00
float path_success ;
path_success = Do_Pathfind_psp ( self , self . enemy ) ;
if ( path_success = = 1 ) {
self . goaldummy . origin = Get_Next_Waypoint ( self , self . origin , VEC_HULL_MIN , VEC_HULL_MAX ) ;
setorigin ( self . goaldummy , self . goaldummy . origin ) ;
self . goalentity = self . goaldummy ;
self . chase_time = time + 7 ;
} else if ( path_success = = - 1 ) {
self . goalentity = self . enemy ;
self . chase_time = time + 6 ;
} else {
bprint ( PRINT_HIGH , " NextPathfind Failure \n " ) ; // this lags like hell
}
2023-02-05 21:03:57 +00:00
# else
2022-02-08 18:42:28 +00:00
self . way_cur + + ;
if ( self . way_cur < 40 & & self . way_path [ self . way_cur ] ! = - 1 ) {
self . goalway = self . way_path [ self . way_cur ] ;
setorigin ( self . goaldummy , waypoints [ self . goalway - 1 ] . org ) ;
} else {
self . way_cur = 0 ;
}
2023-02-05 21:03:57 +00:00
# endif // FTE
2022-02-08 18:42:28 +00:00
}
void ( float dist ) Inside_Walk = {
2023-11-21 17:18:38 +00:00
// Hellhounds should only change targets if current one is in Last Stand
if ( self . classname = = " ai_dog " & & self . enemy . downed = = true ) {
self . enemy = Dog_FindEnemy ( ) ;
}
// Normal Zombie time-out re-targeting
else if ( self . classname ! = " ai_dog " ) {
if ( self . enemy_timeout < time | | self . enemy = = world | | self . enemy . downed = = true ) {
self . enemy_timeout = time + 5 ;
local entity oldEnemy ;
oldEnemy = self . enemy ;
self . enemy = find_new_enemy ( self ) ;
}
2022-02-08 18:42:28 +00:00
}
//================Check for proximity to player ===========
if ( vlen ( self . enemy . origin - self . origin ) < 60 ) {
2023-01-13 17:29:08 +00:00
if ( self . enemy . classname = = " monkey_bomb " & & self . classname ! = " ai_dog " ) {
2022-02-08 18:42:28 +00:00
self . th_idle ( ) ;
}
if ( self . attack_delay < time ) {
self . attack_delay = time + 1 + ( 1 * random ( ) ) ;
2023-11-07 17:56:56 +00:00
if ( self . enemy . health )
self . th_melee ( ) ;
2023-11-21 17:18:38 +00:00
if ( self . enemy . downed = = true ) {
if ( self . classname = = " ai_dog " )
self . enemy = Dog_FindEnemy ( ) ;
else
self . enemy = find_new_enemy ( self ) ;
}
2022-02-08 18:42:28 +00:00
self . goalentity = self . enemy ;
self . chase_time = time + 5 ;
}
return ;
}
if ( vlen ( self . enemy . origin - self . origin ) < 600 ) { //50 feet
if ( self . goalentity = = self . enemy & & self . chase_enemy_time > time ) {
return ;
}
if ( TryWalkToEnemy ( ) )
{
self . chase_enemy_time = time + 0.5 ;
return ;
}
}
if ( self . goalentity = = self . enemy ) {
self . goalentity = self . goaldummy ;
self . chase_time = 0 ;
}
//============= No Target ====================
2022-12-27 18:06:37 +00:00
if ( self . goalentity = = world ) { //not sure when this would ever occur... but whatever.
self . goalentity = self . goaldummy ;
}
2022-02-08 18:42:28 +00:00
//============ GoalDummy is Target ============
if ( self . goalentity = = self . goaldummy ) {
if ( nearby ( self . goaldummy . origin ) ) {
2023-02-05 21:03:57 +00:00
# ifndef FTE
2022-02-08 18:42:28 +00:00
NextPathfindToEnemy ( ) ;
2023-02-05 21:03:57 +00:00
# else
2022-02-08 18:42:28 +00:00
PathfindToEnemy ( ) ;
2023-02-05 21:03:57 +00:00
# endif // FTE
2022-02-08 18:42:28 +00:00
}
if ( self . chase_time < time ) {
if ( self . goaldummy . origin ! = world . origin & & tracemove ( self . origin , VEC_HULL_MIN , VEC_HULL_MAX , self . goalentity . origin , TRUE , self ) = = 1 ) {
self . chase_time = time + 7 ;
} else {
PathfindToEnemy ( ) ;
}
}
}
}
. float droptime ;
void ( float dist ) Zombie_Walk = {
//Resetting velocity from last frame (except for vertical)
self . velocity_x = 0 ;
self . velocity_y = 0 ;
//self.flags = self.flags | FL_PARTIALGROUND;
2023-11-22 21:11:40 +00:00
if ( ! ( self . flags & FL_ONGROUND ) & & self . watertype ! = CONTENT_WATER ) {
2022-02-08 18:42:28 +00:00
if ( ! self . droptime ) {
self . droptime = time + 1 ;
} else if ( self . droptime < time ) {
self . droptime = 0 ;
//bprint(PRINT_HIGH, "not on ground\n");
2023-01-13 17:29:08 +00:00
if ( self . classname ! = " ai_dog " )
self . th_fall ( ) ;
2022-02-08 18:42:28 +00:00
return ;
}
}
if ( self . outside = = TRUE ) {
//handle special walk case for walking to org
Window_Walk ( dist ) ;
return ;
}
if ( self . outside = = 2 ) {
//play_sound_z(2);
Window_Hop ( dist ) ;
//handle special walk case for walking to org
return ;
}
if ( self . outside = = FALSE ) {
if ( self . goalentity = = self . enemy ) {
if ( vlen ( self . origin - self . enemy . origin ) < 60 ) {
return ;
}
}
}
do_walk ( dist ) ;
}
void ( ) Zombie_AI = {
//dist = 0;
float dist = 0 ;
self . flags = self . flags | FL_PARTIALGROUND ;
//check_onfire();
if ( self . outside = = TRUE ) {
play_sound_z ( 2 ) ;
//self.calc_time = time + (0.3 * random());
//Window_Walk(dist);
return ;
} else if ( self . outside = = 2 ) {
play_sound_z ( 2 ) ;
//Window_Hop(0);
return ;
} else if ( self . outside = = FALSE ) {
play_sound_z ( 2 ) ;
//self.calc_time = time + (0.25 + (0.15 * random()));
Inside_Walk ( dist ) ;
}
}
2023-01-13 17:29:08 +00:00
void ( ) Hellhound_AI =
{
Inside_Walk ( 0 ) ;
}
2023-11-30 17:03:15 +00:00
//
// Do_Zombie_AI()
// Behaves differently based on platform -- in FTE,
// all AI can afford to be executed at once. However,
// on every other platform there is a timed delay in
// place and only one zombie AI can be updated at a time.
//
void ( ) Do_Zombie_AI =
{
entity z ;
entity old_self ;
2022-02-08 18:42:28 +00:00
2023-11-30 17:03:15 +00:00
# ifndef FTE
2022-02-08 18:42:28 +00:00
z = find ( lastzombie , aistatus , " 1 " ) ;
if ( z = = world ) {
z = find ( world , aistatus , " 1 " ) ;
if ( z = = world ) {
return ; //no zombies alive.
}
}
2023-11-30 17:03:15 +00:00
old_self = self ;
2022-02-08 18:42:28 +00:00
self = z ;
2023-11-30 17:03:15 +00:00
2023-01-13 17:29:08 +00:00
if ( z . classname = = " ai_zombie " )
Zombie_AI ( ) ;
else if ( z . classname = = " ai_dog " )
Hellhound_AI ( ) ;
2023-11-30 17:03:15 +00:00
self = old_self ;
2022-02-08 18:42:28 +00:00
lastzombie = z ;
2023-11-30 17:03:15 +00:00
# else
z = find ( world , aistatus , " 1 " ) ;
while ( z ! = world ) {
old_self = self ;
self = z ;
// Execute AI
if ( z . classname = = " ai_zombie " )
Zombie_AI ( ) ;
else if ( z . classname = = " ai_dog " )
Hellhound_AI ( ) ;
self = old_self ;
z = find ( z , aistatus , " 1 " ) ;
}
# endif // FTE
2022-10-25 16:49:02 +00:00
}
2023-07-18 16:05:49 +00:00
# ifdef FTE
void ( ) AI_SetAllEnemiesBBOX =
{
2023-11-08 16:50:48 +00:00
entity zombies = find ( world , classname , " ai_zombie " ) ;
2023-07-18 16:05:49 +00:00
2023-11-08 16:50:48 +00:00
while ( zombies ! = world ) {
2023-11-08 17:30:26 +00:00
if ( zombies . aistatus = = " 1 " ) {
zombies . last_solid = zombies . solid ;
zombies . solid = SOLID_BBOX ;
2023-07-18 16:05:49 +00:00
2023-11-08 17:30:26 +00:00
if ( zombies . head ) {
zombies . head . last_solid = zombies . head . solid ;
zombies . head . solid = SOLID_BBOX ;
}
2023-07-18 16:05:49 +00:00
2023-11-08 17:30:26 +00:00
if ( zombies . larm ) {
zombies . larm . last_solid = zombies . larm . solid ;
zombies . larm . solid = SOLID_BBOX ;
}
2023-07-18 16:05:49 +00:00
2023-11-08 17:30:26 +00:00
if ( zombies . rarm ) {
zombies . rarm . last_solid = zombies . rarm . solid ;
zombies . rarm . solid = SOLID_BBOX ;
}
2023-11-08 16:50:48 +00:00
}
2023-11-08 17:30:26 +00:00
2023-11-08 16:50:48 +00:00
zombies = find ( zombies , classname , " ai_zombie " ) ;
2023-07-18 16:05:49 +00:00
}
}
2023-07-18 18:31:12 +00:00
void ( ) AI_RevertEnemySolidState =
2023-07-18 16:05:49 +00:00
{
2023-11-08 16:50:48 +00:00
entity zombies = find ( world , classname , " ai_zombie " ) ;
2023-07-18 16:05:49 +00:00
2023-11-08 16:50:48 +00:00
while ( zombies ! = world ) {
2023-11-08 17:30:26 +00:00
if ( zombies . aistatus = = " 1 " ) {
zombies . solid = zombies . last_solid ;
if ( zombies . head )
zombies . head . solid = zombies . head . last_solid ;
if ( zombies . larm )
zombies . larm . solid = zombies . larm . last_solid ;
if ( zombies . rarm )
zombies . rarm . solid = zombies . rarm . last_solid ;
}
2023-07-18 16:05:49 +00:00
2023-11-08 16:50:48 +00:00
zombies = find ( zombies , classname , " ai_zombie " ) ;
2023-07-18 16:05:49 +00:00
}
}
# endif // FTE