1531 lines
No EOL
36 KiB
Text
1531 lines
No EOL
36 KiB
Text
#include maps\_utility;
|
|
#include common_scripts\utility;
|
|
#include maps\payload_zombiemode_utility;
|
|
|
|
#using_animtree( "generic_human" );
|
|
init()
|
|
{
|
|
level.zombie_move_speed = 1;
|
|
level.zombie_health = 150;
|
|
|
|
zombies = getEntArray( "zombie_spawner", "script_noteworthy" );
|
|
later_rounds = getentarray("later_round_spawners", "script_noteworthy" );
|
|
|
|
zombies = array_combine( zombies, later_rounds );
|
|
|
|
for( i = 0; i < zombies.size; i++ )
|
|
{
|
|
if( is_spawner_targeted_by_blocker( zombies[i] ) )
|
|
{
|
|
zombies[i].locked_spawner = true;
|
|
}
|
|
}
|
|
|
|
array_thread( zombies, ::add_spawn_function, ::zombie_spawn_init );
|
|
}
|
|
|
|
is_spawner_targeted_by_blocker( ent )
|
|
{
|
|
if( IsDefined( ent.targetname ) )
|
|
{
|
|
targeters = GetEntArray( ent.targetname, "target" );
|
|
|
|
for( i = 0; i < targeters.size; i++ )
|
|
{
|
|
if( targeters[i].targetname == "zombie_door" || targeters[i].targetname == "zombie_debris" )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
result = is_spawner_targeted_by_blocker( targeters[i] );
|
|
if( result )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// set up zombie walk cycles
|
|
zombie_spawn_init()
|
|
{
|
|
self.targetname = "zombie";
|
|
self.script_noteworthy = undefined;
|
|
self.animname = "zombie";
|
|
self.ignoreall = true;
|
|
self.allowdeath = true; // allows death during animscripted calls
|
|
self.gib_override = true; // needed to make sure this guy does gibs
|
|
self.is_zombie = true; // needed for melee.gsc in the animscripts
|
|
self.has_legs = true; // Sumeet - This tells the zombie that he is allowed to stand anymore or not, gibbing can take
|
|
// out both legs and then the only allowed stance should be prone.
|
|
|
|
self.gibbed = false;
|
|
self.head_gibbed = false;
|
|
|
|
// might need this so co-op zombie players cant block zombie pathing
|
|
self PushPlayer( true );
|
|
// self.meleeRange = 128;
|
|
// self.meleeRangeSq = anim.meleeRange * anim.meleeRange;
|
|
|
|
animscripts\shared::placeWeaponOn( self.primaryweapon, "none" );
|
|
|
|
// This isn't working, might need an "empty" weapon
|
|
//self animscripts\shared::placeWeaponOn( self.weapon, "none" );
|
|
|
|
self allowedStances( "stand" );
|
|
self.disableArrivals = true;
|
|
self.disableExits = true;
|
|
self.grenadeawareness = 0;
|
|
self.badplaceawareness = 0;
|
|
|
|
self.ignoreSuppression = true;
|
|
self.suppressionThreshold = 1;
|
|
self.noDodgeMove = true;
|
|
self.dontShootWhileMoving = true;
|
|
self.pathenemylookahead = 0;
|
|
|
|
self.badplaceawareness = 0;
|
|
self.chatInitialized = false;
|
|
|
|
self disable_pain();
|
|
|
|
self.maxhealth = level.zombie_health;
|
|
self.health = level.zombie_health;
|
|
self.dropweapon = false;
|
|
level thread zombie_death_event( self );
|
|
|
|
// We need more script/code to get this to work properly
|
|
// self add_to_spectate_list();
|
|
self random_tan();
|
|
self set_zombie_run_cycle();
|
|
// self thread zombie_think();
|
|
self zombie_setup_attack_properties();
|
|
self thread find_flesh();
|
|
self thread zombie_gib_on_damage();
|
|
// self thread zombie_head_gib();
|
|
self zombie_eye_glow();
|
|
self.deathFunction = ::zombie_death_animscript;
|
|
self.flame_damage_time = 0;
|
|
|
|
self zombie_history( "zombie_spawn_init -> Spawned = " + self.origin );
|
|
|
|
self thread zombie_testing();
|
|
|
|
self notify( "zombie_init_done" );
|
|
}
|
|
|
|
set_zombie_run_cycle()
|
|
{
|
|
self set_run_speed();
|
|
|
|
walkcycle = randomint( 4 );
|
|
|
|
if( self.zombie_move_speed == "walk" )
|
|
{
|
|
if( walkcycle == 0 )
|
|
{
|
|
self.deathanim = %ch_dazed_a_death;
|
|
self set_run_anim( "walk1" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["walk1"];
|
|
}
|
|
else if( walkcycle == 1 )
|
|
{
|
|
self.deathanim = %ch_dazed_b_death;
|
|
self set_run_anim( "walk2" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["walk2"];
|
|
}
|
|
else if( walkcycle == 2 )
|
|
{
|
|
self.deathanim = %ch_dazed_c_death;
|
|
self set_run_anim( "walk3" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["walk3"];
|
|
}
|
|
else if( walkcycle == 3 )
|
|
{
|
|
self.deathanim = %ch_dazed_d_death;
|
|
self set_run_anim( "walk4" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["walk4"];
|
|
}
|
|
}
|
|
|
|
walkcycle = randomint( 3 );
|
|
if( self.zombie_move_speed == "run" )
|
|
{
|
|
if( walkcycle == 0 )
|
|
{
|
|
self.deathanim = %ch_dazed_a_death;
|
|
self set_run_anim( "run1" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["run1"];
|
|
}
|
|
else if( walkcycle == 1 )
|
|
{
|
|
self.deathanim = %ch_dazed_b_death;
|
|
self set_run_anim( "run2" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["run2"];
|
|
}
|
|
else if( walkcycle == 2 )
|
|
{
|
|
self.deathanim = %ch_dazed_c_death;
|
|
self set_run_anim( "run3" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["run3"];
|
|
}
|
|
}
|
|
|
|
walkcycle = randomint( 2 );
|
|
if( self.zombie_move_speed == "sprint" )
|
|
{
|
|
if( walkcycle == 0 )
|
|
{
|
|
self.deathanim = %ch_dazed_c_death;
|
|
self set_run_anim( "sprint1" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["sprint1"];
|
|
}
|
|
else if( walkcycle == 1 )
|
|
{
|
|
self.deathanim = %ch_dazed_d_death;
|
|
self set_run_anim( "sprint2" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["sprint2"];
|
|
}
|
|
}
|
|
}
|
|
|
|
set_run_speed()
|
|
{
|
|
// rand = randomintrange( level.zombie_move_speed, level.zombie_move_speed + 35 );
|
|
rand = 71;
|
|
|
|
// self thread print_run_speed( rand );
|
|
if( rand <= 35 )
|
|
{
|
|
self.zombie_move_speed = "walk";
|
|
}
|
|
else if( rand <= 70 )
|
|
{
|
|
self.zombie_move_speed = "run";
|
|
}
|
|
else
|
|
{
|
|
self.zombie_move_speed = "sprint";
|
|
}
|
|
}
|
|
|
|
// this is the main zombie think thread that starts when they spawn in
|
|
zombie_think()
|
|
{
|
|
self endon( "death" );
|
|
|
|
//node = level.exterior_goals[randomint( level.exterior_goals.size )];
|
|
|
|
node = undefined;
|
|
|
|
|
|
|
|
desired_nodes = [];
|
|
self.entrance_nodes = [];
|
|
|
|
if( IsDefined( self.script_forcegoal ) && self.script_forcegoal )
|
|
{
|
|
desired_origin = get_desired_origin();
|
|
|
|
AssertEx( IsDefined( desired_origin ), "Spawner @ " + self.origin + " has a script_forcegoal but did not find a target" );
|
|
|
|
origin = desired_origin;
|
|
|
|
node = getclosest( origin, level.exterior_goals );
|
|
self.entrance_nodes[0] = node;
|
|
|
|
self zombie_history( "zombie_think -> #1 entrance (script_forcegoal) origin = " + self.entrance_nodes[0].origin );
|
|
}
|
|
else
|
|
{
|
|
origin = self.origin;
|
|
|
|
desired_origin = get_desired_origin();
|
|
if( IsDefined( desired_origin ) )
|
|
{
|
|
origin = desired_origin;
|
|
}
|
|
|
|
// Get the 3 closest nodes
|
|
nodes = get_array_of_closest( origin, level.exterior_goals, undefined, 3 );
|
|
|
|
// Figure out the distances between them, if any of them are greater than 256 units compared to the previous, drop it
|
|
max_dist = 500;
|
|
desired_nodes[0] = nodes[0];
|
|
prev_dist = Distance( self.origin, nodes[0].origin );
|
|
for( i = 1; i < nodes.size; i++ )
|
|
{
|
|
dist = Distance( self.origin, nodes[i].origin );
|
|
if( ( dist - prev_dist ) > max_dist )
|
|
{
|
|
break;
|
|
}
|
|
|
|
prev_dist = dist;
|
|
desired_nodes[i] = nodes[i];
|
|
}
|
|
|
|
node = desired_nodes[0];
|
|
if( desired_nodes.size > 1 )
|
|
{
|
|
node = desired_nodes[RandomInt(desired_nodes.size)];
|
|
}
|
|
|
|
self.entrance_nodes = desired_nodes;
|
|
|
|
self zombie_history( "zombie_think -> #1 entrance origin = " + node.origin );
|
|
|
|
// Incase the guy does not move from spawn, then go to the closest one instead
|
|
self thread zombie_assure_node();
|
|
}
|
|
|
|
AssertEx( IsDefined( node ), "Did not find a node!!! [Should not see this!]" );
|
|
|
|
level thread draw_line_ent_to_pos( self, node.origin, "goal" );
|
|
|
|
self.first_node = node;
|
|
|
|
self thread zombie_goto_entrance( node );
|
|
}
|
|
|
|
get_desired_origin()
|
|
{
|
|
if( IsDefined( self.target ) )
|
|
{
|
|
ent = GetEnt( self.target, "targetname" );
|
|
if( !IsDefined( ent ) )
|
|
{
|
|
ent = getstruct( self.target, "targetname" );
|
|
}
|
|
|
|
if( !IsDefined( ent ) )
|
|
{
|
|
ent = GetNode( self.target, "targetname" );
|
|
}
|
|
|
|
AssertEx( IsDefined( ent ), "Cannot find the targeted ent/node/struct, \"" + self.target + "\" at " + self.origin );
|
|
|
|
return ent.origin;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
zombie_goto_entrance( node, endon_bad_path )
|
|
{
|
|
self endon( "death" );
|
|
level endon( "intermission" );
|
|
|
|
if( IsDefined( endon_bad_path ) && endon_bad_path )
|
|
{
|
|
// If we cannot go to the goal, then end...
|
|
// Used from find_flesh
|
|
self endon( "bad_path" );
|
|
}
|
|
|
|
self zombie_history( "zombie_goto_entrance -> start goto entrance " + node.origin );
|
|
|
|
self.got_to_entrance = false;
|
|
self.goalradius = 128;
|
|
self SetGoalPos( node.origin );
|
|
self waittill( "goal" );
|
|
self.got_to_entrance = true;
|
|
|
|
self zombie_history( "zombie_goto_entrance -> reached goto entrance " + node.origin );
|
|
|
|
// Guy should get to goal and tear into building until all barrier chunks are gone
|
|
// self tear_into_building();
|
|
|
|
// here is where they zombie would play the traversal into the building( if it's a window )
|
|
// and begin the player seek logic
|
|
self zombie_setup_attack_properties();
|
|
self thread find_flesh();
|
|
}
|
|
|
|
zombie_assure_node()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "goal" );
|
|
level endon( "intermission" );
|
|
|
|
start_pos = self.origin;
|
|
|
|
for( i = 0; i < self.entrance_nodes.size; i++ )
|
|
{
|
|
if( self zombie_bad_path() )
|
|
{
|
|
self zombie_history( "zombie_assure_node -> assigned assured node = " + self.entrance_nodes[i].origin );
|
|
|
|
println( "^1Zombie @ " + self.origin + " did not move for 1 second. Going to next closest node @ " + self.entrance_nodes[i].origin );
|
|
level thread draw_line_ent_to_pos( self, self.entrance_nodes[i].origin, "goal" );
|
|
self.first_node = self.entrance_nodes[i];
|
|
self SetGoalPos( self.entrance_nodes[i].origin );
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
self zombie_history( "zombie_assure_node -> failed to find a good entrance point" );
|
|
assertmsg( "^1Zombie @ " + self.origin + " did not find a good entrance point... Please fix pathing or Entity setup" );
|
|
|
|
wait( 3 );
|
|
self DoDamage( self.health + 10, self.origin );
|
|
}
|
|
|
|
zombie_bad_path()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "goal" );
|
|
|
|
self thread zombie_bad_path_notify();
|
|
self thread zombie_bad_path_timeout();
|
|
|
|
self.zombie_bad_path = undefined;
|
|
while( !IsDefined( self.zombie_bad_path ) )
|
|
{
|
|
wait( 0.05 );
|
|
}
|
|
|
|
self notify( "stop_zombie_bad_path" );
|
|
|
|
return self.zombie_bad_path;
|
|
}
|
|
|
|
zombie_bad_path_notify()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "stop_zombie_bad_path" );
|
|
|
|
self waittill( "bad_path" );
|
|
self.zombie_bad_path = true;
|
|
}
|
|
|
|
zombie_bad_path_timeout()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "stop_zombie_bad_path" );
|
|
|
|
wait( 2 );
|
|
self.zombie_bad_path = false;
|
|
}
|
|
|
|
// zombies are trying to get at player contained behind barriers, so the barriers
|
|
// need to come down
|
|
//tear_into_building()
|
|
//{
|
|
// self endon( "death" );
|
|
//
|
|
// self zombie_history( "tear_into_building -> start" );
|
|
//
|
|
// while( 1 )
|
|
// {
|
|
// if( IsDefined( self.first_node.script_noteworthy ) )
|
|
// {
|
|
// if( self.first_node.script_noteworthy == "no_blocker" )
|
|
// {
|
|
// return;
|
|
// }
|
|
// }
|
|
//
|
|
// if( !IsDefined( self.first_node.target ) )
|
|
// {
|
|
// return;
|
|
// }
|
|
//
|
|
// if( all_chunks_destroyed( self.first_node.barrier_chunks ) )
|
|
// {
|
|
// self zombie_history( "tear_into_building -> all chunks destroyed" );
|
|
// return;
|
|
// }
|
|
//
|
|
// // Pick a spot to tear down
|
|
// if( !get_attack_spot( self.first_node ) )
|
|
// {
|
|
// self zombie_history( "tear_into_building -> Could not find an attack spot" );
|
|
// wait( 0.5 );
|
|
// continue;
|
|
// }
|
|
//
|
|
// self.goalradius = 4;
|
|
// self SetGoalPos( self.attacking_spot, self.first_node.angles );
|
|
// self waittill( "orientdone" );
|
|
// self zombie_history( "tear_into_building -> Reach position and orientated" );
|
|
//
|
|
// // Now tear down boards
|
|
// while( 1 )
|
|
// {
|
|
// chunk = get_closest_non_destroyed_chunk( self.origin, self.first_node.barrier_chunks );
|
|
//
|
|
// if( !IsDefined( chunk ) )
|
|
// {
|
|
// for( i = 0; i < self.first_node.attack_spots_taken.size; i++ )
|
|
// {
|
|
// self.first_node.attack_spots_taken[i] = false;
|
|
// }
|
|
// return;
|
|
// }
|
|
//
|
|
// self zombie_history( "tear_into_building -> animating" );
|
|
//
|
|
// tear_anim = get_tear_anim( chunk );
|
|
// self AnimScripted( "tear_anim", self.origin, self.first_node.angles, tear_anim );
|
|
//// self zombie_tear_notetracks( "tear_anim", chunk, self.first_node );
|
|
// }
|
|
//
|
|
// self reset_attack_spot();
|
|
// }
|
|
//}
|
|
|
|
reset_attack_spot()
|
|
{
|
|
if( IsDefined( self.attacking_node ) )
|
|
{
|
|
node = self.attacking_node;
|
|
index = self.attacking_spot_index;
|
|
node.attack_spots_taken[index] = false;
|
|
|
|
self.attacking_node = undefined;
|
|
self.attacking_spot_index = undefined;
|
|
}
|
|
}
|
|
|
|
get_attack_spot( node )
|
|
{
|
|
index = get_attack_spot_index( node );
|
|
if( !IsDefined( index ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
self.attacking_node = node;
|
|
self.attacking_spot_index = index;
|
|
node.attack_spots_taken[index] = true;
|
|
self.attacking_spot = node.attack_spots[index];
|
|
|
|
return true;
|
|
}
|
|
|
|
get_attack_spot_index( node )
|
|
{
|
|
indexes = [];
|
|
for( i = 0; i < node.attack_spots.size; i++ )
|
|
{
|
|
if( !node.attack_spots_taken[i] )
|
|
{
|
|
indexes[indexes.size] = i;
|
|
}
|
|
}
|
|
|
|
if( indexes.size == 0 )
|
|
{
|
|
return undefined;
|
|
}
|
|
|
|
return indexes[RandomInt( indexes.size )];
|
|
}
|
|
|
|
//zombie_tear_notetracks( msg, chunk, node )
|
|
//{
|
|
// while( 1 )
|
|
// {
|
|
// self waittill( msg, notetrack );
|
|
//
|
|
// if( notetrack == "end" )
|
|
// {
|
|
// return;
|
|
// }
|
|
//
|
|
// if( notetrack == "board" )
|
|
// {
|
|
// if( !chunk.destroyed )
|
|
// {
|
|
// self.lastchunk_destroy_time = getTime();
|
|
//
|
|
// PlayFx( level._effect["wood_chunk_destory"], chunk.origin );
|
|
// PlayFx( level._effect["wood_chunk_destory"], chunk.origin + ( randomint( 20 ), randomint( 20 ), randomint( 10 ) ) );
|
|
// PlayFx( level._effect["wood_chunk_destory"], chunk.origin + ( randomint( 40 ), randomint( 40 ), randomint( 20 ) ) );
|
|
//
|
|
// level thread maps\\payload_zombiemode_blockers::remove_chunk( chunk, node );
|
|
// }
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
get_tear_anim( chunk )
|
|
{
|
|
if( self.has_legs )
|
|
{
|
|
z_dist = chunk.origin[2] - self.origin[2];
|
|
if( z_dist > 70 )
|
|
{
|
|
tear_anim = %ai_zombie_door_tear_high;
|
|
}
|
|
else if( z_dist < 40 )
|
|
{
|
|
tear_anim = %ai_zombie_door_tear_low;
|
|
}
|
|
else
|
|
{
|
|
anims = [];
|
|
anims[anims.size] = %ai_zombie_door_tear_left;
|
|
anims[anims.size] = %ai_zombie_door_tear_right;
|
|
|
|
tear_anim = anims[RandomInt( anims.size )];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
anims = [];
|
|
anims[anims.size] = %ai_zombie_attack_crawl;
|
|
anims[anims.size] = %ai_zombie_attack_crawl_lunge;
|
|
|
|
tear_anim = anims[RandomInt( anims.size )];
|
|
}
|
|
|
|
return tear_anim;
|
|
}
|
|
|
|
zombie_head_gib( attacker )
|
|
{
|
|
if( IsDefined( self.head_gibbed ) && self.head_gibbed )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self.head_gibbed = true;
|
|
self zombie_eye_glow_stop();
|
|
|
|
size = self GetAttachSize();
|
|
for( i = 0; i < size; i++ )
|
|
{
|
|
model = self GetAttachModelName( i );
|
|
if( IsSubStr( model, "head" ) )
|
|
{
|
|
// SRS 9/2/2008: wet em up
|
|
self thread headshot_blood_fx();
|
|
self play_sound_on_ent( "zombie_head_gib" );
|
|
|
|
self Detach( model, "", true );
|
|
self Attach( "char_ger_honorgd_zomb_behead", "", true );
|
|
break;
|
|
}
|
|
}
|
|
|
|
self thread damage_over_time( self.health * 0.2, 1, attacker );
|
|
}
|
|
|
|
damage_over_time( dmg, delay, attacker )
|
|
{
|
|
self endon( "death" );
|
|
|
|
if( !IsAlive( self ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !IsPlayer( attacker ) )
|
|
{
|
|
attacker = undefined;
|
|
}
|
|
|
|
while( 1 )
|
|
{
|
|
wait( delay );
|
|
|
|
if( IsDefined( attacker ) )
|
|
{
|
|
self DoDamage( dmg, self.origin, attacker );
|
|
}
|
|
else
|
|
{
|
|
self DoDamage( dmg, self.origin );
|
|
}
|
|
}
|
|
}
|
|
|
|
// SRS 9/2/2008: reordered checks, added ability to gib heads with airburst grenades
|
|
head_should_gib( attacker, type, point )
|
|
{
|
|
if ( is_german_build() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( self.head_gibbed )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check if the attacker was a player
|
|
if( !IsDefined( attacker ) || !IsPlayer( attacker ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check the enemy's health
|
|
low_health_percent = ( self.health / self.maxhealth ) * 100;
|
|
if( low_health_percent > 10 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
weapon = attacker GetCurrentWeapon();
|
|
|
|
// SRS 9/2/2008: check for damage type
|
|
// - most SMGs use pistol bullets
|
|
// - projectiles = rockets, raygun
|
|
if( type != "MOD_RIFLE_BULLET" && type != "MOD_PISTOL_BULLET" )
|
|
{
|
|
// maybe it's ok, let's see if it's a grenade
|
|
if( type == "MOD_GRENADE" || type == "MOD_GRENADE_SPLASH" )
|
|
{
|
|
if( Distance( point, self GetTagOrigin( "j_head" ) ) > 55 )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// the grenade airburst close to the head so return true
|
|
return true;
|
|
}
|
|
}
|
|
else if( type == "MOD_PROJECTILE" )
|
|
{
|
|
if( Distance( point, self GetTagOrigin( "j_head" ) ) > 10 )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// shottys don't give a testable damage type but should still gib heads
|
|
else if( WeaponClass( weapon ) != "spread" )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// check location now that we've checked for grenade damage (which reports "none" as a location)
|
|
if( !self animscripts\utility::damageLocationIsAny( "head", "helmet", "neck" ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check weapon - don't want "none", pistol, or flamethrower
|
|
if( weapon == "none" || WeaponClass( weapon ) == "pistol" || WeaponIsGasWeapon( self.weapon ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// does blood fx for fun and to mask head gib swaps
|
|
headshot_blood_fx()
|
|
{
|
|
if( !IsDefined( self ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !is_mature() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
fxTag = "j_neck";
|
|
fxOrigin = self GetTagOrigin( fxTag );
|
|
upVec = AnglesToUp( self GetTagAngles( fxTag ) );
|
|
forwardVec = AnglesToForward( self GetTagAngles( fxTag ) );
|
|
|
|
// main head pop fx
|
|
PlayFX( level._effect["headshot"], fxOrigin, forwardVec, upVec );
|
|
PlayFX( level._effect["headshot_nochunks"], fxOrigin, forwardVec, upVec );
|
|
|
|
wait( 0.3 );
|
|
|
|
if( IsDefined( self ) )
|
|
{
|
|
PlayFxOnTag( level._effect["bloodspurt"], self, fxTag );
|
|
}
|
|
}
|
|
|
|
// gib limbs if enough firepower occurs
|
|
zombie_gib_on_damage()
|
|
{
|
|
// self endon( "death" );
|
|
|
|
while( 1 )
|
|
{
|
|
self waittill( "damage", amount, attacker, direction_vec, point, type );
|
|
|
|
if( !IsDefined( self ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !self zombie_should_gib( amount, attacker, type ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( self head_should_gib( attacker, type, point ) && type != "MOD_BURNED" )
|
|
{
|
|
self zombie_head_gib( attacker );
|
|
continue;
|
|
}
|
|
|
|
if( !self.gibbed )
|
|
{
|
|
// The head_should_gib() above checks for this, so we should not randomly gib if shot in the head
|
|
if( self animscripts\utility::damageLocationIsAny( "head", "helmet", "neck" ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
refs = [];
|
|
switch( self.damageLocation )
|
|
{
|
|
case "torso_upper":
|
|
case "torso_lower":
|
|
// HACK the torso that gets swapped for guts also removes the left arm
|
|
// so we need to sometimes do another ref
|
|
refs[refs.size] = "guts";
|
|
refs[refs.size] = "right_arm";
|
|
break;
|
|
|
|
case "right_arm_upper":
|
|
case "right_arm_lower":
|
|
case "right_hand":
|
|
//if( IsDefined( self.left_arm_gibbed ) )
|
|
// refs[refs.size] = "no_arms";
|
|
//else
|
|
refs[refs.size] = "right_arm";
|
|
|
|
//self.right_arm_gibbed = true;
|
|
break;
|
|
|
|
case "left_arm_upper":
|
|
case "left_arm_lower":
|
|
case "left_hand":
|
|
//if( IsDefined( self.right_arm_gibbed ) )
|
|
// refs[refs.size] = "no_arms";
|
|
//else
|
|
refs[refs.size] = "left_arm";
|
|
|
|
//self.left_arm_gibbed = true;
|
|
break;
|
|
|
|
case "right_leg_upper":
|
|
case "right_leg_lower":
|
|
case "right_foot":
|
|
if( self.health <= 0 )
|
|
{
|
|
// Addition "right_leg" refs so that the no_legs happens less and is more rare
|
|
refs[refs.size] = "right_leg";
|
|
refs[refs.size] = "right_leg";
|
|
refs[refs.size] = "right_leg";
|
|
refs[refs.size] = "no_legs";
|
|
}
|
|
break;
|
|
|
|
case "left_leg_upper":
|
|
case "left_leg_lower":
|
|
case "left_foot":
|
|
if( self.health <= 0 )
|
|
{
|
|
// Addition "left_leg" refs so that the no_legs happens less and is more rare
|
|
refs[refs.size] = "left_leg";
|
|
refs[refs.size] = "left_leg";
|
|
refs[refs.size] = "left_leg";
|
|
refs[refs.size] = "no_legs";
|
|
}
|
|
break;
|
|
default:
|
|
|
|
if( self.damageLocation == "none" )
|
|
{
|
|
// SRS 9/7/2008: might be a nade or a projectile
|
|
if( type == "MOD_GRENADE" || type == "MOD_GRENADE_SPLASH" || type == "MOD_PROJECTILE" )
|
|
{
|
|
// ... in which case we have to derive the ref ourselves
|
|
refs = self derive_damage_refs( point );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
refs[refs.size] = "guts";
|
|
refs[refs.size] = "right_arm";
|
|
refs[refs.size] = "left_arm";
|
|
refs[refs.size] = "right_leg";
|
|
refs[refs.size] = "left_leg";
|
|
refs[refs.size] = "no_legs";
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( refs.size )
|
|
{
|
|
self.a.gib_ref = animscripts\death::get_random( refs );
|
|
|
|
// Don't stand if a leg is gone
|
|
if( ( self.a.gib_ref == "no_legs" || self.a.gib_ref == "right_leg" || self.a.gib_ref == "left_leg" ) && self.health > 0 )
|
|
{
|
|
self.has_legs = false;
|
|
self AllowedStances( "crouch" );
|
|
|
|
which_anim = RandomInt( 3 );
|
|
|
|
if( which_anim == 0 )
|
|
{
|
|
self.deathanim = %ai_zombie_crawl_death_v1;
|
|
self set_run_anim( "death3" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["crawl1"];
|
|
self.crouchRunAnim = level.scr_anim["zombie"]["crawl1"];
|
|
self.crouchrun_combatanim = level.scr_anim["zombie"]["crawl1"];
|
|
}
|
|
else if( which_anim == 1 )
|
|
{
|
|
self.deathanim = %ai_zombie_crawl_death_v2;
|
|
self set_run_anim( "death4" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["crawl2"];
|
|
self.crouchRunAnim = level.scr_anim["zombie"]["crawl2"];
|
|
self.crouchrun_combatanim = level.scr_anim["zombie"]["crawl2"];
|
|
}
|
|
else if( which_anim == 2 )
|
|
{
|
|
self.deathanim = %ai_zombie_crawl_death_v2;
|
|
self set_run_anim( "death4" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["crawl3"];
|
|
self.crouchRunAnim = level.scr_anim["zombie"]["crawl3"];
|
|
self.crouchrun_combatanim = level.scr_anim["zombie"]["crawl3"];
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if( self.health > 0 )
|
|
{
|
|
// force gibbing if the zombie is still alive
|
|
self thread animscripts\death::do_gib();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
zombie_should_gib( amount, attacker, type )
|
|
{
|
|
if( !IsDefined( type ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch( type )
|
|
{
|
|
case "MOD_UNKNOWN":
|
|
case "MOD_CRUSH":
|
|
case "MOD_TELEFRAG":
|
|
case "MOD_FALLING":
|
|
case "MOD_SUICIDE":
|
|
case "MOD_TRIGGER_HURT":
|
|
case "MOD_BURNED":
|
|
case "MOD_MELEE":
|
|
return false;
|
|
}
|
|
|
|
if( type == "MOD_PISTOL_BULLET" || type == "MOD_RIFLE_BULLET" )
|
|
{
|
|
if( !IsDefined( attacker ) || !IsPlayer( attacker ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
weapon = attacker GetCurrentWeapon();
|
|
|
|
if( weapon == "none" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( WeaponClass( weapon ) == "pistol" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( WeaponIsGasWeapon( self.weapon ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// println( "**DEBUG amount = ", amount );
|
|
// println( "**DEBUG self.head_gibbed = ", self.head_gibbed );
|
|
// println( "**DEBUG self.health = ", self.health );
|
|
|
|
prev_health = amount + self.health;
|
|
if( prev_health <= 0 )
|
|
{
|
|
prev_health = 1;
|
|
}
|
|
|
|
damage_percent = ( amount / prev_health ) * 100;
|
|
|
|
if( damage_percent < 10 /*|| damage_percent >= 100*/ )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// SRS 9/7/2008: need to derive damage location for types that return location of "none"
|
|
derive_damage_refs( point )
|
|
{
|
|
if( !IsDefined( level.gib_tags ) )
|
|
{
|
|
init_gib_tags();
|
|
}
|
|
|
|
closestTag = undefined;
|
|
|
|
for( i = 0; i < level.gib_tags.size; i++ )
|
|
{
|
|
if( !IsDefined( closestTag ) )
|
|
{
|
|
closestTag = level.gib_tags[i];
|
|
}
|
|
else
|
|
{
|
|
if( DistanceSquared( point, self GetTagOrigin( level.gib_tags[i] ) ) < DistanceSquared( point, self GetTagOrigin( closestTag ) ) )
|
|
{
|
|
closestTag = level.gib_tags[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
refs = [];
|
|
|
|
// figure out the refs based on the tag returned
|
|
if( closestTag == "J_SpineLower" || closestTag == "J_SpineUpper" || closestTag == "J_Spine4" )
|
|
{
|
|
// HACK the torso that gets swapped for guts also removes the left arm
|
|
// so we need to sometimes do another ref
|
|
refs[refs.size] = "guts";
|
|
refs[refs.size] = "right_arm";
|
|
}
|
|
else if( closestTag == "J_Shoulder_LE" || closestTag == "J_Elbow_LE" || closestTag == "J_Wrist_LE" )
|
|
{
|
|
refs[refs.size] = "left_arm";
|
|
}
|
|
else if( closestTag == "J_Shoulder_RI" || closestTag == "J_Elbow_RI" || closestTag == "J_Wrist_RI" )
|
|
{
|
|
refs[refs.size] = "right_arm";
|
|
}
|
|
else if( closestTag == "J_Hip_LE" || closestTag == "J_Knee_LE" || closestTag == "J_Ankle_LE" )
|
|
{
|
|
refs[refs.size] = "left_leg";
|
|
refs[refs.size] = "no_legs";
|
|
}
|
|
else if( closestTag == "J_Hip_RI" || closestTag == "J_Knee_RI" || closestTag == "J_Ankle_RI" )
|
|
{
|
|
refs[refs.size] = "right_leg";
|
|
refs[refs.size] = "no_legs";
|
|
}
|
|
|
|
ASSERTEX( array_validate( refs ), "get_closest_damage_refs(): couldn't derive refs from closestTag " + closestTag );
|
|
|
|
return refs;
|
|
}
|
|
|
|
init_gib_tags()
|
|
{
|
|
tags = [];
|
|
|
|
// "guts", "right_arm", "left_arm", "right_leg", "left_leg", "no_legs"
|
|
|
|
// "guts"
|
|
tags[tags.size] = "J_SpineLower";
|
|
tags[tags.size] = "J_SpineUpper";
|
|
tags[tags.size] = "J_Spine4";
|
|
|
|
// "left_arm"
|
|
tags[tags.size] = "J_Shoulder_LE";
|
|
tags[tags.size] = "J_Elbow_LE";
|
|
tags[tags.size] = "J_Wrist_LE";
|
|
|
|
// "right_arm"
|
|
tags[tags.size] = "J_Shoulder_RI";
|
|
tags[tags.size] = "J_Elbow_RI";
|
|
tags[tags.size] = "J_Wrist_RI";
|
|
|
|
// "left_leg"/"no_legs"
|
|
tags[tags.size] = "J_Hip_LE";
|
|
tags[tags.size] = "J_Knee_LE";
|
|
tags[tags.size] = "J_Ankle_LE";
|
|
|
|
// "right_leg"/"no_legs"
|
|
tags[tags.size] = "J_Hip_RI";
|
|
tags[tags.size] = "J_Knee_RI";
|
|
tags[tags.size] = "J_Ankle_RI";
|
|
|
|
level.gib_tags = tags;
|
|
}
|
|
|
|
zombie_death_points( origin, mod, hit_location, player )
|
|
{
|
|
// level thread maps\\payload_zombiemode_powerups::powerup_drop( origin );
|
|
|
|
if( !IsDefined( player ) || !IsPlayer( player ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// player maps\\payload_zombiemode_score::player_add_points( "death", mod, hit_location );
|
|
}
|
|
|
|
// Called from animscripts\death.gsc
|
|
zombie_death_animscript()
|
|
{
|
|
self reset_attack_spot();
|
|
|
|
// If no_legs, then use the AI no-legs death
|
|
if( self.has_legs && IsDefined( self.a.gib_ref ) && self.a.gib_ref == "no_legs" )
|
|
{
|
|
self.deathanim = %ai_gib_bothlegs_gib;
|
|
}
|
|
|
|
self.grenadeAmmo = 0;
|
|
|
|
// Give attacker points
|
|
level zombie_death_points( self.origin, self.damagemod, self.damagelocation, self.attacker );
|
|
|
|
if( self.damagemod == "MOD_BURNED" )
|
|
{
|
|
self thread animscripts\death::flame_death_fx();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
damage_on_fire( player )
|
|
{
|
|
self endon ("death");
|
|
self endon ("stop_flame_damage");
|
|
wait( 2 );
|
|
|
|
while( isdefined( self.is_on_fire) && self.is_on_fire )
|
|
{
|
|
if( level.round_number < 6 )
|
|
{
|
|
dmg = level.zombie_health * RandomFloatRange( 0.2, 0.3 ); // 20% - 30%
|
|
}
|
|
else if( level.round_number < 9 )
|
|
{
|
|
dmg = level.zombie_health * RandomFloatRange( 0.1, 0.2 ); // 10% - 20%
|
|
}
|
|
else if( level.round_number < 11 )
|
|
{
|
|
dmg = level.zombie_health * RandomFloatRange( 0.8, 0.16 ); // 6% - 14%
|
|
}
|
|
else
|
|
{
|
|
dmg = level.zombie_health * RandomFloatRange( 0.06, 0.14 ); // 5% - 10%
|
|
}
|
|
|
|
if ( Isdefined( player ) && Isalive( player ) )
|
|
{
|
|
self DoDamage( dmg, self.origin, player );
|
|
}
|
|
else
|
|
{
|
|
self DoDamage( dmg, self.origin, level );
|
|
}
|
|
|
|
wait( randomfloatrange( 2.0, 5.0 ) );
|
|
}
|
|
}
|
|
|
|
zombie_damage( mod, hit_location, hit_origin, player )
|
|
{
|
|
if( !IsDefined( player ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( self zombie_flame_damage( mod, player ) )
|
|
{
|
|
if( self zombie_give_flame_damage_points() )
|
|
{
|
|
// player maps\\payload_zombiemode_score::player_add_points( "damage", mod, hit_location );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// player maps\\payload_zombiemode_score::player_add_points( "damage", mod, hit_location );
|
|
}
|
|
|
|
if ( mod == "MOD_GRENADE" || mod == "MOD_GRENADE_SPLASH" )
|
|
{
|
|
if ( isdefined( player ) && isalive( player ) )
|
|
{
|
|
self DoDamage( level.round_number + randomint( 100, 500 ), self.origin, player);
|
|
}
|
|
else
|
|
{
|
|
self DoDamage( level.round_number + randomint( 100, 500 ), self.origin, undefined );
|
|
}
|
|
}
|
|
else if( mod == "MOD_PROJECTILE" || mod == "MOD_EXPLOSIVE" || mod == "MOD_PROJECTILE_SPLASH" || mod == "MOD_PROJECTILE_SPLASH")
|
|
{
|
|
if ( isdefined( player ) && isalive( player ) )
|
|
{
|
|
self DoDamage( level.round_number * randomint( 100, 500 ), self.origin, player);
|
|
}
|
|
else
|
|
{
|
|
self DoDamage( level.round_number * randomint( 100, 500 ), self.origin, undefined );
|
|
}
|
|
}
|
|
|
|
// self thread maps\\payload_zombiemode_powerups::check_for_instakill( player );
|
|
}
|
|
|
|
zombie_damage_ads( mod, hit_location, hit_origin, player )
|
|
{
|
|
if( !IsDefined( player ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( self zombie_flame_damage( mod, player ) )
|
|
{
|
|
if( self zombie_give_flame_damage_points() )
|
|
{
|
|
// player maps\\payload_zombiemode_score::player_add_points( "damage_ads", mod, hit_location );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// player maps\\payload_zombiemode_score::player_add_points( "damage_ads", mod, hit_location );
|
|
}
|
|
|
|
// self thread maps\\payload_zombiemode_powerups::check_for_instakill( player );
|
|
}
|
|
|
|
zombie_give_flame_damage_points()
|
|
{
|
|
if( GetTime() > self.flame_damage_time )
|
|
{
|
|
self.flame_damage_time = GetTime() + level.zombie_vars["zombie_flame_dmg_point_delay"];
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
zombie_flame_damage( mod, player )
|
|
{
|
|
if( mod == "MOD_BURNED" )
|
|
{
|
|
if( !IsDefined( self.is_on_fire ) || ( Isdefined( self.is_on_fire ) && !self.is_on_fire ) )
|
|
{
|
|
self thread damage_on_fire( player );
|
|
}
|
|
|
|
do_flame_death = true;
|
|
dist = 100 * 100;
|
|
ai = GetAiArray( "axis" );
|
|
for( i = 0; i < ai.size; i++ )
|
|
{
|
|
if( IsDefined( ai[i].is_on_fire ) && ai[i].is_on_fire )
|
|
{
|
|
if( DistanceSquared( ai[i].origin, self.origin ) < dist )
|
|
{
|
|
do_flame_death = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( do_flame_death )
|
|
{
|
|
self thread animscripts\death::flame_death_fx();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
zombie_death_event( zombie )
|
|
{
|
|
zombie waittill( "death" );
|
|
zombie thread zombie_eye_glow_stop();
|
|
}
|
|
|
|
// this is where zombies go into attack mode, and need different attributes set up
|
|
zombie_setup_attack_properties()
|
|
{
|
|
self zombie_history( "zombie_setup_attack_properties()" );
|
|
|
|
// allows zombie to attack again
|
|
self.ignoreall = false;
|
|
|
|
// push the player out of the way so they use traversals in the house.
|
|
self PushPlayer( true );
|
|
|
|
self.pathEnemyFightDist = 64;
|
|
self.meleeAttackDist = 64;
|
|
|
|
// turn off transition anims
|
|
self.disableArrivals = true;
|
|
self.disableExits = true;
|
|
}
|
|
|
|
// the seeker logic for zombies
|
|
find_flesh()
|
|
{
|
|
self endon( "death" );
|
|
level endon( "intermission" );
|
|
|
|
if( level.intermission )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self.ignore_player = undefined;
|
|
|
|
self zombie_history( "find flesh -> start" );
|
|
|
|
self.goalradius = 32;
|
|
while( 1 )
|
|
{
|
|
players = get_players();
|
|
// If playing single player, never ignore the player
|
|
if( players.size == 1 )
|
|
{
|
|
self.ignore_player = undefined;
|
|
}
|
|
|
|
player = get_closest_valid_player( self.origin, self.ignore_player );
|
|
|
|
if( !IsDefined( player ) )
|
|
{
|
|
self zombie_history( "find flesh -> can't find player, continue" );
|
|
if( IsDefined( self.ignore_player ) )
|
|
{
|
|
self.ignore_player = undefined;
|
|
}
|
|
|
|
wait( 1 );
|
|
continue;
|
|
}
|
|
|
|
self.ignore_player = undefined;
|
|
|
|
self.favoriteenemy = player;
|
|
self thread zombie_pathing();
|
|
|
|
self.zombie_path_timer = GetTime() + ( RandomFloatRange( 1, 3 ) * 1000 );
|
|
while( GetTime() < self.zombie_path_timer )
|
|
{
|
|
wait( 0.1 );
|
|
}
|
|
|
|
self zombie_history( "find flesh -> bottom of loop" );
|
|
|
|
self notify( "zombie_acquire_enemy" );
|
|
}
|
|
}
|
|
|
|
zombie_testing()
|
|
{
|
|
/#
|
|
self endon( "death" );
|
|
|
|
while( 1 )
|
|
{
|
|
if( GetDvarInt( "zombie_soak_test" ) < 1 )
|
|
{
|
|
wait( 1 );
|
|
continue;
|
|
}
|
|
|
|
if( !IsDefined( self.favoriteenemy ) )
|
|
{
|
|
wait( 0.5 );
|
|
continue;
|
|
}
|
|
|
|
if( DistanceSquared( self.origin, self.favoriteenemy.origin ) < 64 * 64 )
|
|
{
|
|
self zombie_head_gib();
|
|
self DoDamage( self.health + 10, self.origin );
|
|
}
|
|
|
|
wait( 0.05 );
|
|
}
|
|
#/
|
|
}
|
|
|
|
zombie_pathing()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "zombie_acquire_enemy" );
|
|
level endon( "intermission" );
|
|
|
|
self notify( "stop_acquire_acquire_enemy" );
|
|
self endon( "stop_acquire_acquire_enemy" );
|
|
|
|
if( IsDefined( self.favoriteenemy ) )
|
|
{
|
|
self.favoriteenemy endon( "disconnect" );
|
|
self thread zombie_follow_enemy();
|
|
|
|
self waittill( "bad_path" );
|
|
enemy_is_not_valid = false;
|
|
for( i = 0; i < self.favoriteenemy.zombie_breadcrumbs.size; i++ )
|
|
{
|
|
self.zombie_path_timer += 1000;
|
|
|
|
if( !is_player_valid( self.favoriteenemy ) )
|
|
{
|
|
enemy_is_not_valid = true;
|
|
break;
|
|
}
|
|
|
|
self SetGoalPos( self.favoriteenemy.zombie_breadcrumbs[i] );
|
|
self waittill( "bad_path" );
|
|
}
|
|
|
|
self zombie_history( "find flesh -> no breadcrumbs to follow, bad_pathed out" );
|
|
|
|
if( enemy_is_not_valid )
|
|
{
|
|
println( "^zombie_pathing() -- enemy_is_not_valid, setting zombie_path_timer to 0" );
|
|
self.zombie_path_timer = 0;
|
|
return;
|
|
}
|
|
|
|
// We failed to get to the player, now do something about it...
|
|
println( "^1UNABLE TO PATH TO FAVORITE ENEMY" );
|
|
|
|
if( self in_playable_area() )
|
|
{
|
|
self zombie_history( "find flesh -> in playable area, will find a different enemy to follow" );
|
|
println( "^3zombie_pathing() -- breadcrumbs failed, zombie in playable are, setting zombie_path_timer to 0" );
|
|
|
|
// Ignore the previous target when searching for a new one.
|
|
self.ignore_player = self.favoriteenemy;
|
|
|
|
// Tells the AI to look for a new player right away.
|
|
self.zombie_path_timer = 0;
|
|
}
|
|
else
|
|
{
|
|
// Ok, we failed to get to the player, used for when AI cannot path after tearing barriers
|
|
if( IsDefined( self.entrance_nodes[0] ) )
|
|
{
|
|
self zombie_history( "find flesh -> failed to get to a player and not in playable area" );
|
|
self thread zombie_goto_entrance( self.entrance_nodes[0], true );
|
|
|
|
// If we fail to get to the entrance, and have NOT 'got_to_entrance' restart the loop
|
|
self waittill( "bad_path" );
|
|
|
|
if( !self.got_to_entrance )
|
|
{
|
|
println( "^3zombie_pathing() -- entrance node bad path, setting zombie_path_timer to 0" );
|
|
self.zombie_path_timer = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self zombie_history( "find flesh -> no favoriteenemy" );
|
|
debug_print( "NO FAVORITEENEMY!" );
|
|
}
|
|
}
|
|
|
|
zombie_follow_enemy()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "zombie_acquire_enemy" );
|
|
self endon( "bad_path" );
|
|
level endon( "intermission" );
|
|
|
|
while( 1 )
|
|
{
|
|
if( IsDefined( self.favoriteenemy ) )
|
|
{
|
|
self SetGoalPos( self.favoriteenemy.origin );
|
|
}
|
|
|
|
wait( 0.1 );
|
|
}
|
|
}
|
|
|
|
|
|
// When a Zombie spawns, set his eyes to glowing.
|
|
zombie_eye_glow()
|
|
{
|
|
if( IsDefined( level.zombie_eye_glow ) && !level.zombie_eye_glow )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !IsDefined( self ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
linkTag = "J_Eyeball_LE";
|
|
fxModel = "tag_origin";
|
|
fxTag = "tag_origin";
|
|
|
|
// SRS 9/2/2008: only using one particle now per Barry's request;
|
|
// modified to be able to turn particle off
|
|
self.fx_eye_glow = Spawn( "script_model", self GetTagOrigin( linkTag ) );
|
|
self.fx_eye_glow.angles = self GetTagAngles( linkTag );
|
|
self.fx_eye_glow SetModel( fxModel );
|
|
self.fx_eye_glow LinkTo( self, linkTag );
|
|
|
|
// TEMP for testing
|
|
//self.fx_eye_glow thread maps\_debug::drawTagForever( fxTag );
|
|
|
|
PlayFxOnTag( level._effect["eye_glow"], self.fx_eye_glow, fxTag );
|
|
}
|
|
|
|
// Called when either the Zombie dies or if his head gets blown off
|
|
zombie_eye_glow_stop()
|
|
{
|
|
if( IsDefined( self.fx_eye_glow ) )
|
|
{
|
|
self.fx_eye_glow Delete();
|
|
}
|
|
}
|
|
|
|
//
|
|
// DEBUG
|
|
//
|
|
|
|
zombie_history( msg )
|
|
{
|
|
/#
|
|
if( !IsDefined( self.zombie_history ) )
|
|
{
|
|
self.zombie_history = [];
|
|
}
|
|
|
|
self.zombie_history[self.zombie_history.size] = msg;
|
|
#/
|
|
} |