boiii-scripts/shared/ai/zombie_utility.gsc
2023-04-13 17:30:38 +02:00

3047 lines
74 KiB
Plaintext

#using scripts\shared\ai_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\flag_shared;
#using scripts\shared\fx_shared;
#using scripts\shared\math_shared;
#using scripts\shared\scene_shared;
#using scripts\shared\spawner_shared;
#using scripts\shared\util_shared;
#using scripts\shared\array_shared;
#using scripts\shared\laststand_shared;
#using scripts\shared\ai\zombie_shared;
#using scripts\shared\ai\zombie_death;
#using scripts\shared\ai\systems\gib;
#using scripts\shared\ai\systems\behavior_tree_utility;
#using scripts\codescripts\struct;
#namespace zombie_utility;
function zombieSpawnSetup()
{
self.zombie_move_speed = "walk";
if ( !isdefined( self.zombie_arms_position ) )
{
if(randomint( 2 ) == 0)
self.zombie_arms_position = "up";
else
self.zombie_arms_position = "down";
}
self.missingLegs = false;
self setAvoidanceMask( "avoid none" );
self PushActors( true );
clientfield::set( "zombie", true );
self.ignorePathEnemyFightDist = true; // prevent sight tracing in Actor_ShouldFaceMotion
}
function get_closest_valid_player( origin, ignore_player, ignore_laststand_players = false )
{
PixBeginEvent( "get_closest_valid_player" );
valid_player_found = false;
targets = GetPlayers();
if( isdefined( level.closest_player_targets_override ) )
{
targets = [[ level.closest_player_targets_override ]]();
}
if( IsDefined( ignore_player ) )
{
for(i = 0; i < ignore_player.size; i++ )
{
ArrayRemoveValue( targets, ignore_player[i] );
}
}
done = true;
while ( targets.size && !done )
{
done = true;
for(i = 0; i < targets.size; i++ )
{
target = targets[i];
if( !is_player_valid( target, true, ignore_laststand_players ) )
{
ArrayRemoveValue( targets, target );
done = false;
break;
}
}
}
if( targets.size == 0 )
{
pixendevent();
return undefined;
}
if( IsDefined( self.closest_player_override ) )
{
target = [[ self.closest_player_override ]]( origin, targets );
}
else if( IsDefined( level.closest_player_override ) )
{
target = [[ level.closest_player_override ]]( origin, targets );
}
if( IsDefined( target ) )
{
pixendevent();
return target;
}
sortedPotentialTargets = ArraySortClosest( targets, self.origin );
while(sortedPotentialTargets.size)
{
if( is_player_valid( sortedPotentialTargets[0], true , ignore_laststand_players) )
{
pixendevent();
return sortedPotentialTargets[0];
}
ArrayRemoveValue( sortedPotentialTargets, sortedPotentialTargets[0] );
}
pixendevent();
return undefined;
}
function is_player_valid( player, checkIgnoreMeFlag, ignore_laststand_players )
{
if( !IsDefined( player ) )
{
return false;
}
if( !IsAlive( player ) )
{
return false;
}
if( !IsPlayer( player ) )
{
return false;
}
if( IsDefined(player.is_zombie) && player.is_zombie == true )
{
return false;
}
if( player.sessionstate == "spectator" )
{
return false;
}
if( player.sessionstate == "intermission" )
{
return false;
}
if( ( isdefined( player.intermission ) && player.intermission ) )
{
return false;
}
if(!( isdefined( ignore_laststand_players ) && ignore_laststand_players ))
{
if( player laststand::player_is_in_laststand() )
{
return false;
}
}
if ( player IsNoTarget() )
{
return false;
}
//We only want to check this from the zombie attack script
if( ( isdefined( checkIgnoreMeFlag ) && checkIgnoreMeFlag ) && player.ignoreme )
{
return false;
}
//for additional level specific checks
if( IsDefined( level.is_player_valid_override ) )
{
return [[ level.is_player_valid_override ]]( player );
}
return true;
}
function append_missing_legs_suffix( animstate )
{
if ( self.missingLegs && self HasAnimStateFromASD( animstate + "_crawl" ) )
{
return animstate + "_crawl";
}
return animstate;
}
// Every script calls initAnimTree to ensure a clean, fresh, known animtree state.
// ClearAnim should never be called directly, and this should never occur other than
// at the start of an animscript
// This function now also does any initialization for the scripts that needs to happen
// at the beginning of every main script.
function initAnimTree(animscript)
{
if ( animscript != "pain" && animscript != "death" )
{
self.a.special = "none";
}
assert( IsDefined( animscript ), "Animscript not specified in initAnimTree" );
self.a.script = animscript;
}
// UpdateAnimPose does housekeeping at the start of every script's main function.
function UpdateAnimPose()
{
assert( self.a.movement=="stop" || self.a.movement=="walk" || self.a.movement=="run", "UpdateAnimPose "+self.a.pose+" "+self.a.movement );
self.desired_anim_pose = undefined;
}
function initialize( animscript )
{
if ( IsDefined( self.longDeathStarting ) )
{
if ( animscript != "pain" && animscript != "death" )
{
// we probably just came out of an animcustom.
// just die, it's not safe to do anything else
self DoDamage( self.health + 100, self.origin );
}
if ( animscript != "pain" )
{
self.longDeathStarting = undefined;
self notify( "kill_long_death" );
}
}
if ( IsDefined( self.a.mayOnlyDie ) && animscript != "death" )
{
// we probably just came out of an animcustom.
// just die, it's not safe to do anything else
self DoDamage( self.health + 100, self.origin );
}
// scripts can define this to allow cleanup before moving on
if ( IsDefined( self.a.postScriptFunc ) )
{
scriptFunc = self.a.postScriptFunc;
self.a.postScriptFunc = undefined;
[[scriptFunc]]( animscript );
}
if ( animscript != "death" )
{
self.a.nodeath = false;
}
self.isHoldingGrenade = undefined;
self.coverNode = undefined;
self.changingCoverPos = false;
self.a.scriptStartTime = GetTime();
self.a.atConcealmentNode = false;
if ( IsDefined( self.node ) && (self.node.type == "Conceal Crouch" || self.node.type == "Conceal Stand") )
{
self.a.atConcealmentNode = true;
}
initAnimTree( animscript );
UpdateAnimPose();
}
function GetNodeYawToOrigin(pos)
{
if (IsDefined (self.node))
{
yaw = self.node.angles[1] - GetYaw(pos);
}
else
{
yaw = self.angles[1] - GetYaw(pos);
}
yaw = AngleClamp180( yaw );
return yaw;
}
function GetNodeYawToEnemy()
{
pos = undefined;
if ( isValidEnemy( self.enemy ) )
{
pos = self.enemy.origin;
}
else
{
if (IsDefined (self.node))
{
forward = AnglesToForward(self.node.angles);
}
else
{
forward = AnglesToForward(self.angles);
}
forward = VectorScale (forward, 150);
pos = self.origin + forward;
}
if (IsDefined (self.node))
{
yaw = self.node.angles[1] - GetYaw(pos);
}
else
{
yaw = self.angles[1] - GetYaw(pos);
}
yaw = AngleClamp180( yaw );
return yaw;
}
function GetCoverNodeYawToEnemy()
{
pos = undefined;
if ( isValidEnemy( self.enemy ) )
{
pos = self.enemy.origin;
}
else
{
forward = AnglesToForward(self.coverNode.angles + self.animarray["angle_step_out"][self.a.cornerMode]);
forward = VectorScale (forward, 150);
pos = self.origin + forward;
}
yaw = self.CoverNode.angles[1] + self.animarray["angle_step_out"][self.a.cornerMode] - GetYaw(pos);
yaw = AngleClamp180( yaw );
return yaw;
}
function GetYawToSpot(spot)
{
pos = spot;
yaw = self.angles[1] - GetYaw(pos);
yaw = AngleClamp180( yaw );
return yaw;
}
// warning! returns (my yaw - yaw to enemy) instead of (yaw to enemy - my yaw)
function GetYawToEnemy()
{
pos = undefined;
if ( isValidEnemy( self.enemy ) )
{
pos = self.enemy.origin;
}
else
{
forward = AnglesToForward(self.angles);
forward = VectorScale (forward, 150);
pos = self.origin + forward;
}
yaw = self.angles[1] - GetYaw(pos);
yaw = AngleClamp180( yaw );
return yaw;
}
function GetYaw(org)
{
angles = VectorToAngles(org-self.origin);
return angles[1];
}
function GetYaw2d(org)
{
angles = VectorToAngles((org[0], org[1], 0)-(self.origin[0], self.origin[1], 0));
return angles[1];
}
// 0 if I'm facing my enemy, 90 if I'm side on, 180 if I'm facing away.
function AbsYawToEnemy()
{
assert( isValidEnemy( self.enemy ) );
yaw = self.angles[1] - GetYaw(self.enemy.origin);
yaw = AngleClamp180( yaw );
if (yaw < 0)
{
yaw = -1 * yaw;
}
return yaw;
}
// 0 if I'm facing my enemy, 90 if I'm side on, 180 if I'm facing away.
function AbsYawToEnemy2d()
{
assert( isValidEnemy( self.enemy ) );
yaw = self.angles[1] - GetYaw2d(self.enemy.origin);
yaw = AngleClamp180( yaw );
if (yaw < 0)
{
yaw = -1 * yaw;
}
return yaw;
}
// 0 if I'm facing my enemy, 90 if I'm side on, 180 if I'm facing away.
function AbsYawToOrigin(org)
{
yaw = self.angles[1] - GetYaw(org);
yaw = AngleClamp180( yaw );
if (yaw < 0)
{
yaw = -1 * yaw;
}
return yaw;
}
function AbsYawToAngles(angles)
{
yaw = self.angles[1] - angles;
yaw = AngleClamp180( yaw );
if (yaw < 0)
{
yaw = -1 * yaw;
}
return yaw;
}
function GetYawFromOrigin(org, start)
{
angles = VectorToAngles(org-start);
return angles[1];
}
function GetYawToTag(tag, org)
{
yaw = self GetTagAngles( tag )[1] - GetYawFromOrigin(org, self GetTagOrigin(tag));
yaw = AngleClamp180( yaw );
return yaw;
}
function GetYawToOrigin(org)
{
yaw = self.angles[1] - GetYaw(org);
yaw = AngleClamp180( yaw );
return yaw;
}
function GetEyeYawToOrigin(org)
{
yaw = self GetTagAngles("TAG_EYE")[1] - GetYaw(org);
yaw = AngleClamp180( yaw );
return yaw;
}
function GetCoverNodeYawToOrigin(org)
{
yaw = self.coverNode.angles[1] + self.animarray["angle_step_out"][self.a.cornerMode] - GetYaw(org);
yaw = AngleClamp180( yaw );
return yaw;
}
function isStanceAllowedWrapper( stance )
{
if ( IsDefined( self.coverNode ) )
{
return self.coverNode doesNodeAllowStance( stance );
}
return self IsStanceAllowed( stance );
}
function GetClaimedNode()
{
myNode = self.node;
if ( IsDefined(myNode) && (self nearNode(myNode) || (IsDefined( self.coverNode ) && myNode == self.coverNode)) )
{
return myNode;
}
return undefined;
}
function GetNodeType()
{
myNode = GetClaimedNode();
if (IsDefined(myNode))
{
return myNode.type;
}
return "none";
}
function GetNodeDirection()
{
myNode = GetClaimedNode();
if (IsDefined(myNode))
{
//thread [[anim.println]]("GetNodeDirection found node, returned: "+myNode.angles[1]);#/
return myNode.angles[1];
}
//thread [[anim.println]]("GetNodeDirection didn't find node, returned: "+self.desiredAngle);#/
return self.desiredAngle;
}
function GetNodeForward()
{
myNode = GetClaimedNode();
if (IsDefined(myNode))
{
return AnglesToForward ( myNode.angles );
}
return AnglesToForward( self.angles );
}
function GetNodeOrigin()
{
myNode = GetClaimedNode();
if (IsDefined(myNode))
{
return myNode.origin;
}
return self.origin;
}
function safemod(a,b)
{
/*
Here are some modulus results from in-game:
10 % 3 = 1
10 % -3 = 1
-10 % 3 = -1
-10 % -3 = -1
however, we never want a negative result.
*/
result = int(a) % b;
result += b;
return result % b;
}
// Gives the result as an angle between 0 and 360
function AngleClamp( angle )
{
angleFrac = angle / 360.0;
angle = (angleFrac - floor( angleFrac )) * 360.0;
return angle;
}
// Returns an array of 4 weights (2 of which are guaranteed to be 0), which should be applied to forward,
// right, back and left animations to get the angle specified.
// front
// /----|----\
// / 180 \
// /\ | /\
// / -135 | 135 \
// | \ | / |
// left|-90----+----90-|right
// | / | \ |
// \ -45 | 45 /
// \/ | \/
// \ 0 /
// \----|----/
// back
function QuadrantAnimWeights( yaw )
{
// ALEXP 6/26/09: I don't understand why they'd want trig interpolation between angles instead of linear
//forwardWeight = cos( yaw );
//leftWeight = sin( yaw );
forwardWeight = (90 - abs(yaw)) / 90;
leftWeight = (90 - AbsAngleClamp180(abs(yaw-90))) / 90;
result["front"] = 0;
result["right"] = 0;
result["back"] = 0;
result["left"] = 0;
if ( IsDefined( self.alwaysRunForward ) )
{
assert( self.alwaysRunForward ); // always set alwaysRunForward to either true or undefined.
result["front"] = 1;
return result;
}
useLeans = GetDvarInt( "ai_useLeanRunAnimations");
if (forwardWeight > 0)
{
result["front"] = forwardWeight;
if (leftWeight > 0)
{
result["left"] = leftWeight;
}
else
{
result["right"] = -1 * leftWeight;
}
}
else if( useLeans )
{
result["back"] = -1 * forwardWeight;
if (leftWeight > 0)
{
result["left"] = leftWeight;
}
else
{
result["right"] = -1 * leftWeight;
}
}
else //cod4 back strafe
{
// if moving backwards, don't blend.
// it looks horrible because the feet cycle in the opposite direction.
// either way, feet slide, but this looks better.
backWeight = -1 * forwardWeight;
if ( leftWeight > backWeight )
{
result["left"] = 1;
}
else if ( leftWeight < forwardWeight )
{
result["right"] = 1;
}
else
{
result["back"] = 1;
}
}
return result;
}
function getQuadrant(angle)
{
angle = AngleClamp(angle);
if (angle<45 || angle>315)
{
quadrant = "front";
}
else if (angle<135)
{
quadrant = "left";
}
else if (angle<225)
{
quadrant = "back";
}
else
{
quadrant = "right";
}
return quadrant;
}
// Checks to see if the input is equal to any of up to ten other inputs.
function IsInSet(input, set)
{
for (i = set.size - 1; i >= 0; i--)
{
if (input == set[i])
{
return true;
}
}
return false;
}
function NotifyAfterTime(notifyString, killmestring, time)
{
self endon("death");
self endon(killmestring);
wait time;
self notify (notifyString);
}
function drawStringTime(msg, org, color, timer)
{
/#
maxtime = timer*20;
for (i=0;i<maxtime;i++)
{
Print3d (org, msg, color, 1, 1);
wait .05;
}
#/
}
function showLastEnemySightPos(string)
{
/#
self notify ("got known enemy2");
self endon ("got known enemy2");
self endon ("death");
if ( !isValidEnemy( self.enemy ) )
{
return;
}
if (self.enemy.team == "allies")
{
color = (0.4, 0.7, 1);
}
else
{
color = (1, 0.7, 0.4);
}
while (1)
{
{wait(.05);};
if (!IsDefined (self.lastEnemySightPos))
{
continue;
}
Print3d (self.lastEnemySightPos, string, color, 1, 2.15); // origin, text, RGB, alpha, scale
}
#/
}
function debugTimeout()
{
wait(5);
self notify ("timeout");
}
function debugPosInternal( org, string, size )
{
/#
self endon ("death");
self notify ("stop debug " + org);
self endon ("stop debug " + org);
ent = SpawnStruct();
ent thread debugTimeout();
ent endon ("timeout");
if (self.enemy.team == "allies")
{
color = (0.4, 0.7, 1);
}
else
{
color = (1, 0.7, 0.4);
}
while (1)
{
{wait(.05);};
Print3d (org, string, color, 1, size); // origin, text, RGB, alpha, scale
}
#/
}
function debugPos( org, string )
{
thread debugPosInternal( org, string, 2.15 );
}
function debugPosSize( org, string, size )
{
thread debugPosInternal( org, string, size );
}
function showDebugProc(fromPoint, toPoint, color, printTime)
{
/#
self endon ("death");
// self notify ("stop debugline " + self.export);
// self endon ("stop debugline " + self.export);
timer = printTime*20;
for (i=0;i<timer;i+=1)
{
{wait(.05);};
line (fromPoint, toPoint, color);
}
#/
}
function showDebugLine( fromPoint, toPoint, color, printTime )
{
self thread showDebugProc( fromPoint, toPoint +( 0, 0, -5 ), color, printTime );
}
function getNodeOffset(node)
{
if ( IsDefined( node.offset ) )
{
return node.offset;
}
//(right offset, forward offset, vertical offset)
// you can get an actor's current eye offset by setting scr_eyeoffset to his entnum.
// this should be redone whenever animations change significantly.
cover_left_crouch_offset = (-26, .4, 36);
cover_left_stand_offset = (-32, 7, 63);
cover_right_crouch_offset = (43.5, 11, 36);
cover_right_stand_offset = (36, 8.3, 63);
cover_crouch_offset = (3.5, -12.5, 45); // maybe we could account for the fact that in cover crouch he can stand if he needs to?
cover_stand_offset = (-3.7, -22, 63);
cornernode = false;
nodeOffset = (0,0,0);
right = AnglesToRight(node.angles);
forward = AnglesToForward(node.angles);
switch(node.type)
{
case "Cover Left":
case "Cover Left Wide":
if ( node isNodeDontStand() && !node isNodeDontCrouch() )
{
nodeOffset = calculateNodeOffset( right, forward, cover_left_crouch_offset );
}
else
{
nodeOffset = calculateNodeOffset( right, forward, cover_left_stand_offset );
}
break;
case "Cover Right":
case "Cover Right Wide":
if ( node isNodeDontStand() && !node isNodeDontCrouch() )
{
nodeOffset = calculateNodeOffset( right, forward, cover_right_crouch_offset );
}
else
{
nodeOffset = calculateNodeOffset( right, forward, cover_right_stand_offset );
}
break;
case "Cover Stand":
case "Conceal Stand":
case "Turret":
nodeOffset = calculateNodeOffset( right, forward, cover_stand_offset );
break;
case "Cover Crouch":
case "Cover Crouch Window":
case "Conceal Crouch":
nodeOffset = calculateNodeOffset( right, forward, cover_crouch_offset );
break;
}
node.offset = nodeOffset;
return node.offset;
}
function calculateNodeOffset( right, forward, baseoffset )
{
return VectorScale( right, baseoffset[0] ) + VectorScale( forward, baseoffset[1] ) + (0, 0, baseoffset[2]);
}
function checkPitchVisibility( fromPoint, toPoint, atNode )
{
// check vertical angle is within our aiming abilities
pitch = AngleClamp180( VectorToAngles( toPoint - fromPoint )[0] );
if ( abs( pitch ) > 45 )
{
if ( IsDefined( atNode ) && atNode.type != "Cover Crouch" && atNode.type != "Conceal Crouch" )
{
return false;
}
if ( pitch > 45 || pitch < anim.coverCrouchLeanPitch - 45 )
{
return false;
}
}
return true;
}
function showLines(start, end, end2)
{
/#
for (;;)
{
line(start, end, (1,0,0), 1);
{wait(.05);};
line(start, end2, (0,0,1), 1);
{wait(.05);};
}
#/
}
// Returns an animation from an array of animations with a corresponding array of weights.
function anim_array(animArray, animWeights)
{
total_anims = animArray.size;
idleanim = RandomInt(total_anims);
assert (total_anims);
assert (animArray.size == animWeights.size);
if (total_anims == 1)
{
return animArray[0];
}
weights = 0;
total_weight = 0;
for (i = 0; i < total_anims; i++)
{
total_weight += animWeights[i];
}
anim_play = RandomFloat(total_weight);
current_weight = 0;
for (i = 0; i < total_anims; i++)
{
current_weight += animWeights[i];
if (anim_play >= current_weight)
{
continue;
}
idleanim = i;
break;
}
return animArray[idleanim];
}
function notForcedCover()
{
return ((self.a.forced_cover == "none") || (self.a.forced_cover == "Show"));
}
function forcedCover(msg)
{
return IsDefined(self.a.forced_cover) && (self.a.forced_cover == msg);
}
function print3dtime(timer, org, msg, color, alpha, scale)
{
/#
newtime = timer / 0.05;
for (i=0;i<newtime;i++)
{
Print3d (org, msg, color, alpha, scale);
{wait(.05);};
}
#/
}
function print3drise (org, msg, color, alpha, scale)
{
/#
newtime = 5 / 0.05;
up = 0;
org = org;
for (i=0;i<newtime;i++)
{
up+=0.5;
Print3d (org + (0,0,up), msg, color, alpha, scale);
{wait(.05);};
}
#/
}
function crossproduct (vec1, vec2)
{
return (vec1[0]*vec2[1] - vec1[1]*vec2[0] > 0);
}
function scriptChange()
{
self.a.current_script = "none";
self notify (anim.scriptChange);
}
function delayedScriptChange()
{
{wait(.05);};
scriptChange();
}
function sawEnemyMove(timer)
{
if (!IsDefined(timer))
{
timer = 500;
}
return (GetTime() - self.personalSightTime < timer);
}
function canThrowGrenade()
{
if (!self.grenadeAmmo)
{
return false;
}
if (self.script_forceGrenade)
{
return true;
}
return (IsPlayer(self.enemy));
}
function random_weight (array)
{
idleanim = RandomInt (array.size);
if (array.size > 1)
{
anim_weight = 0;
for (i=0;i<array.size;i++)
{
anim_weight += array[i];
}
anim_play = RandomFloat (anim_weight);
anim_weight = 0;
for (i=0;i<array.size;i++)
{
anim_weight += array[i];
if (anim_play < anim_weight)
{
idleanim = i;
break;
}
}
}
return idleanim;
}
function setFootstepEffect(name, fx)
{
assert(IsDefined(name), "Need to define the footstep surface type.");
assert(IsDefined(fx), "Need to define the mud footstep effect.");
if (!IsDefined(anim.optionalStepEffects))
{
anim.optionalStepEffects = [];
}
anim.optionalStepEffects[anim.optionalStepEffects.size] = name;
level._effect["step_" + name] = fx;
anim.optionalStepEffectFunction = &zombie_shared::playFootStepEffect;
}
function persistentDebugLine(start, end)
{
/#
self endon ("death");
level notify ("newdebugline");
level endon ("newdebugline");
for (;;)
{
line (start,end, (0.3,1,0), 1);
{wait(.05);};
}
#/
}
function isNodeDontStand()
{
return (self.spawnflags & 4) == 4;
}
function isNodeDontCrouch()
{
return (self.spawnflags & 8) == 8;
}
function doesNodeAllowStance( stance )
{
if ( stance == "stand" )
{
return !self isNodeDontStand();
}
else
{
Assert( stance == "crouch" );
return !self isNodeDontCrouch();
}
}
function animArray( animname ) /* string */
{
//println( "playing anim: ", animname );
assert( IsDefined(self.a.array) );
/#
if ( !IsDefined(self.a.array[animname]) )
{
dumpAnimArray();
assert( IsDefined(self.a.array[animname]), "self.a.array[ \"" + animname + "\" ] is undefined" );
}
#/
return self.a.array[animname];
}
function animArrayAnyExist( animname )
{
assert( IsDefined( self.a.array ) );
/#
if ( !IsDefined(self.a.array[animname]) )
{
dumpAnimArray();
assert( IsDefined(self.a.array[animname]), "self.a.array[ \"" + animname + "\" ] is undefined" );
}
#/
return self.a.array[animname].size > 0;
}
function animArrayPickRandom( animname )
{
assert( IsDefined( self.a.array ) );
/#
if ( !IsDefined(self.a.array[animname]) )
{
dumpAnimArray();
assert( IsDefined(self.a.array[animname]), "self.a.array[ \"" + animname + "\" ] is undefined" );
}
#/
assert( self.a.array[animname].size > 0 );
if ( self.a.array[animname].size > 1 )
{
index = RandomInt( self.a.array[animname].size );
}
else
{
index = 0;
}
return self.a.array[animname][index];
}
/#
function dumpAnimArray()
{
println("self.a.array:");
keys = getArrayKeys( self.a.array );
for ( i=0; i < keys.size; i++ )
{
if ( isarray( self.a.array[ keys[i] ] ) )
{
println( " array[ \"" + keys[i] + "\" ] = {array of size " + self.a.array[ keys[i] ].size + "}" );
}
else
{
println( " array[ \"" + keys[i] + "\" ] = ", self.a.array[ keys[i] ] );
}
}
}
#/
function getAnimEndPos( theanim )
{
moveDelta = getMoveDelta( theanim, 0, 1, self );
return self localToWorldCoords( moveDelta );
}
function isValidEnemy( enemy )
{
if ( !IsDefined( enemy ) )
{
return false;
}
return true;
}
function damageLocationIsAny( a, b, c, d, e, f, g, h, i, j, k, ovr )
{
/* possibile self.damageLocation's:
"torso_upper"
"torso_lower"
"helmet"
"head"
"neck"
"left_arm_upper"
"left_arm_lower"
"left_hand"
"right_arm_upper"
"right_arm_lower"
"right_hand"
"gun"
"none"
"left_leg_upper"
"left_leg_lower"
"left_foot"
"right_leg_upper"
"right_leg_lower"
"right_foot"
*/
if(!isDefined(self.damageLocation))
return false;
if ( !IsDefined( a ) ) return false; if ( self.damageLocation == a ) return true;
if ( !IsDefined( b ) ) return false; if ( self.damageLocation == b ) return true;
if ( !IsDefined( c ) ) return false; if ( self.damageLocation == c ) return true;
if ( !IsDefined( d ) ) return false; if ( self.damageLocation == d ) return true;
if ( !IsDefined( e ) ) return false; if ( self.damageLocation == e ) return true;
if ( !IsDefined( f ) ) return false; if ( self.damageLocation == f ) return true;
if ( !IsDefined( g ) ) return false; if ( self.damageLocation == g ) return true;
if( !IsDefined( h ) ) return false; if( self.damageLocation == h ) return true;
if( !IsDefined( i ) ) return false; if( self.damageLocation == i ) return true;
if( !IsDefined( j ) ) return false; if( self.damageLocation == j ) return true;
if( !IsDefined( k ) ) return false; if( self.damageLocation == k ) return true;
assert(!IsDefined(ovr));
return false;
}
function ragdollDeath( moveAnim )
{
self endon ( "killanimscript" );
lastOrg = self.origin;
moveVec = (0,0,0);
for ( ;; )
{
{wait(.05);};
force = distance( self.origin, lastOrg );
lastOrg = self.origin;
if ( self.health == 1 )
{
self.a.nodeath = true;
self startRagdoll();
{wait(.05);};
physicsExplosionSphere( lastOrg, 600, 0, force * 0.1 );
self notify ( "killanimscript" );
return;
}
}
}
function isCQBWalking()
{
return IsDefined( self.cqbwalking ) && self.cqbwalking;
}
function squared( value )
{
return value * value;
}
function randomizeIdleSet()
{
self.a.idleSet = RandomInt( 2 );
}
// meant to be used with any integer seed, for a small integer maximum (ideally one that divides anim.randomIntTableSize)
function getRandomIntFromSeed( intSeed, intMax )
{
assert( intMax > 0 );
index = intSeed % anim.randomIntTableSize;
return anim.randomIntTable[ index ] % intMax;
}
// MikeD (1/24/2008): Added Banzai Feature.
function is_banzai()
{
return IsDefined( self.banzai ) && self.banzai;
}
// SCRIPTER_MOD: JesseS (4/16/2008): HMG guys have their own anims
function is_heavy_machine_gun()
{
return IsDefined( self.heavy_machine_gunner ) && self.heavy_machine_gunner;
}
function is_zombie()
{
if (IsDefined(self.is_zombie) && self.is_zombie)
{
return true;
}
return false;
}
function is_civilian()
{
if (IsDefined(self.is_civilian) && self.is_civilian)
{
return true;
}
return false;
}
function is_skeleton(skeleton)
{
if ((skeleton == "base") && IsSubStr(get_skeleton(), "scaled"))
{
// Scaled skeletons should identify as "base" as well
return true;
}
return (get_skeleton() == skeleton);
}
function get_skeleton()
{
if (IsDefined(self.skeleton))
{
return self.skeleton;
}
else
{
return "base";
}
}
function set_orient_mode( mode, val1 )
{
/#
if ( level.dog_debug_orient == self getentnum() )
{
if ( IsDefined( val1 ) )
println( "DOG: Setting orient mode: " + mode + " " + val1 + " " + getTime() );
else
println( "DOG: Setting orient mode: " + mode + " " + getTime() );
}
#/
if ( IsDefined( val1 ) )
self OrientMode( mode, val1 );
else
self OrientMode( mode );
}
function debug_anim_print( text )
{
/#
if ( IsDefined( level.dog_debug_anims ) && level.dog_debug_anims )
println( text+ " " + getTime() );
if ( IsDefined( level.dog_debug_anims_ent ) && level.dog_debug_anims_ent == self getentnum() )
println( text+ " " + getTime() );
#/
}
function debug_turn_print( text, line )
{
/#
if ( IsDefined( level.dog_debug_turns ) && level.dog_debug_turns == self getentnum() )
{
duration = 200;
currentYawColor = (1,1,1);
lookaheadYawColor = (1,0,0);
desiredYawColor = (1,1,0);
currentYaw = AngleClamp180(self.angles[1]);
desiredYaw = AngleClamp180(self.desiredangle);
lookaheadDir = self.lookaheaddir;
lookaheadAngles = vectortoangles(lookaheadDir);
lookaheadYaw = AngleClamp180(lookaheadAngles[1]);
println( text+ " " + getTime() + " cur: " + currentYaw + " look: " + lookaheadYaw + " desired: " + desiredYaw );
}
#/
}
function debug_allow_combat()
{
/#
return ( anim_get_dvar_int( "debug_dog_allow_combat", "1" ) );
#/
return true;
}
function debug_allow_movement()
{
/#
return ( anim_get_dvar_int( "debug_dog_allow_movement", "1" ) );
#/
return true;
}
//--------------------------------------------------------------------------------------------------
// FUNCTIONS MOVED TO SHARED UTILITY FROM CP (_ZM_UTILITY)
//--------------------------------------------------------------------------------------------------
function set_zombie_var( zvar, value, is_float = false, column = 1, is_team_based = false )
{
if(!isdefined(level.zombie_vars))level.zombie_vars=[];
if ( is_team_based )
{
foreach( team in level.teams )
{
if(!isdefined(level.zombie_vars[team]))level.zombie_vars[team]=[];
level.zombie_vars[ team ][ zvar ] = value;
}
}
else
{
level.zombie_vars[zvar] = value;
}
return value;
}
function spawn_zombie( spawner,target_name,spawn_point,round_number)
{
if( !IsDefined( spawner ) )
{
/# println( "ZM >> spawn_zombie - NO SPAWNER DEFINED" ); #/
return undefined;
}
while ( GetFreeActorCount() < 1 )
{
{wait(.05);};
}
spawner.script_moveoverride = true;
if( ( isdefined( spawner.script_forcespawn ) && spawner.script_forcespawn ))
{
if(( SessionModeIsCampaignZombiesGame() ))
guy = spawner spawner::spawn( true );
else if( IsActorSpawner( spawner ) && IsDefined( level.overrideZombieSpawn ) )
{
guy = [[level.overrideZombieSpawn]]();
}
else
{
guy = spawner SpawnFromSpawner( 0, true );
}
if( !zombie_spawn_failed( guy ) )
{
guy.spawn_time = GetTime(); // Time at spawning
if ( isdefined( level.giveExtraZombies ) )
{
guy [[level.giveExtraZombies]]();
}
guy EnableAimAssist();
if(IsDefined(round_number))
{
guy._starting_round_number = round_number;
}
//guy.type = "zombie";
guy.team = level.zombie_team;
if( IsActor( guy ) )
guy ClearEntityOwner();
level.zombieMeleePlayerCounter = 0;
if( IsActor( guy ) )
guy forceteleport( spawner.origin );
guy show();
spawner.count = 666;
if( IsDefined( target_name ) )
{
guy.targetname = target_name;
}
if(IsDefined(spawn_point) && IsDefined(level.move_spawn_func))
{
guy thread [[level.move_spawn_func]](spawn_point);
}
/#
if( IsDefined( spawner.zm_variant_type ) )
{
guy.variant_type = spawner.zm_variant_type;
}
#/
return guy;
}
else
{
/# println( "ZM >> spawn_zombie - FAILED TO SPAWN A ZOMBIE FROM SPAWNER AT ", spawner.origin ); #/
return undefined;
}
}
else
{
/# println( "ZM >> spawn_zombie - ZOMBIE SPAWNER MUST BE SET FORCESPAWN", spawner.origin ); #/
return undefined;
}
return undefined;
}
/@
"Name: zombie_spawn_failed( <spawn> )"
"Summary: Checks to see if the spawned AI spawned correctly or had errors. Also waits until all spawn initialization is complete. Returns true or false."
"MandatoryArg: <spawn> : The actor that just spawned"
@/
function zombie_spawn_failed( spawn )
{
if ( IsDefined(spawn) && IsAlive(spawn) )
{
if ( IsAlive(spawn) )
{
return false;
}
}
return true;
}
//--------------------------------------------------------------------------------------------------
// FUNCTIONS MOVED TO SHARED UTILITY FROM CP (_ZM_SPAWNER)
//--------------------------------------------------------------------------------------------------
function get_desired_origin()
{
if( IsDefined( self.target ) )
{
ent = GetEnt( self.target, "targetname" );
if( !IsDefined( ent ) )
{
ent = struct::get( self.target, "targetname" );
}
if( !IsDefined( ent ) )
{
ent = GetNode( self.target, "targetname" );
}
assert( IsDefined( ent ), "Cannot find the targeted ent/node/struct, \"" + self.target + "\" at " + self.origin );
return ent.origin;
}
return undefined;
}
function hide_pop()
{
self endon( "death" );
self Ghost();
wait( 0.5 );
if ( IsDefined( self ) )
{
self Show();
util::wait_network_frame();
if(IsDefined(self))
{
self.create_eyes = true;
}
}
}
function handle_rise_notetracks(note, spot)
{
self thread finish_rise_notetracks(note, spot);
}
function finish_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");
}
}
/*
function zombie_rise_death:
function Track when the zombie should die, set the death anim, and stop the animscripted so he can die
*/
function zombie_rise_death(zombie, spot)
{
//self.nodeathragdoll = true;
zombie.zombie_rise_death_out = false;
zombie endon("rise_anim_finished");
while ( IsDefined( zombie ) && IsDefined( zombie.health ) && zombie.health > 1) // health will only go down to 1 when playing animation with AnimScripted()
{
zombie waittill("damage", amount);
}
if( IsDefined(spot) )
{
spot notify("stop_zombie_rise_fx");
}
if ( IsDefined( zombie ) )
{
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.
}
}
function get_rise_death_anim()
{
if ( self.zombie_rise_death_out )
{
return "zm_rise_death_out";
}
self.noragdoll = true;
self.nodeathragdoll = true;
return "zm_rise_death_in";
}
function 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;
}
}
function zombie_gut_explosion()
{
self.guts_explosion=1;
GibServerUtils::Annihilate( self );
}
//--------------------------------------------------------------------------------------------------
// ZOMBIE EYE GLOWS
//--------------------------------------------------------------------------------------------------
/*
function delayed_zombie_eye_glow:
function Fixes problem where zombies that climb out of the ground are warped to their start positions
function and their eyes glowed above the ground for a split second before their animation started even
function though the zombie model is hidden. and applying this delay to all the zombies doesn't really matter.
*/
function delayed_zombie_eye_glow()
{
self endon("zombie_delete");
self endon("death");
if ( ( isdefined( self.in_the_ground ) && self.in_the_ground ) || ( isdefined( self.in_the_ceiling ) && self.in_the_ceiling ) )
{
while(!IsDefined(self.create_eyes))
{
wait(0.1);
}
}
else
{
wait .5;
}
self zombie_eye_glow();
}
// When a Zombie spawns, set his eyes to glowing.
function zombie_eye_glow()
{
if(!IsDefined(self) || !IsActor( self ))
{
return;
}
if ( !IsDefined( self.no_eye_glow ) || !self.no_eye_glow )
{
self clientfield::set("zombie_has_eyes", 1);
}
}
// Called when either the Zombie dies or if his head gets blown off
function zombie_eye_glow_stop()
{
if(!IsDefined(self) || !IsActor( self ))
{
return;
}
if ( !IsDefined( self.no_eye_glow ) || !self.no_eye_glow )
{
self clientfield::set("zombie_has_eyes", 0);
}
}
//--------------------------------------------------------------------------------------------------
// ZOMBIE ROUND FUNCTIONALITY
//--------------------------------------------------------------------------------------------------
//put the conditions in here which should
//cause the failsafe to reset
function round_spawn_failsafe_debug_draw()
{
self endon("death");//guy just died
//////////////////////////////////////////////////////////////
//FAILSAFE "hack shit" DT#33203
//////////////////////////////////////////////////////////////
prevorigin = self.origin;
while(1)
{
if( ( isdefined( level.toggle_keyline_always ) && level.toggle_keyline_always ) )
{
self clientfield::set("zombie_keyline_render", 1);
wait( 1.0 );
continue;
}
wait( 4.0 );
//if i've torn a board down in the last 5 seconds, just
//wait again.
if ( IsDefined(self.lastchunk_destroy_time) )
{
if ( (GetTime() - self.lastchunk_destroy_time) < 8000 )
continue;
}
//hasnt moved 24 inches in 10 seconds - draw outline
if ( DistanceSquared( self.origin, prevorigin ) < 576 )
{
self clientfield::set("zombie_keyline_render", 1);
}
else
{
self clientfield::set("zombie_keyline_render", 0);
}
prevorigin = self.origin;
}
//////////////////////////////////////////////////////////////
//END OF FAILSAFE "hack"
//////////////////////////////////////////////////////////////
}
//--------------------------------------------------------------------------------------------------
// ZOMBIE ROUND FUNCTIONALITY
//--------------------------------------------------------------------------------------------------
//put the conditions in here which should
//cause the failsafe to reset
function round_spawn_failsafe()
{
self endon("death");//guy just died
if( ( isdefined( level.debug_keyline_zombies ) && level.debug_keyline_zombies ) )
{
self thread round_spawn_failsafe_debug_draw();
}
//////////////////////////////////////////////////////////////
//FAILSAFE "hack shit" DT#33203
//////////////////////////////////////////////////////////////
prevorigin = self.origin;
while(1)
{
if( !level.zombie_vars["zombie_use_failsafe"] )
{
return;
}
if ( ( isdefined( self.ignore_round_spawn_failsafe ) && self.ignore_round_spawn_failsafe ) )
{
return;
}
if(!IsDefined(level.failsafe_waittime))
{
level.failsafe_waittime = 30;
}
wait( level.failsafe_waittime );
if( self.missingLegs )
{
wait( 10.0 );
}
//inert zombies can ignore this
if ( ( isdefined( self.is_inert ) && self.is_inert ) )
{
continue;
}
//if i've torn a board down in the last 5 seconds, just
//wait 30 again.
if ( IsDefined(self.lastchunk_destroy_time) )
{
if ( (GetTime() - self.lastchunk_destroy_time) < 8000 )
continue;
}
//fell out of world
if ( self.origin[2] < level.zombie_vars["below_world_check"] )
{
if(( isdefined( level.put_timed_out_zombies_back_in_queue ) && level.put_timed_out_zombies_back_in_queue ) && !level flag::get("special_round") && !( isdefined( self.isscreecher ) && self.isscreecher ) )
{
level.zombie_total++;
level.zombie_total_subtract++;
}
self dodamage( self.health + 100, (0,0,0) );
break;
}
//hasnt moved 24 inches in 30 seconds?
if ( DistanceSquared( self.origin, prevorigin ) < 576 )
{
if( IsDefined( level.move_failsafe_override ) )
{
self thread [[level.move_failsafe_override]]( prevorigin );
}
else
{
//add this zombie back into the spawner queue to be re-spawned
if(( isdefined( level.put_timed_out_zombies_back_in_queue ) && level.put_timed_out_zombies_back_in_queue ) && !level flag::get("special_round"))
{
//only if they have crawled thru a window and then timed out
if ( !self.ignoreall && !( isdefined( self.nuked ) && self.nuked ) && !( isdefined( self.marked_for_death ) && self.marked_for_death ) && !( isdefined( self.isscreecher ) && self.isscreecher ) && !self.missingLegs )
{
level.zombie_total++;
level.zombie_total_subtract++;
}
}
//add this to the stats even tho he really didn't 'die'
level.zombies_timeout_playspace++;
// DEBUG HACK
self dodamage( self.health + 100, (0,0,0) );
}
break;
}
prevorigin = self.origin;
}
//////////////////////////////////////////////////////////////
//END OF FAILSAFE "hack"
//////////////////////////////////////////////////////////////
}
function ai_calculate_health( round_number )
{
level.zombie_health = level.zombie_vars["zombie_health_start"];
for ( i=2; i <= round_number; i++ )
{
// After round 10, get exponentially harder
if ( i >= 10 )
{
old_health = level.zombie_health;
level.zombie_health += Int( level.zombie_health * level.zombie_vars["zombie_health_increase_multiplier"] );
if ( level.zombie_health < old_health )
{
// we must have overflowed the signed integer space, just use the last good health, it'll give some headroom to the capped value to account for extra damage applications
level.zombie_health = old_health;
return;
}
}
else
{
level.zombie_health = Int( level.zombie_health + level.zombie_vars["zombie_health_increase"] );
}
}
}
function default_max_zombie_func( max_num, n_round )
{
/#
count = GetDvarInt( "zombie_default_max", -1 );
if ( count > -1 )
{
return count;
}
#/
max = max_num;
if( n_round < 2 )
{
max = int( max_num * 0.25 );
}
else if( n_round < 3 )
{
max = int( max_num * 0.3 );
}
else if( n_round < 4 )
{
max = int( max_num * 0.5 );
}
else if( n_round < 5 )
{
max = int( max_num * 0.7 );
}
else if( n_round < 6 )
{
max = int( max_num * 0.9 );
}
return max;
}
function zombie_speed_up()
{
if( level.round_number <= 3 )
{
return;
}
level endon( "intermission" );
level endon( "end_of_round" );
level endon( "restart_round" );
level endon( "kill_round" );
// Wait until we've finished spawning
while ( level.zombie_total > 4 )
{
wait( 3.0 );
}
// Keep checking as long as there's a zombie left.
a_ai_zombies = get_round_enemy_array();
while( a_ai_zombies.size > 0 || level.zombie_total > 0 )
{
if( a_ai_zombies.size == 1 )
{
ai_zombie = a_ai_zombies[0];
if( IsAlive( ai_zombie ) )
{
if ( IsDefined( level.zombie_speed_up ) )
{
ai_zombie thread [[ level.zombie_speed_up ]]();
}
else
{
//set_zombie_run_cycle to sprint
if( !( ai_zombie.zombie_move_speed === "sprint" ) )
{
ai_zombie zombie_utility::set_zombie_run_cycle( "sprint" );
ai_zombie.zombie_move_speed_original = ai_zombie.zombie_move_speed;
}
}
}
}
wait(0.5);
a_ai_zombies = get_round_enemy_array();
}
}
function get_current_zombie_count()
{
enemies = get_round_enemy_array();
return enemies.size;
}
function get_round_enemy_array()
{
a_ai_enemies = [];
a_ai_valid_enemies = [];
a_ai_enemies = GetAITeamArray( level.zombie_team );
for( i = 0; i < a_ai_enemies.size; i++ )
{
if ( ( isdefined( a_ai_enemies[i].ignore_enemy_count ) && a_ai_enemies[i].ignore_enemy_count ) )
{
continue;
}
if ( !isdefined( a_ai_valid_enemies ) ) a_ai_valid_enemies = []; else if ( !IsArray( a_ai_valid_enemies ) ) a_ai_valid_enemies = array( a_ai_valid_enemies ); a_ai_valid_enemies[a_ai_valid_enemies.size]=a_ai_enemies[i];;
}
return a_ai_valid_enemies;
}
// Returns an array of all zombies
function get_zombie_array()
{
enemies = [];
valid_enemies = [];
enemies = GetAiSpeciesArray( level.zombie_team, "all" );
for( i = 0; i < enemies.size; i++ )
{
if ( enemies[i].archetype == "zombie" )
{
if ( !isdefined( valid_enemies ) ) valid_enemies = []; else if ( !IsArray( valid_enemies ) ) valid_enemies = array( valid_enemies ); valid_enemies[valid_enemies.size]=enemies[i];;
}
}
return valid_enemies;
}
// self = zombie
function set_zombie_run_cycle_override_value( new_move_speed )
{
set_zombie_run_cycle( new_move_speed );
self.zombie_move_speed_override = new_move_speed;
}
// self = zombie
function set_zombie_run_cycle_restore_from_override()
{
str_restore_move_speed = self.zombie_move_speed_restore;
self.zombie_move_speed_override = undefined; // turn off the override, so set_zombie_run_cycle will accept our change
set_zombie_run_cycle( str_restore_move_speed );
}
function set_zombie_run_cycle( new_move_speed )
{
// if we're currently overriding the zombie speed, store off any attempted changes and we can restore them later
if( isdefined( self.zombie_move_speed_override ) )
{
self.zombie_move_speed_restore = new_move_speed;
return;
}
self.zombie_move_speed_original = self.zombie_move_speed;
if ( IsDefined( new_move_speed ) )
{
self.zombie_move_speed = new_move_speed;
}
else
{
if( level.gamedifficulty == 0 ) //no sprinters on easy
self set_run_speed_easy();
else
self set_run_speed();
}
if( IsDefined( level.zm_variant_type_max ) )
{
/#
// sjakatdar TU3 (11/12/2015)
// Removed the debugging here to make sure that we have same behavior as ship as this lead to some ship only problems
// If you need to test variations, just change the script locally
if(0)
{
debug_variant_type = GetDvarInt( "scr_zombie_variant_type", -1 );
if( debug_variant_type != -1 )
{
if(debug_variant_type <= level.zm_variant_type_max[self.zombie_move_speed][self.zombie_arms_position] )
{
self.variant_type = debug_variant_type;
}
else
{
self.variant_type = level.zm_variant_type_max[self.zombie_move_speed][self.zombie_arms_position] - 1;
}
}
else
{
self.variant_type = RandomInt( level.zm_variant_type_max[self.zombie_move_speed][self.zombie_arms_position] );
}
}
#/
if( self.archetype === "zombie" )
{
if ( isdefined( self.zm_variant_type_max ) )
{
self.variant_type = RandomInt( self.zm_variant_type_max[self.zombie_move_speed][self.zombie_arms_position] );
}
else
{
if ( isdefined( level.zm_variant_type_max[self.zombie_move_speed] ) )
{
self.variant_type = RandomInt( level.zm_variant_type_max[self.zombie_move_speed][self.zombie_arms_position] );
}
else
{
/#
ErrorMsg( "No variants set up for move speed " + self.zombie_move_speed );
#/
self.variant_type = 0;
}
}
}
}
self.needs_run_update = true;
self notify( "needs_run_update" );
self.deathanim = self zombie_utility::append_missing_legs_suffix( "zm_death" );
}
function set_run_speed()
{
// We may want to force running zombies at the start of a round, to get zombies in to the arena quickly
if( isdefined(level.zombie_force_run) )
{
self.zombie_move_speed = "run";
level.zombie_force_run--;
if( level.zombie_force_run <= 0 )
{
level.zombie_force_run = undefined;
}
return;
}
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";
}
}
function set_run_speed_easy()
{
rand = randomintrange( level.zombie_move_speed, level.zombie_move_speed + 25 );
// self thread print_run_speed( rand );
if( rand <= 35 )
{
self.zombie_move_speed = "walk";
}
else
{
self.zombie_move_speed = "run";
}
}
//self = zombie being knocked down
//entity = entity knocking zombie down
function setup_zombie_knockdown( entity )
{
self.knockdown = true;
zombie_to_entity = entity.origin - self.origin;
zombie_to_entity_2d = VectorNormalize( ( zombie_to_entity[0], zombie_to_entity[1], 0 ) );
zombie_forward = AnglesToForward( self.angles );
zombie_forward_2d = VectorNormalize( ( zombie_forward[0], zombie_forward[1], 0 ) );
zombie_right = AnglesToRight( self.angles );
zombie_right_2d = VectorNormalize( ( zombie_right[0], zombie_right[1], 0 ) );
dot = VectorDot( zombie_to_entity_2d, zombie_forward_2d );
if( dot >= 0.5 )
{
self.knockdown_direction = "front";
self.getup_direction = "getup_back";
}
else if ( dot < 0.5 && dot > -0.5 )
{
dot = VectorDot( zombie_to_entity_2d, zombie_right_2d );
if( dot > 0 )
{
self.knockdown_direction = "right";
if ( math::cointoss() )
{
self.getup_direction = "getup_back";
}
else
{
self.getup_direction = "getup_belly";
}
}
else
{
self.knockdown_direction = "left";
self.getup_direction = "getup_belly";
}
}
else
{
self.knockdown_direction = "back";
self.getup_direction = "getup_belly";
}
}
function clear_all_corpses()
{
corpse_array = GetCorpseArray();
for ( i = 0; i < corpse_array.size; i++ )
{
if ( IsDefined( corpse_array[ i ] ) )
{
corpse_array[ i ] Delete();
}
}
}
function get_current_actor_count()
{
count = 0;
actors = GetAiSpeciesArray( level.zombie_team, "all" );
if (IsDefined(actors))
count += actors.size;
count += get_current_corpse_count();
return count;
}
function get_current_corpse_count()
{
corpse_array = GetCorpseArray();
if (IsDefined(corpse_array))
return corpse_array.size;
return 0;
}
//--------------------------------------------------------------------------------------------------
// ZOMBIE GIBBING
//--------------------------------------------------------------------------------------------------
// gib limbs if enough firepower occurs
function zombie_gib_on_damage()
{
// self endon( "death" );
while( 1 )
{
self waittill( "damage", amount, attacker, direction_vec, point, type, tagName, ModelName, Partname, weapon );
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, type );
continue;
}
if( !( isdefined( self.gibbed ) && self.gibbed ) && isDefined(self.damageLocation) )
{
// The head_should_gib() above checks for this, so we should not randomly gib if shot in the head
if ( self damagelocationisany( "head", "helmet", "neck" ) )
{
continue;
}
//Zombie is about to stumble, so don't gib.
self.stumble = undefined;
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
if ( !GibServerUtils::IsGibbed( self, 32 ) )
GibServerUtils::GibRightArm( self );
break;
case "right_arm_upper":
case "right_arm_lower":
case "right_hand":
if ( !GibServerUtils::IsGibbed( self, 32 ) )
GibServerUtils::GibRightArm( self );
break;
case "left_arm_upper":
case "left_arm_lower":
case "left_hand":
if ( !GibServerUtils::IsGibbed( self, 16 ) )
GibServerUtils::GibLeftArm( self );
break;
case "right_leg_upper":
case "right_leg_lower":
case "right_foot":
if( self.health <= 0 )
{
GibServerUtils::GibRightLeg( self );
if( randomint( 100 ) > 75 )
GibServerUtils::GibLeftLeg( self );
self.missingLegs = true;
}
break;
case "left_leg_upper":
case "left_leg_lower":
case "left_foot":
if( self.health <= 0 )
{
GibServerUtils::GibLeftLeg( self );
if( randomint( 100 ) > 75 )
GibServerUtils::GibRightLeg( self );
self.missingLegs = true;
}
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" || type == "MOD_PROJECTILE_SPLASH" )
{
// ... in which case we have to derive the ref ourselves
self derive_damage_refs( point );
break;
}
}
}
if( IsDefined( level.custom_derive_damage_refs ) )
{
//refs = self [[ level.custom_derive_damage_refs ]](refs, point, weapon);
}
// Don't stand if a leg is gone
if( ( isdefined( self.missingLegs ) && self.missingLegs ) && self.health > 0 )//( self.a.gib_ref == "no_legs" || self.a.gib_ref == "right_leg" || self.a.gib_ref == "left_leg" ) && self.health > 0 ) //( GibServerUtils::IsGibbed(self, GIB_LEGS_LEFT_LEG_FLAG) || GibServerUtils::IsGibbed(self, GIB_LEGS_RIGHT_LEG_FLAG) ) && self.health > 0)
{
self AllowedStances( "crouch" );
// reduce collbox so player can jump over
self setPhysParams( 15, 0, 24 );
self AllowPitchAngle( 1 );
self setPitchOrient();
health = self.health;
health = health * 0.1;
if ( IsDefined( self.crawl_anim_override ) )
{
self [[ self.crawl_anim_override ]]();
}
}
if( self.health > 0 )
{
if ( IsDefined( level.gib_on_damage ) )
{
self thread [[ level.gib_on_damage ]]();
}
}
}
}
}
function add_zombie_gib_weapon_callback( weapon_name, gib_callback, gib_head_callback )
{
if(!isdefined(level.zombie_gib_weapons))level.zombie_gib_weapons=[];
if(!isdefined(level.zombie_gib_head_weapons))level.zombie_gib_head_weapons=[];
level.zombie_gib_weapons[weapon_name] = gib_callback;
level.zombie_gib_head_weapons[weapon_name] = gib_head_callback;
}
function have_zombie_weapon_gib_callback( weapon )
{
if(!isdefined(level.zombie_gib_weapons))level.zombie_gib_weapons=[];
if(!isdefined(level.zombie_gib_head_weapons))level.zombie_gib_head_weapons=[];
if ( IsWeapon( weapon ) )
weapon = weapon.name;
if ( IsDefined(level.zombie_gib_weapons[weapon] ) )
return true;
return false;
}
function get_zombie_weapon_gib_callback( weapon, damage_percent )
{
if(!isdefined(level.zombie_gib_weapons))level.zombie_gib_weapons=[];
if(!isdefined(level.zombie_gib_head_weapons))level.zombie_gib_head_weapons=[];
if ( IsWeapon( weapon ) )
weapon = weapon.name;
if ( IsDefined(level.zombie_gib_weapons[weapon] ) )
{
return self [[level.zombie_gib_weapons[weapon]]]( damage_percent );
}
return false;
}
function have_zombie_weapon_gib_head_callback( weapon )
{
if(!isdefined(level.zombie_gib_weapons))level.zombie_gib_weapons=[];
if(!isdefined(level.zombie_gib_head_weapons))level.zombie_gib_head_weapons=[];
if ( IsWeapon( weapon ) )
weapon = weapon.name;
if ( IsDefined(level.zombie_gib_head_weapons[weapon] ) )
return true;
return false;
}
function get_zombie_weapon_gib_head_callback( weapon, damage_location )
{
if(!isdefined(level.zombie_gib_weapons))level.zombie_gib_weapons=[];
if(!isdefined(level.zombie_gib_head_weapons))level.zombie_gib_head_weapons=[];
if ( IsWeapon( weapon ) )
weapon = weapon.name;
if ( IsDefined(level.zombie_gib_head_weapons[weapon] ) )
{
return self [[level.zombie_gib_head_weapons[weapon]]]( damage_location );
}
return false;
}
function zombie_should_gib( amount, attacker, type )
{
if( !IsDefined( type ) )
{
return false;
}
if ( ( isdefined( self.is_on_fire ) && self.is_on_fire ) )
{
return false;
}
if ( IsDefined( self.no_gib ) && ( self.no_gib == 1 ) )
{
return false;
}
prev_health = amount + self.health;
if( prev_health <= 0 )
{
prev_health = 1;
}
damage_percent = ( amount / prev_health ) * 100;
weapon = undefined;
if( IsDefined( attacker ) )
{
if( IsPlayer( attacker ) || ( isdefined( attacker.can_gib_zombies ) && attacker.can_gib_zombies ) )
{
if( IsPlayer( attacker ) )
{
weapon = attacker GetCurrentWeapon();
}
else
{
weapon = attacker.weapon;
}
if ( have_zombie_weapon_gib_callback( weapon ) )
{
if ( self get_zombie_weapon_gib_callback( weapon, damage_percent ) )
{
return true;
}
return false;
}
}
}
switch( type )
{
case "MOD_UNKNOWN":
case "MOD_TELEFRAG":
case "MOD_FALLING":
case "MOD_SUICIDE":
case "MOD_TRIGGER_HURT":
case "MOD_BURNED":
return false;
case "MOD_MELEE":
//Z2 HasPerk( "specialty_altmelee" ) is returning undefined
// if( isPlayer( attacker ) && randomFloat( 1 ) > 0.25 && attacker HasPerk( "specialty_altmelee" ) )
// {
// return true;
// }
// else
{
return false;
}
}
if( type == "MOD_PISTOL_BULLET" || type == "MOD_RIFLE_BULLET" )
{
if( !IsDefined( attacker ) || !IsPlayer( attacker ) )
{
return false;
}
if( weapon == level.weaponNone || (IsDefined(level.start_weapon) && weapon == level.start_weapon) || weapon.isGasWeapon )
{
return false;
}
}
// println( "**DEBUG amount = ", amount );
// println( "**DEBUG self.head_gibbed = ", self.head_gibbed );
// println( "**DEBUG self.health = ", self.health );
if( damage_percent < 10 /*|| damage_percent >= 100*/ )
{
return false;
}
return true;
}
function head_should_gib( attacker, type, point )
{
if( ( isdefined( self.head_gibbed ) && self.head_gibbed ) )
{
return false;
}
if( !isdefined( attacker ) )
{
return false;
}
if( !IsPlayer( attacker ) )
{
if( !( isdefined( attacker.can_gib_zombies ) && attacker.can_gib_zombies ) )//allow non player types to gib (robot)
{
return false;
}
}
// check if the attacker was a player
if( IsPlayer(attacker ))
{
weapon = attacker GetCurrentWeapon();
}
else
{
weapon = attacker.weapon;
}
if ( have_zombie_weapon_gib_head_callback( weapon ) )
{
if ( self get_zombie_weapon_gib_head_callback( weapon, self.damagelocation ) )
{
return true;
}
return false;
}
// 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( weapon.weapClass != "spread" )
{
return false;
}
}
// check location now that we've checked for grenade damage (which reports "none" as a location)
if ( !self damagelocationisany( "head", "helmet", "neck" ) )
{
return false;
}
// check weapon - don't want "none", base pistol, or flamethrower
if( ( type == "MOD_PISTOL_BULLET" && weapon.weapClass != "smg" && weapon.weapClass != "spread" ) ||
weapon == level.weaponNone ||
(IsDefined(level.start_weapon) && weapon == level.start_weapon) ||
weapon.isGasWeapon )
{
return false;
}
//DCS: temporarily added for cp zombie gibbing until can check a weapon gdt "do gibbing" availability.
if( SessionModeIsCampaignGame() && (type == "MOD_PISTOL_BULLET" && weapon.weapClass != "smg"))
{
return false;
}
// check the enemy's health
low_health_percent = ( self.health / self.maxhealth ) * 100;
if( low_health_percent > 10 )
{
// TOOD(David Young 9-15-14): Commenting out because gibbing anything
// should not happen here. Will remove completely once hat gibbing is supported.
// self zombie_hat_gib( attacker, type );
return false;
}
return true;
}
function zombie_hat_gib( attacker, means_of_death )
{
self endon( "death" );
if ( ( isdefined( self.hat_gibbed ) && self.hat_gibbed ) )
{
return;
}
if ( !IsDefined( self.gibSpawn5 ) || !IsDefined( self.gibSpawnTag5 ) )
{
return;
}
self.hat_gibbed = true;
if ( IsDefined( self.hatmodel ) )
{
self detach( self.hatModel, "" );
}
temp_array = [];
temp_array[0] = level._ZOMBIE_GIB_PIECE_INDEX_HAT;
self gib( "normal", temp_array );
//stat tracking
if ( IsDefined( level.track_gibs ) )
{
level [[ level.track_gibs ]]( self, temp_array );
}
}
function head_gib_damage_over_time( dmg, delay, attacker, means_of_death )
{
self endon( "death" );
self endon( "exploding" );
if( !IsAlive( self ) )
{
return;
}
if( !IsPlayer( attacker ) )
{
attacker = self;
}
if ( !IsDefined( means_of_death ) )
{
means_of_death = "MOD_UNKNOWN";
}
dot_location = self.damageLocation;
dot_weapon = self.damageweapon;
while( 1 )
{
if( IsDefined( delay ) )
{
wait( delay );
}
if (IsDefined(self))
{
if( ( isdefined( self.no_gib ) && self.no_gib ) )
{
return;
}
if(IsDefined(attacker))
{
self DoDamage( dmg, self GetTagOrigin( "j_neck" ), attacker, self, dot_location, means_of_death, 0, dot_weapon );//player can drop out
}
else
{
self DoDamage( dmg, self GetTagOrigin( "j_neck" ));
}
}
}
}
// SRS 9/7/2008: need to derive damage location for types that return location of "none"
function 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];
}
}
}
// 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
//GibServerUtils::GibEntity( self, GIB_TORSO_GUTS_FLAG );
GibServerUtils::GibRightArm( self );
}
else if( closestTag == "J_Shoulder_LE" || closestTag == "J_Elbow_LE" || closestTag == "J_Wrist_LE" )
{
if ( !GibServerUtils::IsGibbed( self, 16 ) )
GibServerUtils::GibLeftArm( self );
}
else if( closestTag == "J_Shoulder_RI" || closestTag == "J_Elbow_RI" || closestTag == "J_Wrist_RI" )
{
if ( !GibServerUtils::IsGibbed( self, 32 ) )
GibServerUtils::GibRightArm( self );
}
else if( closestTag == "J_Hip_LE" || closestTag == "J_Knee_LE" || closestTag == "J_Ankle_LE" )
{
if ( ( isdefined( self.noCrawler ) && self.noCrawler ) )
{
return;
}
GibServerUtils::GibLeftLeg( self );
if( randomint(100) > 75)
GibServerUtils::GibRightLeg( self );
self.missingLegs = true;
}
else if( closestTag == "J_Hip_RI" || closestTag == "J_Knee_RI" || closestTag == "J_Ankle_RI" )
{
if ( ( isdefined( self.noCrawler ) && self.noCrawler ) )
{
return;
}
GibServerUtils::GibRightLeg( self );
if( randomint(100) > 75)
GibServerUtils::GibLeftLeg( self );
self.missingLegs = true;
}
//return refs;
}
function 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;
}
//--------------------------------------------------------------------------------------------------
function getAnimDirection( damageyaw )
{
if( ( damageyaw > 135 ) ||( damageyaw <= -135 ) ) // Front quadrant
{
return "front";
}
else if( ( damageyaw > 45 ) &&( damageyaw <= 135 ) ) // Right quadrant
{
return "right";
}
else if( ( damageyaw > -45 ) &&( damageyaw <= 45 ) ) // Back quadrant
{
return "back";
}
else
{ // Left quadrant
return "left";
}
return "front";
}
function anim_get_dvar_int( dvar, def )
{
return int( anim_get_dvar( dvar, def ) );
}
// dvar set/fetch/check
function anim_get_dvar( dvar, def )
{
if ( GetDvarString( dvar ) != "" )
return getdvarfloat( dvar );
else
{
SetDvar( dvar, def );
return def;
}
}
function makeZombieCrawler( b_both_legs )
{
if( ( isdefined( b_both_legs ) && b_both_legs ) )
{
val = 100;
}
else
{
val = randomint( 100 );
}
if( val > 75 )
{
GibServerUtils::GibRightLeg( self );
GibServerUtils::GibLeftLeg( self );
}
else if ( val > 37 )
GibServerUtils::GibRightLeg( self );
else
GibServerUtils::GibLeftLeg( self );
self.missingLegs = true;
self AllowedStances( "crouch" );
// reduce collbox so player can jump over
self setPhysParams( 15, 0, 24 );
self AllowPitchAngle( 1 );
self setPitchOrient();
health = self.health;
health = health * 0.1;
}
function zombie_head_gib( attacker, means_of_death )
{
self endon( "death" );
if ( ( isdefined( self.head_gibbed ) && self.head_gibbed ) )
{
return;
}
if( ( isdefined( self.no_gib ) && self.no_gib ) )
{
return;
}
self.head_gibbed = true;
self zombie_eye_glow_stop();
if( !( isdefined( self.disable_head_gib ) && self.disable_head_gib ) )
{
GibServerUtils::GibHead( self );
}
self thread head_gib_damage_over_time( ceil( self.health * 0.2 ), 1, attacker, means_of_death );
}
function gib_random_parts()
{
if( ( isdefined( self.no_gib ) && self.no_gib ) )
{
return;
}
val = randomint( 100 );
if( val > 50 )
{
self zombie_utility::zombie_head_gib();
}
val = randomint( 100 );
if( val > 50 )
{
GibServerUtils::GibRightLeg( self );
}
val = randomint( 100 );
if( val > 50 )
{
GibServerUtils::GibLeftLeg( self );
}
val = randomint( 100 );
if( val > 50 )
{
if ( !GibServerUtils::IsGibbed( self, 32 ) )
GibServerUtils::GibRightArm( self );
}
val = randomint( 100 );
if( val > 50 )
{
if ( !GibServerUtils::IsGibbed( self, 16 ) )
GibServerUtils::GibLeftArm( self );
}
}
// ------------- ignore player utility ----------- //
function autoexec init_ignore_player_handler()
{
level._IGNORE_PLAYER_HANDLER = [];
}
function register_ignore_player_handler( archetype, ignore_player_func )
{
Assert( IsDefined( archetype ), "IgnorePlayerHandler undefined archetype." );
Assert( !IsDefined( level._IGNORE_PLAYER_HANDLER[ archetype ] ), "IgnorePlayerHandler for " + archetype + " is already registered." );
level._IGNORE_PLAYER_HANDLER[ archetype ] = ignore_player_func;
}
function run_ignore_player_handler()
{
if ( IsDefined( level._IGNORE_PLAYER_HANDLER[ self.archetype ] ) )
{
self [[ level._IGNORE_PLAYER_HANDLER[ self.archetype ] ]]();
}
}
// hit marker on player, call this in the damage callback for AI on the player attacker to show crosshair
function show_hit_marker() // self = player
{
if ( IsDefined( self ) && IsDefined( self.hud_damagefeedback ) )
{
self.hud_damagefeedback SetShader( "damage_feedback", 24, 48 );
self.hud_damagefeedback.alpha = 1;
self.hud_damagefeedback FadeOverTime(1);
self.hud_damagefeedback.alpha = 0;
}
}