3047 lines
74 KiB
Plaintext
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;
|
|
}
|
|
} |