2616 lines
No EOL
64 KiB
Text
2616 lines
No EOL
64 KiB
Text
#include maps\_utility;
|
|
#include common_scripts\utility;
|
|
#include maps\_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);
|
|
array_thread(zombies, ::add_spawn_function, ::zombie_rise);
|
|
|
|
|
|
dogs = getEntArray( "zombie_dog_spawner", "script_noteworthy" );
|
|
later_dogs = getentarray("later_round_dog_spawners", "script_noteworthy" );
|
|
|
|
dogs = array_combine( dogs, later_dogs );
|
|
|
|
for( i = 0; i < dogs.size; i++ )
|
|
{
|
|
if( is_spawner_targeted_by_blocker( dogs[i] ) )
|
|
{
|
|
dogs[i].locked_spawner = true;
|
|
}
|
|
}
|
|
|
|
array_thread( dogs, ::add_spawn_function, ::zombie_spawn_init_dog );
|
|
|
|
}
|
|
|
|
|
|
#using_animtree ("dog");
|
|
zombie_spawn_dog_tracker()
|
|
{
|
|
self.melee_attack_anim = %ai_zombie_dog_attack_v1;
|
|
self endon("death");
|
|
while(1)
|
|
{
|
|
wait(.25);
|
|
players = get_players();
|
|
for(i=0;i<players.size;i++)
|
|
{
|
|
if(isAlive(players[i]) && !isDefined(players[i].revivetrigger))
|
|
{
|
|
if (distance2d(self.origin,players[i].origin) <=48)
|
|
{
|
|
self.melee_attack_anim = %ai_zombie_dog_attack_v1;
|
|
}
|
|
else
|
|
{
|
|
self.melee_attack_anim = %german_shepherd_run_attack;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#using_animtree( "generic_human" );
|
|
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 thread zombie_gib_on_damage();
|
|
self thread zombie_damage_failsafe();
|
|
|
|
// self thread zombie_head_gib();
|
|
self thread delayed_zombie_eye_glow(); // delayed eye glow for ground crawlers (the eyes floated above the ground before the anim started)
|
|
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" );
|
|
}
|
|
|
|
/*
|
|
delayed_zombie_eye_glow:
|
|
Fixes problem where zombies that climb out of the ground are warped to their start positions
|
|
and their eyes glowed above the ground for a split second before their animation started even
|
|
though the zombie model is hidden. and applying this delay to all the zombies doesn't really matter.
|
|
*/
|
|
delayed_zombie_eye_glow()
|
|
{
|
|
wait .5;
|
|
self zombie_eye_glow();
|
|
}
|
|
|
|
zombie_spawn_init_dog()
|
|
{
|
|
self.targetname = "zombie_dog";
|
|
self.script_noteworthy = undefined;
|
|
self.animname = "zombie_dog";
|
|
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;
|
|
|
|
self PushPlayer( true );
|
|
|
|
// 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;
|
|
|
|
//chrisp - 60% chance that dogs will explode during death
|
|
if(randomint(100)>40)
|
|
{
|
|
self.a.nodeath = true;
|
|
}
|
|
|
|
//self.deathfunction = ::zombie_dog_death;
|
|
self thread zombie_dog_eye_glow();
|
|
|
|
self disable_pain();
|
|
|
|
self.maxhealth = int(level.zombie_health / 4);
|
|
self.health = int(level.zombie_health / 4);
|
|
|
|
self thread zombie_think();
|
|
//self thread zombie_damage_failsafe(); // if pounced on, might cause problems
|
|
|
|
self.flame_damage_time = 0;
|
|
|
|
self zombie_history( "zombie_dog_spawn_init -> Spawned = " + self.origin );
|
|
|
|
self thread zombie_testing();
|
|
|
|
self notify( "zombie_dog_init_done" );
|
|
|
|
//chris_p - zombie dogs change their melee anims depending on distance from enemy
|
|
self thread zombie_spawn_dog_tracker();
|
|
//chris_p - zombie dogs should give points when damaged/killed
|
|
self thread zombie_dog_spawn_dmg_watch();
|
|
self thread zombie_dog_spawn_death_watch();
|
|
|
|
}
|
|
|
|
/*------------------------------------
|
|
zombie dogs give points when damaged
|
|
------------------------------------*/
|
|
zombie_dog_spawn_dmg_watch()
|
|
{
|
|
self endon("death");
|
|
|
|
while(1)
|
|
{
|
|
self waittill("damage", amount ,attacker, dir, point, mod);
|
|
self thread zombie_damage( self.damagemod, self.damagelocation, self.origin, self.attacker,true );
|
|
}
|
|
}
|
|
zombie_dog_spawn_death_watch()
|
|
{
|
|
self waittill("death");
|
|
|
|
//remove the glowing eyes
|
|
if( IsDefined( self.fx_eye_glow ) )
|
|
{
|
|
self.fx_eye_glow Delete();
|
|
}
|
|
|
|
if(isPlayer(self.attacker))
|
|
{
|
|
self.attacker maps\_zombiemode_score::player_add_points( "death", self.damagemod, self.damagelocation,true );
|
|
}
|
|
|
|
if(isDefined(self.a.nodeath))
|
|
{
|
|
playfx(level._effect["dog_gib"],self.origin);
|
|
|
|
self delete();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
zombie_damage_failsafe()
|
|
{
|
|
self endon ("death");
|
|
|
|
continue_failsafe_damage = false;
|
|
while (1)
|
|
{
|
|
//should only be for zombie exploits
|
|
wait 0.5;
|
|
|
|
if (!isdefined(self.enemy))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (self istouching(self.enemy))
|
|
{
|
|
old_org = self.origin;
|
|
if (!continue_failsafe_damage)
|
|
{
|
|
wait 5;
|
|
}
|
|
|
|
if (!isdefined(self.enemy))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (self istouching(self.enemy)
|
|
&& !self.enemy maps\_laststand::player_is_in_laststand()
|
|
&& isalive(self.enemy))
|
|
{
|
|
if (distancesquared(old_org, self.origin) < (35 * 35) )
|
|
{
|
|
setsaveddvar("player_deathInvulnerableTime", 0);
|
|
self.enemy DoDamage( self.enemy.health + 1000, self.enemy.origin, undefined, undefined, "riflebullet" );
|
|
setsaveddvar("player_deathInvulnerableTime", level.startInvulnerableTime);
|
|
|
|
continue_failsafe_damage = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
continue_failsafe_damage = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
set_zombie_run_cycle()
|
|
{
|
|
self set_run_speed();
|
|
|
|
death_anims = [];
|
|
death_anims[death_anims.size] = %ch_dazed_a_death;
|
|
death_anims[death_anims.size] = %ch_dazed_b_death;
|
|
death_anims[death_anims.size] = %ch_dazed_c_death;
|
|
death_anims[death_anims.size] = %ch_dazed_d_death;
|
|
|
|
self.deathanim = random(death_anims);
|
|
|
|
switch(self.zombie_move_speed)
|
|
{
|
|
case "walk":
|
|
var = randomintrange(1, 8);
|
|
self set_run_anim( "walk" + var );
|
|
self.run_combatanim = level.scr_anim["zombie"]["walk" + var];
|
|
break;
|
|
case "run":
|
|
var = randomintrange(1, 6);
|
|
self set_run_anim( "run" + var );
|
|
self.run_combatanim = level.scr_anim["zombie"]["run" + var];
|
|
break;
|
|
case "sprint":
|
|
var = randomintrange(1, 4);
|
|
self set_run_anim( "sprint" + var );
|
|
self.run_combatanim = level.scr_anim["zombie"]["sprint" + var];
|
|
break;
|
|
}
|
|
}
|
|
|
|
set_run_speed()
|
|
{
|
|
rand = randomintrange( level.zombie_move_speed, level.zombie_move_speed + 35 );
|
|
|
|
// 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 )];
|
|
|
|
//CHRIS_P - test dudes rising from ground
|
|
if (GetDVarInt("zombie_rise_test") || (isDefined(self.script_string) && self.script_string == "riser" && randomint(100) > 25))
|
|
{
|
|
self.do_rise = 1;
|
|
//self notify("do_rise");
|
|
self waittill("risen");
|
|
}
|
|
else
|
|
{
|
|
self notify("no_rise");
|
|
}
|
|
|
|
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 );
|
|
|
|
if ( self.type != "dog" )
|
|
{
|
|
// Guy should get to goal and tear into building until all barrier chunks are gone
|
|
self tear_into_building();
|
|
|
|
//REMOVED THIS, WAS CAUSING ISSUES
|
|
if(isDefined(self.first_node.clip))
|
|
{
|
|
//if(!isDefined(self.first_node.clip.disabled) || !self.first_node.clip.disabled)
|
|
//{
|
|
// self.first_node.clip disable_trigger();
|
|
self.first_node.clip connectpaths();
|
|
//}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
else
|
|
{
|
|
// Dogs should get to goal and tear into building until all barrier chunks are gone
|
|
self crash_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_dog();
|
|
self thread find_flesh_dog();
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
}
|
|
// CHRISP - must add an additional check, since the 'self.entrance_nodes' array is not dynamically updated to accomodate for entrance points that can be turned on and off
|
|
// only do this if it's the asylum map
|
|
if(level.script == "nazi_zombie_asylum")
|
|
{
|
|
wait(2);
|
|
// Get more nodes and try again
|
|
nodes = get_array_of_closest( self.origin, level.exterior_goals, undefined, 20 );
|
|
self.entrance_nodes = nodes;
|
|
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(20);
|
|
//iprintln( "^1Zombie @ " + self.origin + " did not find a good entrance point... Please fix pathing or Entity setup" );
|
|
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()
|
|
{
|
|
//chrisp - added this
|
|
//checkpass = false;
|
|
|
|
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" );
|
|
}
|
|
|
|
// 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" );
|
|
|
|
// chrisp - do one final check to make sure that the boards are still torn down
|
|
// this *mostly* prevents the zombies from coming through the windows as you are boarding them up.
|
|
if( all_chunks_destroyed( self.first_node.barrier_chunks ) )
|
|
{
|
|
self zombie_history( "tear_into_building -> all chunks destroyed" );
|
|
return;
|
|
}
|
|
|
|
// 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 );
|
|
|
|
//chris - adding new window attack & gesture animations ;)
|
|
if(level.script != "nazi_zombie_prototype")
|
|
{
|
|
attack = self should_attack_player_thru_boards();
|
|
if(isDefined(attack) && !attack)
|
|
{
|
|
self do_a_taunt();
|
|
}
|
|
}
|
|
//chrisp - fix the extra tear anim bug
|
|
if( all_chunks_destroyed( self.first_node.barrier_chunks ) )
|
|
{
|
|
for( i = 0; i < self.first_node.attack_spots_taken.size; i++ )
|
|
{
|
|
self.first_node.attack_spots_taken[i] = false;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
self reset_attack_spot();
|
|
}
|
|
}
|
|
|
|
/*------------------------------------
|
|
checks to see if the zombie should
|
|
do a taunt when tearing thru the boards
|
|
------------------------------------*/
|
|
do_a_taunt()
|
|
{
|
|
if(getdvar("zombie_taunt_freq") == "")
|
|
{
|
|
setdvar("zombie_taunt_freq","5");
|
|
}
|
|
freq = getdvarint("zombie_taunt_freq");
|
|
|
|
if( freq >= randomint(100) )
|
|
{
|
|
anime = random(level._zombie_board_taunt);
|
|
self animscripted("zombie_taunt",self.origin,self.angles,anime);
|
|
wait(getanimlength(anime));
|
|
}
|
|
}
|
|
|
|
/*------------------------------------
|
|
checks to see if the players are near
|
|
the entrance and tries to attack them
|
|
thru the boards. 50% chance
|
|
------------------------------------*/
|
|
should_attack_player_thru_boards()
|
|
{
|
|
|
|
//no board attacks if they are crawlers
|
|
if( !self.has_legs)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(getdvar("zombie_reachin_freq") == "")
|
|
{
|
|
setdvar("zombie_reachin_freq","50");
|
|
}
|
|
freq = getdvarint("zombie_reachin_freq");
|
|
|
|
players = get_players();
|
|
attack = false;
|
|
|
|
for(i=0;i<players.size;i++)
|
|
{
|
|
if(distance2d(self.origin,players[i].origin) <= 72)
|
|
{
|
|
attack = true;
|
|
}
|
|
}
|
|
if(attack && freq >= randomint(100) )
|
|
{
|
|
//iprintln("checking attack");
|
|
|
|
//check to see if the guy is left, right, or center
|
|
|
|
if(self.attacking_spot_index == 0) //he's in the center
|
|
{
|
|
|
|
if(randomint(100) > 50)
|
|
{
|
|
self animscripted("window_melee",self.origin,self.angles,%ai_zombie_window_attack_arm_l_out);
|
|
}
|
|
else
|
|
{
|
|
self animscripted("window_melee",self.origin,self.angles,%ai_zombie_window_attack_arm_r_out);
|
|
}
|
|
self window_notetracks( "window_melee" );
|
|
|
|
}
|
|
else if(self.attacking_spot_index == 2) //<-- he's to the left
|
|
{
|
|
self animscripted("window_melee",self.origin,self.angles,%ai_zombie_window_attack_arm_r_out);
|
|
self window_notetracks( "window_melee" );
|
|
}
|
|
else if(self.attacking_spot_index == 1) //<-- he's to the right
|
|
{
|
|
self animscripted("window_melee",self.origin,self.angles,%ai_zombie_window_attack_arm_l_out);
|
|
self window_notetracks( "window_melee" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
window_notetracks(msg)
|
|
{
|
|
while(1)
|
|
{
|
|
self waittill( msg, notetrack );
|
|
|
|
if( notetrack == "end" )
|
|
{
|
|
return;
|
|
}
|
|
if( notetrack == "fire" )
|
|
{
|
|
if(self.ignoreall)
|
|
{
|
|
self.ignoreall = false;
|
|
}
|
|
self melee();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
crash_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( "goal" );
|
|
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 -> crash" );
|
|
|
|
//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 );
|
|
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\_zombiemode_blockers::remove_chunk( chunk, self.first_node, true );
|
|
|
|
if( all_chunks_destroyed( self.first_node.barrier_chunks ) )
|
|
{
|
|
EarthQuake( randomfloatrange( 0.5, 0.8 ), 0.5, chunk.origin, 300 );
|
|
|
|
if( IsDefined( self.first_node.clip ) )
|
|
{
|
|
self.first_node.clip ConnectPaths();
|
|
wait( 0.05 );
|
|
self.first_node.clip disable_trigger();
|
|
}
|
|
else
|
|
{
|
|
for( i = 0; i < self.first_node.barrier_chunks.size; i++ )
|
|
{
|
|
self.first_node.barrier_chunks[i] ConnectPaths();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EarthQuake( RandomFloatRange( 0.1, 0.15 ), 0.2, chunk.origin, 200 );
|
|
}
|
|
|
|
}
|
|
|
|
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\_zombiemode_blockers::remove_chunk( chunk, node,true );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 ( is_german_build() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
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( 5 );
|
|
|
|
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_v1;
|
|
self set_run_anim( "death3" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["crawl3"];
|
|
self.crouchRunAnim = level.scr_anim["zombie"]["crawl3"];
|
|
self.crouchrun_combatanim = level.scr_anim["zombie"]["crawl3"];
|
|
}
|
|
else if( which_anim == 3 )
|
|
{
|
|
self.deathanim = %ai_zombie_crawl_death_v2;
|
|
self set_run_anim( "death4" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["crawl4"];
|
|
self.crouchRunAnim = level.scr_anim["zombie"]["crawl4"];
|
|
self.crouchrun_combatanim = level.scr_anim["zombie"]["crawl4"];
|
|
}
|
|
else if( which_anim == 4 )
|
|
{
|
|
self.deathanim = %ai_zombie_crawl_death_v1;
|
|
self set_run_anim( "death3" );
|
|
self.run_combatanim = level.scr_anim["zombie"]["crawl5"];
|
|
self.crouchRunAnim = level.scr_anim["zombie"]["crawl5"];
|
|
self.crouchrun_combatanim = level.scr_anim["zombie"]["crawl5"];
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
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 ( is_german_build() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
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,zombie )
|
|
{
|
|
|
|
//TUEY Play VO if you get a Head Shot
|
|
|
|
//add this check ( 3/24/09 - Chrisp)
|
|
if(level.script != "nazi_zombie_prototype")
|
|
{
|
|
level thread play_death_vo(hit_location, player,mod,zombie);
|
|
}
|
|
//ChrisP - no points or powerups for killing zombies
|
|
if(IsDefined(zombie.marked_for_death))
|
|
{
|
|
return;
|
|
}
|
|
|
|
level thread maps\_zombiemode_powerups::powerup_drop( origin );
|
|
|
|
if( !IsDefined( player ) || !IsPlayer( player ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
player maps\_zombiemode_score::player_add_points( "death", mod, hit_location );
|
|
}
|
|
|
|
play_death_vo(hit_location, player,mod,zombie)
|
|
{
|
|
// CHRISP - adding some modifiers here so that it doens't play 100% of the time
|
|
// and takes into account the damage type.
|
|
// default is 10% chance of saying something
|
|
if( getdvar("zombie_death_vo_freq") == "" )
|
|
{
|
|
setdvar("zombie_death_vo_freq","100"); //TUEY moved to 50---We can take this out\tweak this later.
|
|
}
|
|
|
|
chance = getdvarint("zombie_death_vo_freq");
|
|
|
|
sound = undefined;
|
|
//just return and don't play a sound if the chance is not there
|
|
if(chance < randomint(100) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//TUEY - this funciton allows you to play a voice over when you kill a zombie and its last hit spot was something specific (like Headshot).
|
|
//players = getplayers();
|
|
index = maps\_zombiemode_weapons::get_player_index(player);
|
|
|
|
if(!isdefined (level.player_is_speaking))
|
|
{
|
|
level.player_is_speaking = 0;
|
|
}
|
|
if(!isdefined(level.zombie_vars["zombie_insta_kill"] ))
|
|
{
|
|
level.zombie_vars["zombie_insta_kill"] = 0;
|
|
}
|
|
if(hit_location == "head" && level.zombie_vars["zombie_insta_kill"] != 1 )
|
|
{
|
|
//no VO for non bullet headshot kills
|
|
if( mod != "MOD_PISTOL_BULLET" && mod != "MOD_RIFLE_BULLET" )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//chrisp - far headshot sounds
|
|
if(distance(player.origin,zombie.origin) > 400)
|
|
{
|
|
sound = "plr_" + index + "_vox_kill_headdist" + "_" + randomintrange(0, 11);
|
|
}
|
|
//remove headshot sounds for instakill
|
|
if (level.zombie_vars["zombie_insta_kill"] != 0)
|
|
{
|
|
sound = undefined;
|
|
}
|
|
|
|
}
|
|
//check for close range kills, and play a special sound, unless instakill is on
|
|
if(distance(player.origin,zombie.origin) < 64 && level.zombie_vars["zombie_insta_kill"] == 0)
|
|
{
|
|
sound = "plr_" + index + "_vox_close" + "_" + randomintrange(0, 7);
|
|
}
|
|
|
|
|
|
//special case for close range melee attacks while insta-kill is on
|
|
if (level.zombie_vars["zombie_insta_kill"] != 0)
|
|
{
|
|
if( mod == "MOD_MELEE" || mod == "MOD_BAYONET" || mod == "MOD_UNKNOWN" && distance(player.origin,zombie.origin) < 64)
|
|
{
|
|
sound = "plr_" + index + "_vox_melee_insta" + "_" + randomintrange(0, 5);
|
|
}
|
|
}
|
|
|
|
//This keeps multiple voice overs from playing on the same player (both killstreaks and headshots).
|
|
if (level.player_is_speaking != 1 && isDefined(sound) && level.zombie_vars["zombie_insta_kill"] != 0)
|
|
{
|
|
level.player_is_speaking = 1;
|
|
player playsound(sound, "sound_done");
|
|
player waittill("sound_done");
|
|
//This ensures that there is at least 2 seconds waittime before playing another VO.
|
|
wait(2);
|
|
level.player_is_speaking = 0;
|
|
}
|
|
//This allows us to play VO's faster if the player is in Instakill and killing at a short distance.
|
|
else if (level.player_is_speaking != 1 && isDefined(sound) && level.zombie_vars["zombie_insta_kill"] == 0)
|
|
{
|
|
level.player_is_speaking = 1;
|
|
player playsound(sound, "sound_done");
|
|
player waittill("sound_done");
|
|
//This ensures that there is at least 3 seconds waittime before playing another VO.
|
|
wait(0.5);
|
|
level.player_is_speaking = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 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
|
|
|
|
//ChrisP - 12/8/08 - added additional 'self' argument
|
|
level zombie_death_points( self.origin, self.damagemod, self.damagelocation, self.attacker,self );
|
|
|
|
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.15, 0.25 );
|
|
}
|
|
else if( level.round_number < 11 )
|
|
{
|
|
dmg = level.zombie_health * RandomFloatRange( 0.1, 0.2 );
|
|
}
|
|
else
|
|
{
|
|
dmg = level.zombie_health * RandomFloatRange( 0.1, 0.15 );
|
|
}
|
|
|
|
if ( Isdefined( player ) && Isalive( player ) )
|
|
{
|
|
self DoDamage( dmg, self.origin, player );
|
|
}
|
|
else
|
|
{
|
|
self DoDamage( dmg, self.origin, level );
|
|
}
|
|
|
|
wait( randomfloatrange( 1.0, 3.0 ) );
|
|
}
|
|
}
|
|
|
|
zombie_damage( mod, hit_location, hit_origin, player, is_dog )
|
|
{
|
|
|
|
//ChrisP - 12/8 - no points for killing gassed zombies!
|
|
if(isDefined(self.marked_for_death))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !IsDefined( player ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( self zombie_flame_damage( mod, player ) )
|
|
{
|
|
if( self zombie_give_flame_damage_points() )
|
|
{
|
|
player maps\_zombiemode_score::player_add_points( "damage", mod, hit_location,is_dog );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player maps\_zombiemode_score::player_add_points( "damage", mod, hit_location,is_dog );
|
|
}
|
|
|
|
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\_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\_zombiemode_score::player_add_points( "damage_ads", mod, hit_location );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player maps\_zombiemode_score::player_add_points( "damage_ads", mod, hit_location );
|
|
}
|
|
|
|
self thread maps\_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" )
|
|
{
|
|
self.moveplaybackrate = 0.8;
|
|
|
|
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();
|
|
playsoundatposition ("death_vocals", zombie.origin);
|
|
// this is controlling killstreak voice over in the asylum.gsc
|
|
if(isdefined (zombie.attacker) && isplayer(zombie.attacker) && level.script != "nazi_zombie_prototype")
|
|
{
|
|
if(!isdefined ( zombie.attacker.killcounter))
|
|
{
|
|
zombie.attacker.killcounter = 1;
|
|
}
|
|
else
|
|
{
|
|
zombie.attacker.killcounter ++;
|
|
}
|
|
|
|
zombie.attacker notify("zom_kill");
|
|
}
|
|
|
|
}
|
|
|
|
// 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;
|
|
|
|
//try to prevent always turning towards the enemy
|
|
self.maxsightdistsqrd = 128 * 128;
|
|
|
|
// turn off transition anims
|
|
self.disableArrivals = true;
|
|
self.disableExits = true;
|
|
}
|
|
|
|
// this is where zombies go into attack mode, and need different attributes set up
|
|
zombie_setup_attack_properties_dog()
|
|
{
|
|
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 )
|
|
{
|
|
|
|
// try to split the zombies up when the bunch up
|
|
// see if a bunch zombies are already near my current target; if there's a bunch
|
|
// and I'm still far enough away, ignore my current target and go after another one
|
|
// near_zombies = getaiarray("axis");
|
|
// same_enemy_count = 0;
|
|
// for (i = 0; i < near_zombies.size; i++)
|
|
// {
|
|
// if ( isdefined( near_zombies[i] ) && isalive( near_zombies[i] ) )
|
|
// {
|
|
// if ( isdefined( near_zombies[i].favoriteenemy ) && isdefined( self.favoriteenemy )
|
|
// && near_zombies[i].favoriteenemy == self.favoriteenemy )
|
|
// {
|
|
// if ( distancesquared( near_zombies[i].origin, self.favoriteenemy.origin ) < 225 * 225
|
|
// && distancesquared( near_zombies[i].origin, self.origin ) > 525 * 525)
|
|
// {
|
|
// same_enemy_count++;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// if (same_enemy_count > 12)
|
|
// {
|
|
// self.ignore_player = self.favoriteenemy;
|
|
// }
|
|
|
|
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" );
|
|
}
|
|
}
|
|
|
|
find_flesh_dog()
|
|
{
|
|
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 )
|
|
{
|
|
|
|
// try to split the zombies up when the bunch up
|
|
// see if a bunch zombies are already near my current target; if there's a bunch
|
|
// and I'm still far enough away, ignore my current target and go after another one
|
|
// near_zombies = getaiarray("axis");
|
|
// same_enemy_count = 0;
|
|
// for (i = 0; i < near_zombies.size; i++)
|
|
// {
|
|
// if ( isdefined( near_zombies[i] ) && isalive( near_zombies[i] ) )
|
|
// {
|
|
// if ( isdefined( near_zombies[i].favoriteenemy ) && isdefined( self.favoriteenemy )
|
|
// && near_zombies[i].favoriteenemy == self.favoriteenemy )
|
|
// {
|
|
// if ( distancesquared( near_zombies[i].origin, self.favoriteenemy.origin ) < 225 * 225
|
|
// && distancesquared( near_zombies[i].origin, self.origin ) > 525 * 525)
|
|
// {
|
|
// same_enemy_count++;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// if (same_enemy_count > 12)
|
|
// {
|
|
// self.ignore_player = self.favoriteenemy;
|
|
// }
|
|
|
|
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" );
|
|
|
|
assert( IsDefined( self.favoriteenemy ) );
|
|
self.favoriteenemy endon( "disconnect" );
|
|
|
|
self thread zombie_follow_enemy();
|
|
self waittill( "bad_path" );
|
|
|
|
debug_print( "Zombie couldn't path to player at origin: " + self.favoriteenemy.origin + " Falling back to breadcrumb system" );
|
|
crumb_list = self.favoriteenemy.zombie_breadcrumbs;
|
|
bad_crumbs = [];
|
|
|
|
while( 1 )
|
|
{
|
|
if( !is_player_valid( self.favoriteenemy ) )
|
|
{
|
|
self.zombie_path_timer = 0;
|
|
return;
|
|
}
|
|
|
|
goal = zombie_pathing_get_breadcrumb( self.favoriteenemy.origin, crumb_list, bad_crumbs, ( RandomInt( 100 ) < 20 ) );
|
|
|
|
if ( !IsDefined( goal ) )
|
|
{
|
|
debug_print( "Zombie exhausted breadcrumb search" );
|
|
goal = self.favoriteenemy.spectator_respawn.origin;
|
|
}
|
|
|
|
debug_print( "Setting current breadcrumb to " + goal );
|
|
|
|
self.zombie_path_timer += 1000;
|
|
self SetGoalPos( goal );
|
|
self waittill( "bad_path" );
|
|
|
|
debug_print( "Zombie couldn't path to breadcrumb at " + goal + " Finding next breadcrumb" );
|
|
for( i = 0; i < crumb_list.size; i++ )
|
|
{
|
|
if( goal == crumb_list[i] )
|
|
{
|
|
bad_crumbs[bad_crumbs.size] = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
zombie_pathing_get_breadcrumb( origin, breadcrumbs, bad_crumbs, pick_random )
|
|
{
|
|
assert( IsDefined( origin ) );
|
|
assert( IsDefined( breadcrumbs ) );
|
|
assert( IsArray( breadcrumbs ) );
|
|
|
|
/#
|
|
if ( pick_random )
|
|
{
|
|
debug_print( "Finding random breadcrumb" );
|
|
}
|
|
#/
|
|
|
|
for( i = 0; i < breadcrumbs.size; i++ )
|
|
{
|
|
if ( pick_random )
|
|
{
|
|
crumb_index = RandomInt( breadcrumbs.size );
|
|
}
|
|
else
|
|
{
|
|
crumb_index = i;
|
|
}
|
|
|
|
if( crumb_is_bad( crumb_index, bad_crumbs ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return breadcrumbs[crumb_index];
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
crumb_is_bad( crumb, bad_crumbs )
|
|
{
|
|
for ( i = 0; i < bad_crumbs.size; i++ )
|
|
{
|
|
if ( bad_crumbs[i] == crumb )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
jitter_enemies_bad_breadcrumbs( start_crumb )
|
|
{
|
|
trace_distance = 35;
|
|
jitter_distance = 2;
|
|
|
|
index = start_crumb;
|
|
|
|
while (isdefined(self.favoriteenemy.zombie_breadcrumbs[ index + 1 ]))
|
|
{
|
|
current_crumb = self.favoriteenemy.zombie_breadcrumbs[ index ];
|
|
next_crumb = self.favoriteenemy.zombie_breadcrumbs[ index + 1 ];
|
|
|
|
angles = vectortoangles(current_crumb - next_crumb);
|
|
|
|
right = anglestoright(angles);
|
|
left = anglestoright(angles + (0,180,0));
|
|
|
|
dist_pos = current_crumb + vectorScale( right, trace_distance );
|
|
|
|
trace = bulletTrace( current_crumb, dist_pos, true, undefined );
|
|
vector = trace["position"];
|
|
|
|
if (distance(vector, current_crumb) < 17 )
|
|
{
|
|
self.favoriteenemy.zombie_breadcrumbs[ index ] = current_crumb + vectorScale( left, jitter_distance );
|
|
continue;
|
|
}
|
|
|
|
|
|
// try the other side
|
|
dist_pos = current_crumb + vectorScale( left, trace_distance );
|
|
|
|
trace = bulletTrace( current_crumb, dist_pos, true, undefined );
|
|
vector = trace["position"];
|
|
|
|
if (distance(vector, current_crumb) < 17 )
|
|
{
|
|
self.favoriteenemy.zombie_breadcrumbs[ index ] = current_crumb + vectorScale( right, jitter_distance );
|
|
continue;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
|
|
// When a Zombie spawns, set his eyes to glowing.
|
|
zombie_dog_eye_glow()
|
|
{
|
|
if( IsDefined( level.zombie_eye_glow ) && !level.zombie_eye_glow )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !IsDefined( self ) )
|
|
{
|
|
return;
|
|
}
|
|
wait(.5);
|
|
|
|
if(isDefined(level._effect["dog_eye_glow"] ))
|
|
{
|
|
self.fx_eye_glow = Spawn( "script_model", self GetTagOrigin( "TAG_EYE" ) );
|
|
self.fx_eye_glow.angles = self GetTagAngles( "TAG_EYE" );
|
|
self.fx_eye_glow SetModel( "tag_origin" );
|
|
self.fx_eye_glow LinkTo( self, "TAG_EYE" );
|
|
PlayFxOnTag( level._effect["eye_glow"], self.fx_eye_glow, "tag_origin" );
|
|
}
|
|
}
|
|
|
|
//
|
|
// DEBUG
|
|
//
|
|
|
|
zombie_history( msg )
|
|
{
|
|
/#
|
|
if( !IsDefined( self.zombie_history ) )
|
|
{
|
|
self.zombie_history = [];
|
|
}
|
|
|
|
self.zombie_history[self.zombie_history.size] = msg;
|
|
#/
|
|
}
|
|
|
|
/*
|
|
Zombie Rise Stuff
|
|
*/
|
|
|
|
zombie_rise()
|
|
{
|
|
self endon("death");
|
|
self endon("no_rise");
|
|
|
|
while(!IsDefined(self.do_rise))
|
|
{
|
|
wait_network_frame();
|
|
}
|
|
|
|
self do_zombie_rise();
|
|
}
|
|
|
|
/*
|
|
zombie_rise:
|
|
Zombies rise from the ground
|
|
*/
|
|
do_zombie_rise()
|
|
{
|
|
self endon("death");
|
|
|
|
self.zombie_rise_version = (RandomInt(99999) % 2) + 1; // equally choose between version 1 and verson 2 of the animations
|
|
if (self.zombie_move_speed != "walk")
|
|
{
|
|
// only do version 1 anims for "run" and "sprint"
|
|
self.zombie_rise_version = 1;
|
|
}
|
|
|
|
self.in_the_ground = true;
|
|
|
|
//self.zombie_rise_version = 1; // TESTING: override version
|
|
|
|
self.anchor = spawn("script_origin", self.origin);
|
|
self.anchor.angles = self.angles;
|
|
self linkto(self.anchor);
|
|
|
|
spots = GetStructArray("zombie_rise", "targetname");
|
|
spot = random(spots);
|
|
|
|
/#
|
|
if (GetDVarInt("zombie_rise_test"))
|
|
{
|
|
spot = SpawnStruct(); // I know this never gets deleted, but it's just for testing
|
|
spot.origin = (472, 240, 56); // TEST LOCATION
|
|
spot.angles = (0, 0, 0);
|
|
}
|
|
#/
|
|
|
|
anim_org = spot.origin;
|
|
anim_ang = spot.angles;
|
|
|
|
//TODO: bbarnes: do a bullet trace to the ground so the structs don't have to be exactly on the ground.
|
|
|
|
if (self.zombie_rise_version == 2)
|
|
{
|
|
anim_org = anim_org + (0, 0, -14); // version 2 animation starts 14 units below the ground
|
|
}
|
|
else
|
|
{
|
|
anim_org = anim_org + (0, 0, -45); // start the animation 45 units below the ground
|
|
}
|
|
|
|
//self Teleport(anim_org, anim_ang); // we should get this working for MP
|
|
|
|
self Hide();
|
|
self.anchor moveto(anim_org, .05);
|
|
self.anchor waittill("movedone");
|
|
|
|
// face goal
|
|
target_org = maps\_zombiemode_spawner_asylum::get_desired_origin();
|
|
if (IsDefined(target_org))
|
|
{
|
|
anim_ang = VectorToAngles(target_org - self.origin);
|
|
self.anchor RotateTo((0, anim_ang[1], 0), .05);
|
|
self.anchor waittill("rotatedone");
|
|
}
|
|
|
|
self unlink();
|
|
self.anchor delete();
|
|
|
|
self thread hide_pop(); // hack to hide the pop when the zombie gets to the start position before the anim starts
|
|
|
|
level thread zombie_rise_death(self, spot);
|
|
spot thread zombie_rise_fx(self);
|
|
|
|
//self animMode("nogravity");
|
|
//self setFlaggedAnimKnoballRestart("rise", level.scr_anim["zombie"]["rise_walk"], %body, 1, .1, 1); // no "noclip" mode for these anim functions
|
|
|
|
self AnimScripted("rise", self.origin, self.angles, self get_rise_anim());
|
|
self animscripts\shared::DoNoteTracks("rise", ::handle_rise_notetracks, undefined, spot);
|
|
|
|
self notify("rise_anim_finished");
|
|
spot notify("stop_zombie_rise_fx");
|
|
self.in_the_ground = false;
|
|
self notify("risen");
|
|
}
|
|
|
|
hide_pop()
|
|
{
|
|
wait .5;
|
|
self Show();
|
|
}
|
|
|
|
handle_rise_notetracks(note, spot)
|
|
{
|
|
// the anim notetracks control which death anim to play
|
|
// default to "deathin" (still in the ground)
|
|
|
|
if (note == "deathout" || note == "deathhigh")
|
|
{
|
|
self.zombie_rise_death_out = true;
|
|
self notify("zombie_rise_death_out");
|
|
|
|
wait 2;
|
|
spot notify("stop_zombie_rise_fx");
|
|
}
|
|
}
|
|
|
|
/*
|
|
zombie_rise_death:
|
|
Track when the zombie should die, set the death anim, and stop the animscripted so he can die
|
|
*/
|
|
zombie_rise_death(zombie, spot)
|
|
{
|
|
//self.nodeathragdoll = true;
|
|
zombie.zombie_rise_death_out = false;
|
|
|
|
zombie endon("rise_anim_finished");
|
|
|
|
while (zombie.health > 1) // health will only go down to 1 when playing animation with AnimScripted()
|
|
{
|
|
zombie waittill("damage", amount);
|
|
}
|
|
|
|
spot notify("stop_zombie_rise_fx");
|
|
|
|
zombie.deathanim = zombie get_rise_death_anim();
|
|
zombie StopAnimScripted(); // stop the anim so the zombie can die. death anim is handled by the anim scripts.
|
|
}
|
|
|
|
/*
|
|
zombie_rise_fx: self is the script struct at the rise location
|
|
Play the fx as the zombie crawls out of the ground and thread another function to handle the dust falling
|
|
off when the zombie is out of the ground.
|
|
*/
|
|
zombie_rise_fx(zombie)
|
|
{
|
|
self thread zombie_rise_dust_fx(zombie);
|
|
self thread zombie_rise_burst_fx();
|
|
playsoundatposition ("zombie_spawn", self.origin);
|
|
zombie endon("death");
|
|
self endon("stop_zombie_rise_fx");
|
|
wait 1;
|
|
if (zombie.zombie_move_speed != "sprint")
|
|
{
|
|
// wait longer before starting billowing fx if it's not a really fast animation
|
|
wait 1;
|
|
}
|
|
}
|
|
|
|
zombie_rise_burst_fx()
|
|
{
|
|
self endon("stop_zombie_rise_fx");
|
|
self endon("rise_anim_finished");
|
|
|
|
|
|
playfx(level._effect["rise_burst"],self.origin + ( 0,0,randomintrange(5,10) ) );
|
|
wait(.25);
|
|
playfx(level._effect["rise_billow"],self.origin + ( randomintrange(-10,10),randomintrange(-10,10),randomintrange(5,10) ) );
|
|
|
|
|
|
//burst_time = 10; // play dust fx for a max time
|
|
//interval = randomfloatrange(.15,.45); // wait this time in between playing the effect
|
|
|
|
//for (t = 0; t < burst_time; t += interval)
|
|
//{
|
|
// wait interval;
|
|
//}
|
|
}
|
|
|
|
zombie_rise_dust_fx(zombie)
|
|
{
|
|
dust_tag = "J_SpineUpper";
|
|
|
|
self endon("stop_zombie_rise_dust_fx");
|
|
self thread stop_zombie_rise_dust_fx(zombie);
|
|
|
|
dust_time = 7.5; // play dust fx for a max time
|
|
dust_interval = .1; //randomfloatrange(.1,.25); // wait this time in between playing the effect
|
|
|
|
//TODO - add rising dust stuff ere
|
|
|
|
for (t = 0; t < dust_time; t += dust_interval)
|
|
{
|
|
PlayfxOnTag(level._effect["rise_dust"], zombie, dust_tag);
|
|
wait dust_interval;
|
|
}
|
|
}
|
|
|
|
stop_zombie_rise_dust_fx(zombie)
|
|
{
|
|
zombie waittill("death");
|
|
self notify("stop_zombie_rise_dust_fx");
|
|
}
|
|
|
|
/*
|
|
get_rise_anim:
|
|
Return a random rise animation based on a possible set of animations
|
|
*/
|
|
get_rise_anim()
|
|
{
|
|
///* TESTING: put this block back in
|
|
speed = self.zombie_move_speed;
|
|
return random(level._zombie_rise_anims[self.zombie_rise_version][speed]);
|
|
//*/
|
|
|
|
//return %ai_zombie_traverse_ground_v1_crawlfast;
|
|
//return %ai_zombie_traverse_ground_v2_walk;
|
|
//return %ai_zombie_traverse_ground_v2_walk_altB;
|
|
}
|
|
|
|
/*
|
|
get_rise_death_anim:
|
|
Return a random death animation based on a possible set of animations
|
|
*/
|
|
get_rise_death_anim()
|
|
{
|
|
possible_anims = [];
|
|
|
|
if (self.zombie_rise_death_out)
|
|
{
|
|
possible_anims = level._zombie_rise_death_anims[self.zombie_rise_version]["out"];
|
|
}
|
|
else
|
|
{
|
|
possible_anims = level._zombie_rise_death_anims[self.zombie_rise_version]["in"];
|
|
}
|
|
|
|
return random(possible_anims);
|
|
} |