IW4-Dump-Files/animscripts/melee.gsc

1734 lines
48 KiB
Plaintext
Raw Normal View History

2017-07-08 11:47:21 -07:00
#include animscripts\Utility;
#include animscripts\SetPoseMovement;
#include animscripts\Combat_Utility;
#include common_scripts\Utility;
#include maps\_utility;
#using_animtree( "generic_human" );
// ===========================================================
// AI vs Player melee
// ===========================================================
sqr8 = 8 * 8;
sqr16 = 16 * 16;
sqr32 = 32 * 32;
sqr36 = 36 * 36;
sqr64 = 64 * 64;
MELEE_RANGE = 64;
MELEE_RANGE_SQ = sqr64;
MELEE_ACTOR_BOUNDS_RADIUS = 32; // a little bigger than twice the radius of an actor's bounding box
MELEE_ACTOR_BOUNDS_RADIUS_MINUS_EPSILON = (MELEE_ACTOR_BOUNDS_RADIUS-0.1); // used for asserts
CHARGE_RANGE_SQ = 160 * 160;
CHARGE_RANGE_SQ_VS_PLAYER = 200 * 200;
FAILED_INIT_NEXT_MELEE_TIME = 150; // basic IsValid() falure
FAILED_CHARGE_NEXT_MELEE_TIME = 1500; // charge failures (both standard/aiVSai)
FAILED_STANDARD_NEXT_MELEE_TIME = 2500; // standard melee failure
NOTETRACK_SYNC = "sync";
NOTETRACK_UNSYNC = "unsync";
NOTETRACK_ATTACHKNIFE = "attach_knife";
NOTETRACK_DETACTKNIFE = "detach_knife";
NOTETRACK_STAB = "stab";
NOTETRACK_DEATH = "melee_death";
NOTETRACK_INTERACT = "melee_interact";
KNIFE_ATTACK_MODEL = "weapon_parabolic_knife";
KNIFE_ATTACK_TAG = "TAG_INHAND";
KNIFE_ATTACK_SOUND = "melee_knife_hit_body";
KNIFE_ATTACK_FX_NAME = "melee_knife_ai";
KNIFE_ATTACK_FX_PATH = "impacts/flesh_hit_knife";
KNIFE_ATTACK_FX_TAG = "TAG_KNIFE_FX";
Melee_Init()
{
precacheModel( KNIFE_ATTACK_MODEL );
level._effect[ KNIFE_ATTACK_FX_NAME ] = loadfx( KNIFE_ATTACK_FX_PATH );
}
Melee_StealthCheck()
{
if ( !isdefined( self._stealth ) )
return false;
if ( isdefined( self.ent_flag ) && isdefined( self.ent_flag[ "_stealth_enabled" ] ) && self.ent_flag[ "_stealth_enabled" ] )
if ( isdefined( self.ent_flag[ "_stealth_attack" ] ) && !self.ent_flag[ "_stealth_attack" ] )
return true;
return false;
}
Melee_TryExecuting()
{
// Must have a valid enemy before we try anything
if ( !isDefined( self.enemy ) )
return false;
if ( isdefined( self.dontmelee ) )
return false;
if ( Melee_StealthCheck() )
return false;
if ( !Melee_AcquireMutex( self.enemy ) )
return false;
Melee_ResetAction();
if ( !Melee_ChooseAction() )
{
Melee_ReleaseMutex( self.enemy );
return false;
}
self animcustom( ::Melee_MainLoop, ::Melee_EndScript );
}
// Setup internal melee structure for sanity/cache tracking
Melee_ResetAction()
{
assert( isDefined( self.melee ) );
assert( isDefined( self.enemy.melee ) );
self.melee.target = self.enemy;
self.melee.initiated = false;
self.melee.inProgress = false;
}
// After succesfully checking for melee, initialize our move
Melee_ChooseAction()
{
if ( !Melee_IsValid() )
return false;
self.melee.initiated = true;
if ( Melee_AIvsAI_ChooseAction() )
{
self.melee.func = ::Melee_AIvsAI_Main;
return true;
}
if ( Melee_Standard_ChooseAction() )
{
if ( isdefined( self.specialMelee_Standard ) )
self.melee.func = self.specialMelee_Standard;
else
self.melee.func = ::Melee_Standard_Main;
return true;
}
// Don't try again for a while since we can't start
self.melee.func = undefined;
self.nextMeleeCheckTime = gettime() + FAILED_INIT_NEXT_MELEE_TIME;
self.nextMeleeCheckTarget = self.melee.target;
return false;
}
Melee_UpdateAndValidateStartPos()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
assert( isDefined( self.melee.startPos ) );
assert( isDefined( self.melee.target ) );
ignoreActors = true;
// If the target is too close from the start pos, move the start pos in a way that our traces will succeed.
distFromTarget2d = distance2d( self.melee.startPos, self.melee.target.origin );
if ( distFromTarget2d < MELEE_ACTOR_BOUNDS_RADIUS )
{
// Calculate the direction from the target to the start pos, and push that start pos a bit
dirToStartPos2d = vectorNormalize( (self.melee.startPos[0] - self.melee.target.origin[0], self.melee.startPos[1] - self.melee.target.origin[1], 0) );
self.melee.startPos += dirToStartPos2d * (MELEE_ACTOR_BOUNDS_RADIUS - distFromTarget2d);
assertex( distance2d( self.melee.startPos, self.melee.target.origin ) >= (MELEE_ACTOR_BOUNDS_RADIUS_MINUS_EPSILON), "Invalid distance to target: " + distance2d( self.melee.startPos, self.melee.target.origin ) + ", should be more than " + (MELEE_ACTOR_BOUNDS_RADIUS_MINUS_EPSILON) );
ignoreActors = false;
}
// Height-based checks
floorPos = self getDropToFloorPosition( self.melee.startPos );
if ( !isDefined( floorPos ) )
return false;
// Point is so far from the ground that we can't reach it, fail
if ( abs( self.melee.startPos[2] - floorPos[2] ) > (MELEE_RANGE * 0.80) )
return false;
// Point is on another floor / platform, can't get that high
if ( abs( self.origin[2] - floorPos[2] ) > (MELEE_RANGE * 0.80) )
return false;
// If the point is fine, update its value
self.melee.startPos = floorPos;
assertex( distance2d( self.melee.startPos, self.melee.target.origin ) >= (MELEE_ACTOR_BOUNDS_RADIUS_MINUS_EPSILON), "Invalid distance to target: " + distance2d( self.melee.startPos, self.melee.target.origin ) + ", should be more than " + (MELEE_ACTOR_BOUNDS_RADIUS_MINUS_EPSILON) );
// Now check whether movement is possible to that point
// First check to see if we can reach our start pos
if ( !self mayMoveToPoint( self.melee.startPos, true, ignoreActors ) )
return false;
// Compute a point that's just outside of the target's bounds. Do a first trace to that point which doesn't
// ignore actors, and then a second trace which does
// if we're going around a corner, the two traces will pick a point to form a 90 angle.
// otherwise we pick a point right outside of the target's box
if ( isDefined( self.melee.startToTargetCornerAngles ) )
{
// first find the corner based on the angles
targetToStartPos = self.melee.startPos - self.melee.target.origin;
cornerDir = anglesToForward( self.melee.startToTargetCornerAngles );
cornerDirLen = vectorDot( cornerDir, targetToStartPos );
mayMoveTargetOrigin = self.melee.startPos - (cornerDir * cornerDirLen);
// push it out a bit if it's too close to the target
cornerToTarget = self.melee.target.origin - mayMoveTargetOrigin;
cornerToTargetLen = distance2d( self.melee.target.origin, mayMoveTargetOrigin );
if ( cornerToTargetLen < MELEE_ACTOR_BOUNDS_RADIUS )
mayMoveTargetOrigin -= cornerToTarget * ((MELEE_ACTOR_BOUNDS_RADIUS-cornerToTargetLen)/MELEE_ACTOR_BOUNDS_RADIUS);
}
else
{
dirToStartPos2d = vectorNormalize( (self.melee.startPos[0] - self.melee.target.origin[0], self.melee.startPos[1] - self.melee.target.origin[1], 0) );
mayMoveTargetOrigin = self.melee.target.origin + dirToStartPos2d * MELEE_ACTOR_BOUNDS_RADIUS;
}
assert( isDefined( mayMoveTargetOrigin ) );
if ( !self mayMoveFromPointToPoint( self.melee.startPos, mayMoveTargetOrigin, true, false ) )
return false;
if ( !self mayMoveFromPointToPoint( mayMoveTargetOrigin, self.melee.target.origin, true, true ) )
return false;
return true;
}
// Checks for various self / target conditions. Does not check for pathing issues.
Melee_IsValid()
{
// Must have a target still
if ( !isDefined( self.melee.target ) )
return false;
target = self.melee.target;
if ( isdefined( target.dontMelee ) )
return false;
// Distance check should usually fail
enemyDistanceSq = distanceSquared( self.origin, target.origin );
if ( isdefined( self.meleeChargeDistSq ) )
chargeDistSq = self.meleeChargeDistSq;
else if ( isplayer( target ) )
chargeDistSq = CHARGE_RANGE_SQ_VS_PLAYER;
else
chargeDistSq = CHARGE_RANGE_SQ;
// Enemy isn't even close enough to initiate
if ( !self.melee.initiated && (enemyDistanceSq > chargeDistSq) )
return false;
//
// Self Checks
//
// Don't charge if we're about to die
if ( !isAlive( self ) )
return false;
// Don't melee on the first frame ...
if ( isDefined( self.a.noFirstFrameMelee ) && (self.a.scriptStartTime >= gettime() + 50) )
return false;
// Prevent doing checks too often on the same target
if ( isDefined( self.nextMeleeCheckTime ) && isDefined( self.nextMeleeCheckTarget ) && (gettime() < self.nextMeleeCheckTime) && ( self.nextMeleeCheckTarget == target ) )
return false;
// Can't melee if we're not standing or crouching
if ( isdefined( self.a.onback ) || (self.a.pose == "prone") )
return false;
// can't melee while sidearm is out. need animations for this.
// we rely on main loop to put away sidearm if necessary.
if ( usingSidearm() )
return false;
// don't melee charge with a grenade in range, unless you have a shield
if ( isDefined( self.grenade ) && ( self.frontShieldAngleCos == 1 ) )
return false;
//
// Enemy checks
//
if ( !isAlive( target ) )
return false;
// no melee on enemies that are flagged as such
if ( isDefined( target.dontAttackMe ) || (isDefined( target.ignoreMe ) && target.ignoreMe) )
return false;
// no meleeing virtual targets
if ( !isAI( target ) && !isPlayer( target ) )
return false;
if ( isAI( target ) )
{
// special state, can't allow meleeing
if ( target isInScriptedState() )
return false;
// crawling/dying
if ( target doingLongDeath() || target.delayedDeath )
return false;
}
// Check if our enemy is in a proper pose to get melee'd
if ( isPlayer( target ) )
enemyPose = target getStance();
else
enemyPose = target.a.pose;
if ( (enemyPose != "stand") && (enemyPose != "crouch") )
return false;
// Disable melee completely when both targets are invulnerable
if ( isDefined( self.magic_bullet_shield ) && isDefined( target.magic_bullet_shield ) )
return false;
// don't melee charge with a grenade in range of the enemy
if ( isDefined( target.grenade ) )
return false;
//
// Position Checks
//
// Have extra tolerance when already in progress, since some animations twist the origin quite a bit ( for example standard melee )
if ( self.melee.inProgress )
yawThreshold = 110;
else
yawThreshold = 60;
yawToEnemy = AngleClamp180( self.angles[ 1 ] - GetYaw( target.origin ) );
if ( abs( yawToEnemy ) > yawThreshold )
return false;
// Enemy is already close enough to melee.
if ( enemyDistanceSq <= MELEE_RANGE_SQ )
return true;
// if we already started, but no longer in melee range, fail/abort
if ( self.melee.inProgress )
return false;
// we can't melee from our position and need to charge, but failed a charge recently on the same target ; fail
if ( isDefined( self.nextMeleeChargeTime ) && isDefined( self.nextMeleeChargeTarget ) && (gettime() < self.nextMeleeChargeTime) && (self.nextMeleeChargeTarget == target) )
return false;
return true;
}
Melee_StartMovement()
{
self.melee.playingMovementAnim = true;
self.a.movement = "run";
}
Melee_StopMovement()
{
self clearanim( %body, 0.2 );
self.melee.playingMovementAnim = undefined;
self.a.movement = "stop";
self orientMode( "face default" );
}
Melee_MainLoop()
{
self endon( "killanimscript" );
self endon( "end_melee" );
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
assert( isDefined( self.melee.func ) );
while( true )
{
prevFunc = self.melee.func;
[[ self.melee.func ]]();
// No more melee actions available, or no new ones, finish
if ( !isDefined( self.melee.func ) || (prevFunc == self.melee.func) )
break;
}
}
Melee_Standard_DelayStandardCharge( target )
{
if ( !isDefined ( target ) )
return;
self.nextMeleeStandardChargeTime = getTime() + FAILED_STANDARD_NEXT_MELEE_TIME;
self.nextMeleeStandardChargeTarget = target;
}
Melee_Standard_CheckTimeConstraints()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
assert( isDefined( self.melee.target ) );
// out of range and too early to do a standard melee
targetDistSq = distanceSquared( self.melee.target.origin, self.origin );
if ( (targetDistSq > MELEE_RANGE_SQ) && isDefined( self.nextMeleeStandardChargeTime ) && isDefined( self.nextMeleeStandardChargeTarget ) && (getTime() < self.nextMeleeStandardChargeTime) && (self.nextMeleeStandardChargeTarget == self.melee.target) )
return false;
return true;
}
Melee_Standard_ChooseAction()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
assert( isDefined( self.melee.target ) );
if ( isDefined( self.melee.target.magic_bullet_shield ) )
return false;
if ( !Melee_Standard_CheckTimeConstraints() )
return false;
if ( isdefined( self.melee.target.specialMeleeChooseAction ) )
return false;
return Melee_Standard_UpdateAndValidateTarget();
}
Melee_Standard_ResetGiveUpTime()
{
if ( isdefined( self.meleeChargeDistSq ) )
chargeDistSq = self.meleeChargeDistSq;
else if ( isplayer( self.melee.target ) )
chargeDistSq = CHARGE_RANGE_SQ_VS_PLAYER;
else
chargeDistSq = CHARGE_RANGE_SQ;
if ( distanceSquared( self.origin, self.melee.target.origin ) > chargeDistSq )
self.melee.giveUpTime = gettime() + 3000;
else
self.melee.giveUpTime = gettime() + 1000;
}
Melee_Standard_Main()
{
self animMode( "zonly_physics" );
Melee_Standard_ResetGiveUpTime();
while ( true )
{
assert( isdefined( self.melee.target ) );
// first, charge forward if we need to; get into place to play the melee animation
if ( !Melee_Standard_GetInPosition() )
{
// if we couldn't get in place to melee, don't try to charge for a little while and abort
self.nextMeleeChargeTime = getTime() + FAILED_CHARGE_NEXT_MELEE_TIME;
self.nextMeleeChargeTarget = self.melee.target;
break;
}
if ( !isdefined( self.melee.target ) )
break;
assert( (self.a.pose == "stand") || (self.a.pose == "crouch") );
self animscripts\battleChatter_ai::evaluateMeleeEvent();
self orientMode( "face point", self.melee.target.origin );
self setflaggedanimknoballrestart( "meleeanim", %melee_1, %body, 1, .2, 1 );
self.melee.inProgress = true;
// If the attack loop returns false, we need to stop this melee
if( !Melee_Standard_PlayAttackLoop() )
{
// Since getting here means that we've done a melee but our attack is no longer valid, delay before we can do a standard attack again.
Melee_Standard_DelayStandardCharge( self.melee.target );
break;
}
}
self animMode( "none" );
}
Melee_Standard_PlayAttackLoop()
{
while ( true )
{
self waittill( "meleeanim", note );
if ( note == "end" )
{
return true;
}
if ( note == "stop" )
{
// check if it's worth continuing with another melee.
// and see if we could so something better , or continue with our attacks
if ( !Melee_ChooseAction() )
return false;
// Return whether the action we choose is the same as this one, in which case we'll simply continue.
assert( isDefined( self.melee.func ) );
if ( self.melee.func != ::Melee_Standard_Main )
return true;
}
if ( note == "fire" )
{
if ( isdefined( self.melee.target ) )
{
oldhealth = self.melee.target.health;
self melee();
if ( isDefined( self.melee.target ) && (self.melee.target.health < oldhealth) )
Melee_Standard_ResetGiveUpTime();
}
}
}
}
// this will update our target position based on our target
Melee_Standard_UpdateAndValidateTarget()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
if ( !isDefined( self.melee.target ) )
return false;
if ( !Melee_IsValid() )
return false;
dirToTarget = vectorNormalize( self.melee.target.origin - self.origin );
self.melee.startPos = self.melee.target.origin - 40.0 * dirToTarget;
return Melee_UpdateAndValidateStartPos();
}
distance2dSquared( a, b ) // should be moved to code
{
diff = (a[0] - b[0], a[1] - b[1], 0 );
return lengthSquared( diff );
}
// this function makes the guy run towards his enemy, and start raising his gun if he's close enough to melee.
// it will return false if he gives up, or true if he's ready to start a melee animation.
Melee_Standard_GetInPosition()
{
if ( !Melee_Standard_UpdateAndValidateTarget() )
return false;
enemyDistanceSq = distance2dSquared( self.origin, self.melee.target.origin );
if ( enemyDistanceSq <= MELEE_RANGE_SQ )
{
// just play a melee-from-standing transition
self SetFlaggedAnimKnobAll( "readyanim", %stand_2_melee_1, %body, 1, .3, 1 );
self animscripts\shared::DoNoteTracks( "readyanim" );
return true;
}
self Melee_PlayChargeSound();
prevEnemyPos = self.melee.target.origin;
sampleTime = 0.1;
raiseGunAnimTravelDist = length( getmovedelta( %run_2_melee_charge, 0, 1 ) );
meleeAnimTravelDist = 32;
shouldRaiseGunDist = MELEE_RANGE * 0.75 + meleeAnimTravelDist + raiseGunAnimTravelDist;
shouldRaiseGunDistSq = shouldRaiseGunDist * shouldRaiseGunDist;
shouldMeleeDist = MELEE_RANGE + meleeAnimTravelDist;
shouldMeleeDistSq = shouldMeleeDist * shouldMeleeDist;
raiseGunFullDuration = getanimlength( %run_2_melee_charge ) * 1000;
raiseGunFinishDuration = raiseGunFullDuration - 100;
raiseGunPredictDuration = raiseGunFullDuration - 200;
raiseGunStartTime = 0;
predictedEnemyDistSqAfterRaiseGun = undefined;
runAnim = %run_lowready_F;
if ( isplayer( self.melee.target ) && self.melee.target == self.enemy )
self orientMode( "face enemy" );
else
self orientMode( "face point", self.melee.target.origin );
self SetFlaggedAnimKnobAll( "chargeanim", runAnim, %body, 1, .3, 1 );
raisingGun = false;
while ( 1 )
{
time = gettime();
willBeWithinRangeWhenGunIsRaised = ( isdefined( predictedEnemyDistSqAfterRaiseGun ) && predictedEnemyDistSqAfterRaiseGun <= shouldRaiseGunDistSq );
if ( !raisingGun )
{
if ( willBeWithinRangeWhenGunIsRaised )
{
Melee_StartMovement();
self SetFlaggedAnimKnobAllRestart( "chargeanim", %run_2_melee_charge, %body, 1, .2, 1 );
raiseGunStartTime = time;
raisingGun = true;
}
}
else
{
// if we *are* raising our gun, don't stop unless we're hopelessly out of range,
// or if we hit the end of the raise gun animation and didn't melee yet
withinRangeNow = enemyDistanceSq <= shouldRaiseGunDistSq;
if ( time - raiseGunStartTime >= raiseGunFinishDuration || ( !willBeWithinRangeWhenGunIsRaised && !withinRangeNow ) )
{
Melee_StartMovement();
self SetFlaggedAnimKnobAll( "chargeanim", runAnim, %body, 1, .3, 1 );
raisingGun = false;
}
}
self animscripts\shared::DoNoteTracksForTime( sampleTime, "chargeanim" );
// now that we moved a bit, see if our target moved before we check for valid melee
// it's possible something happened in the meantime that makes meleeing impossible.
if ( !Melee_Standard_UpdateAndValidateTarget() )
{
Melee_StopMovement();
return false;
}
enemyDistanceSq = distance2dSquared( self.origin, self.melee.target.origin );
enemyVel = vector_multiply( self.melee.target.origin - prevEnemyPos, 1 / ( gettime() - time ) );// units / msec
prevEnemyPos = self.melee.target.origin;
// figure out where the player will be when we hit them if we (a) start meleeing now, or (b) start raising our gun now
predictedEnemyPosAfterRaiseGun = self.melee.target.origin + vector_multiply( enemyVel, raiseGunPredictDuration );
predictedEnemyDistSqAfterRaiseGun = distance2dSquared( self.origin, predictedEnemyPosAfterRaiseGun );
// if we're done raising our gun, and starting a melee now will hit the guy, our preparation is finished
// when fighting non-players, don't wait for the gun raise to finish, or we'll walk through them
if ( raisingGun && (enemyDistanceSq <= shouldMeleeDistSq) && (gettime() - raiseGunStartTime >= raiseGunFinishDuration || !isPlayer( self.melee.target )) )
break;
// don't keep charging if we've been doing this for too long.
if ( !raisingGun && (gettime() >= self.melee.giveUpTime) )
{
Melee_StopMovement();
return false;
}
}
Melee_StopMovement();
return true;
}
Melee_PlayChargeSound()
{
if ( !isdefined( self.a.nextMeleeChargeSound ) )
self.a.nextMeleeChargeSound = 0;
if ( ( isdefined( self.enemy ) && isplayer( self.enemy ) ) || randomint( 3 ) == 0 )
{
if ( gettime() > self.a.nextMeleeChargeSound )
{
self animscripts\face::SayGenericDialogue( "meleecharge" );
self.a.nextMeleeChargeSound = gettime() + 8000;
}
}
}
// ===========================================================
// AI vs AI synced melee
// ===========================================================
Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Flip( angleDiff )
{
flipAngleThreshold = 90;
// Have extra tolerance when already in progress, since some animations twist the origin quite a bit ( for example standard melee )
if ( self.melee.inProgress )
flipAngleThreshold += 50;
// facing each other
if ( abs( angleDiff ) < flipAngleThreshold )
return false;
target = self.melee.target;
Melee_Decide_Winner();
if ( self.melee.winner )
{
self.melee.animName = %melee_F_awin_attack;
target.melee.animName = %melee_F_awin_defend;
target.melee.surviveAnimName = %melee_F_awin_defend_survive;
}
else
{
self.melee.animName = %melee_F_dwin_attack;
target.melee.animName = %melee_F_dwin_defend;
}
return true;
}
Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Wrestle( angleDiff )
{
wrestleAngleThreshold = 100;
// Have extra tolerance when already in progress, since some animations twist the origin quite a bit ( for example standard melee )
if ( self.melee.inProgress )
wrestleAngleThreshold += 50;
// facing each other
if ( abs( angleDiff ) < wrestleAngleThreshold )
return false;
target = self.melee.target;
// Attacker must be able to win
if ( isDefined( target.magic_bullet_shield ) )
return false;
/#
// DEBUGGING CASES FOR TEST MAP
if ( isDefined( target.meleeAlwaysWin ) )
{
assert( !isDefined( self.magic_bullet_shield ) );
return false;
}
#/
self.melee.winner = true;
self.melee.animName = %bog_melee_R_attack;
target.melee.animName = %bog_melee_R_defend;
target.melee.surviveAnimName = %bog_melee_R_backdeath2;
return true;
}
Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Behind( angleDiff )
{
// from behind right
if ( (-90 > angleDiff) || (angleDiff > 0) )
return false;
target = self.melee.target;
// Attacker must be able to win
if ( isDefined( target.magic_bullet_shield ) )
return false;
/#
// DEBUGGING CASES FOR TEST MAP
if ( isDefined( target.meleeAlwaysWin ) )
{
assert( !isDefined( self.magic_bullet_shield ) );
return false;
}
#/
self.melee.winner = true;
self.melee.animName = %melee_sync_attack;
target.melee.animName = %melee_sync_defend;
return true;
}
Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_BuildExposedList()
{
// If this AI is forced to play a specific melee, do so!
if ( isDefined( self.meleeForcedExposedFlip ) )
{
assert( !isDefined( self.meleeForcedExposedWrestle ) ); //can't force both
exposedMelees[0] = ::Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Flip;
}
else if ( isDefined( self.meleeForcedExposedWrestle ) )
{
exposedMelees[0] = ::Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Wrestle;
}
else
{
// Randomize whether flip or wrestle gets tested first. Behind always tested last.
flipIndex = randomInt( 2 );
wrestleIndex = 1 - flipIndex;
behindIndex = 2;
exposedMelees[flipIndex] = ::Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Flip;
exposedMelees[wrestleIndex] = ::Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Wrestle;
exposedMelees[behindIndex] = ::Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_Behind;
}
return exposedMelees;
}
Melee_AIvsAI_Exposed_ChooseAnimationAndPosition()
{
assert( isDefined( self ) );
assert( isDefined( self.melee.target ) );
// Choose which sequence to play based on angles
target = self.melee.target;
angleToEnemy = vectortoangles( target.origin - self.origin );
angleDiff = AngleClamp180( target.angles[ 1 ] - angleToEnemy[ 1 ] );
exposedMelees = Melee_AIvsAI_Exposed_ChooseAnimationAndPosition_BuildExposedList();
for( i = 0; i < exposedMelees.size; i++ )
{
// Test each melee move in order
if ( [[ exposedMelees[i] ]]( angleDiff ) )
{
assert( isDefined ( self.melee.animName ) );
assert( isDefined ( target.melee.animName ) );
// Calculate the position based on the chosen animation. The angles are set so that the attacker faces the enemy before linking
self.melee.startAngles = ( 0, angleToEnemy[1], 0 );
self.melee.startPos = getStartOrigin( target.origin, target.angles, self.melee.animName );
// Succeed if it's on a proper floor/height, we can move in position and we we have a LOS to the target
if ( Melee_UpdateAndValidateStartPos() )
return true;
}
}
// No moves possible
return false;
}
Melee_Decide_Winner()
{
assert( isDefined( self.melee ) );
assert( isDefined( self.melee.target ) );
target = self.melee.target;
/#
// DEBUGGING CASES FOR TEST MAP
if( isDefined( self.meleeAlwaysWin ) )
{
assert( !isDefined( target.magic_bullet_shield ) );
self.melee.winner = true;
return;
}
else if ( isDefined( target.meleeAlwaysWin ) )
{
assert( !isDefined( self.magic_bullet_shield ) );
self.melee.winner = false;
return;
}
#/
// Figure out who wins
if ( isDefined( self.magic_bullet_shield ) )
{
assert( !isDefined( target.magic_bullet_shield ) );
self.melee.winner = true;
}
else if ( isDefined( target.magic_bullet_shield ) )
{
self.melee.winner = false;
}
else
{
self.melee.winner = cointoss();
}
}
Melee_AIvsAI_SpecialCover_ChooseAnimationAndPosition()
{
assert( isDefined( self ) );
assert( isDefined( self.melee.target ) );
assert( isDefined( self.melee.target.covernode ) );
target = self.melee.target;
Melee_Decide_Winner();
if ( target.covernode.type == "Cover Left" )
{
if ( self.melee.winner )
{
self.melee.animName = %cornerSdL_melee_winA_attacker;
target.melee.animName = %cornerSdL_melee_winA_defender;
target.melee.surviveAnimName = %cornerSdL_melee_winA_defender_survive;
}
else
{
self.melee.animName = %cornerSdL_melee_winD_attacker;
self.melee.surviveAnimName = %cornerSdL_melee_winD_attacker_survive;
target.melee.animName = %cornerSdL_melee_winD_defender;
}
}
else // Right
{
assert( target.covernode.type == "Cover Right" );
if ( self.melee.winner )
{
self.melee.animName = %cornerSdR_melee_winA_attacker;
target.melee.animName = %cornerSdR_melee_winA_defender;
}
else
{
self.melee.animName = %cornerSdR_melee_winD_attacker;
target.melee.animName = %cornerSdR_melee_winD_defender;
}
}
// The start position is based on the cover node of the target
self.melee.startPos = getStartOrigin( target.covernode.origin, target.covernode.angles, self.melee.animName );
self.melee.startAngles = ( target.covernode.angles[0], AngleClamp180( target.covernode.angles[1] + 180 ), target.covernode.angles[2] );
target.melee.faceYaw = getNodeForwardYaw( target.covernode );
// Make sure we can move to the selected point ( no re-try for now )
self.melee.startToTargetCornerAngles = target.covernode.angles;
if ( !Melee_UpdateAndValidateStartPos() )
{
self.melee.startToTargetCornerAngles = undefined;
return false;
}
return true;
}
Melee_AIvsAI_SpecialCover_CanExecute()
{
assert( isDefined ( self ) );
assert( isDefined ( self.melee.target ) );
cover = self.melee.target.covernode;
if ( !isDefined( cover ) )
return false;
// Make sure the enemy is hiding or leaning out and not currently exposing
if ( (distanceSquared( cover.origin, self.melee.target.origin ) > 16) && isdefined( self.melee.target.a.coverMode ) && ( (self.melee.target.a.coverMode != "hide") && (self.melee.target.a.coverMode != "lean") ) )
return false;
// Must be within a some arc in front of the cover
coverToSelfAngles = vectortoangles( self.origin - cover.origin );
angleDiff = AngleClamp180( cover.angles[ 1 ] - coverToSelfAngles[ 1 ] );
// Only do it for left/right covers for now
if ( cover.type == "Cover Left" )
{
if ( (angleDiff >= -50) && (angleDiff <= 0) )
return true;
}
else if ( cover.type == "Cover Right" )
{
if ( (angleDiff >= 0) && (angleDiff <= 50) )
return true;
}
return false;
}
Melee_AIvsAI_ChooseAction()
{
assert( isDefined( self.melee ) );
assert( isDefined( self.melee.target ) );
target = self.melee.target;
// We can only do AI vs AI between human AIs
if ( !isAI( target ) || (target.type != "human") )
return false;
// Can't do AIvsAI in stairs
assert( isDefined( self.stairsState ) );
assert( isDefined( target.stairsState ) );
if ( (self.stairsState != "none") || (target.stairsState != "none") )
return false;
// At least one of the two needs not to have bullet shield to be in melee to begin with
assert( !isDefined( self.magic_bullet_shield ) || !isdefined( self.melee.target.magic_bullet_shield ) );
if ( isdefined( self.specialMeleeChooseAction ) )
{
if ( ![[ self.specialMeleeChooseAction ]]() )
return false;
self.melee.precisePositioning = true;
}
else if ( isdefined( target.specialMeleeChooseAction ) )
{
return false;
}
// If we can execute a special cover sequence, do so, otherwise revert to standard
else if ( Melee_AIvsAI_SpecialCover_CanExecute() && Melee_AIvsAI_SpecialCover_ChooseAnimationAndPosition() )
{
self.melee.precisePositioning = true;
}
else
{
if ( !Melee_AIvsAI_Exposed_ChooseAnimationAndPosition() )
return false;
self.melee.precisePositioning = false;
}
// Save the current facing yaw if none of the behaviors requested something specific.
if ( !isDefined ( target.melee.faceYaw ) )
target.melee.faceYaw = target.angles[1];
// And the offset from the target to the start pos so that we can do validity checks
self.melee.startPosOffset = ( self.melee.startPos - target.origin );
// If we get here, we can get to our position and an action has been chosen
return true;
}
Melee_AIvsAI_ScheduleNoteTrackLink( target )
{
// Set us up to get sync'd when we get the note track ( not immediately as regular melees )
self.melee.syncNoteTrackEnt = target;
target.melee.syncNoteTrackEnt = undefined;
}
Melee_AIvsAI_TargetLink( target )
{
assert( isDefined( self ) );
assert( isDefined( target ) );
// If the target is no longer meleeing, don't attach (should only be valid if surviving)
if ( !isDefined( target.melee ) )
{
assert( isDefined( self.melee.survive ) );
return;
}
self Melee_PlayChargeSound();
// Only attach to our target if he's still alive
if ( !isAlive( target ) )
return;
// Sync up - this var needs to stay outside the melee struct because code uses it!
self.syncedMeleeTarget = target;
target.syncedMeleeTarget = self;
self.melee.linked = true;
target.melee.linked = true;
self linkToBlendToTag( target, "tag_sync", true, true );
}
Melee_AIvsAI_Main()
{
// charge to correct position
if ( !Melee_AIvsAI_GetInPosition() )
{
// if we couldn't get in place to melee, don't try to charge for a little while and abort
self.nextMeleeChargeTime = gettime() + FAILED_CHARGE_NEXT_MELEE_TIME;
self.nextMeleeChargeTarget = self.melee.target;
return;
}
target = self.melee.target;
// make sure both are still alive - get in position should have aborted otherwise
assert( isAlive( self ) && isAlive( target ) );
// setup linking/syncing
// catch any leftover sync issues
assert( !isDefined( self.syncedMeleeTarget ) );
assert( !isDefined( target.syncedMeleeTarget ) );
assert( isDefined( self.melee.animName ) );
assert( animHasNotetrack( self.melee.animName, NOTETRACK_SYNC ) );
self Melee_AIvsAI_ScheduleNoteTrackLink( target );
// Setup who gets to live
if ( self.melee.winner )
{
self.melee.death = undefined;
target.melee.death = true;
}
else
{
target.melee.death = undefined;
self.melee.death = true;
}
// link up the two in case someone ends the script early
self.melee.partner = target;
target.melee.partner = self;
if ( self usingSideArm() )
{
self forceUseWeapon( self.primaryWeapon, "primary" );
self.lastWeapon = self.primaryWeapon;
}
if ( target usingSideArm() )
{
target forceUseWeapon( target.primaryWeapon, "primary" );
target.lastWeapon = target.primaryWeapon;
}
//save weapons
self.melee.weapon = self.weapon;
self.melee.weaponSlot = self getCurrentWeaponSlotName();
target.melee.weapon = target.weapon;
target.melee.weaponSlot = target getCurrentWeaponSlotName();
// mark melee as in progress for the initiater
self.melee.inProgress = true;
// Run animation on our target
target animcustom( ::Melee_AIvsAI_Execute, ::Melee_EndScript );
target thread Melee_AIvsAI_AnimCustomInterruptionMonitor( self );
// release the target now that it started, we're no longer allowed to mess with it
self.melee.target = undefined;
// We're already in a custom, call directly
self Melee_AIvsAI_Execute();
}
Melee_AIvsAI_AnimCustomInterruptionMonitor( attacker )
{
assert( isDefined( attacker ) );
self endon( "end_melee" );
self endon( "melee_aivsai_execute" );
// Wait for a couple of frames. If the execution hasn't started then, fail.
wait 0.1;
if ( isDefined( attacker ) )
attacker notify( "end_melee" );
self notify( "end_melee" );
}
Melee_AIvsAI_GetInPosition_UpdateAndValidateTarget( initialTargetOrigin, giveUpTime )
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
assert( isDefined( initialTargetOrigin ) );
// Took too long
if ( isDefined( giveUpTime ) && (giveUpTime <= getTime()) )
return false;
// Check if we can still melee while charging
if ( !Melee_IsValid() )
return false;
target = self.melee.target;
// If target moves too much , fail
positionDelta = distanceSquared( target.origin, initialTargetOrigin );
// Less tolerant to movement when the target should be in cover
assert( isDefined( self.melee.precisePositioning ) );
if ( self.melee.precisePositioning )
positionThreshold = sqr16;
else
positionThreshold = sqr36;
if ( positionDelta > positionThreshold )
return false;
// Make sure the target hasn't moved in a way that would make us unable to do the melee
// Update our starting position
// Make sure target is not out of reach
self.melee.startPos = target.origin + self.melee.startPosOffset;
if ( !Melee_UpdateAndValidateStartPos() )
return false;
return true;
}
Melee_AIvsAI_GetInPosition_IsSuccessful( initialTargetOrigin )
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
assert( isDefined( self.melee.startPos ) );
assert( isDefined( self.melee.target ) );
assert( isDefined( initialTargetOrigin ) );
// at the start pos
dist2dToStartPos = distanceSquared( (self.origin[0], self.origin[1], 0), (self.melee.startPos[0], self.melee.startPos[1], 0) );
if ( (dist2dToStartPos < sqr8) && (abs( self.melee.startPos[2] - self.origin[2] ) < MELEE_RANGE) )
return true;
// in between enemy and start pos
dist2dFromStartPosToTargetSq = distanceSquared( (initialTargetOrigin[0], initialTargetOrigin[1], 0), (self.melee.startPos[0], self.melee.startPos[1], 0) );
dist2dToTargetSq = distanceSquared( (self.origin[0], self.origin[1], 0), (self.melee.target.origin[0], self.melee.target.origin[1], 0) );
if ( (dist2dFromStartPosToTargetSq > dist2dToTargetSq) && (abs( self.melee.target.origin[2] - self.origin[2] ) < MELEE_RANGE) )
return true;
return false;
}
Melee_AIvsAI_GetInPosition_Finalize( initialTargetOrigin )
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
assert( isDefined( self.melee.precisePositioning ) );
assert( isDefined( initialTargetOrigin ) );
// stop the animation and such
Melee_StopMovement();
if ( self.melee.precisePositioning )
{
assert( isDefined( self.melee.startPos ) );
assert( isDefined( self.melee.startAngles ) );
self forceTeleport( self.melee.startPos, self.melee.startAngles );
wait 0.05;
}
else
{
self orientMode( "face angle", self.melee.startAngles[1] );
wait 0.05;
}
// Teleport might have made the sequence invalid, make sure it's still right as we exit
return Melee_AIvsAI_GetInPosition_UpdateAndValidateTarget( initialTargetOrigin );
}
Melee_AIvsAI_GetInPosition()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
// Check if we can still melee while charging
if ( !Melee_IsValid() )
return false;
Melee_StartMovement();
self clearanim( %body, 0.2 );
self setAnimKnobAll( animscripts\run::GetRunAnim(), %body, 1, 0.2 );
self animMode( "zonly_physics" );
self.keepClaimedNode = true;
giveUpTime = getTime() + 1500;
assert( isDefined( self.melee.target ) );
assert( isDefined( self.melee.target.origin ) );
initialTargetOrigin = self.melee.target.origin;
/#
self notify ( "MDBG_att_getInPosition", self.melee.target );
self.melee.target notify ( "MDBG_def_getInPosition", self );
#/
while ( Melee_AIvsAI_GetInPosition_UpdateAndValidateTarget( initialTargetOrigin, giveUpTime ) )
{
if ( Melee_AIvsAI_GetInPosition_IsSuccessful( initialTargetOrigin ) )
return Melee_AIvsAI_GetInPosition_Finalize( initialTargetOrigin );
// play run forward anim
self orientMode( "face point", self.melee.startPos );
wait .05;
}
Melee_StopMovement();
return false;
}
Melee_AIvsAI_Execute()
{
self endon( "killanimscript" );
self endon( "end_melee" );
self notify( "melee_aivsai_execute" );
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
self animMode( "zonly_physics" );
self.a.special = "none";
self.specialDeathFunc = undefined;
// Check whether something makes us drop our weapon. If we get revived we'll need to restore it
self thread Melee_DroppedWeaponMonitorThread();
// Check for our partner ending melee early, for getting saved etc
self thread Melee_PartnerEndedMeleeMonitorThread();
// If we have faceYaw specified, use them, otherwise stay oriented as we were
if ( isDefined( self.melee.faceYaw ) )
self orientMode( "face angle", self.melee.faceYaw );
else
self orientMode( "face current" );
// only have standing melees for now, set these with notetracks
self.a.pose = "stand";
self clearanim( %body, 0.2 );
// Disable some interruptions if we're going to die, we don't want to break out of melee
if ( isDefined( self.melee.death ) )
self Melee_DisableInterruptions();
// Start the base animation, and loop over the note tracks until one of them tell us to stop
self setFlaggedAnimKnobAllRestart( "meleeAnim", self.melee.animName, %body, 1, 0.2 );
endNote = self animscripts\shared::DoNoteTracks( "meleeAnim", ::Melee_HandleNoteTracks );
// If the survival animation stopped us, play it now
if ( (endNote == NOTETRACK_DEATH) && isDefined( self.melee.survive ) )
{
// If we dropped our weapon but we got saved, restore it immediately
Melee_DroppedWeaponRestore();
self setflaggedanimknoballrestart( "meleeAnim", self.melee.surviveAnimName, %body, 1, 0.2 );
endNote = self animscripts\shared::DoNoteTracks( "meleeAnim", ::Melee_HandleNoteTracks );
}
// If we're marked for death, make sure we die before exiting
if ( isDefined( self.melee ) && isDefined( self.melee.death ) )
self kill();
// note sure what this does:
self.keepClaimedNode = false;
}
Melee_DisableInterruptions()
{
//save the states so we can restore them
self.melee.wasAllowingPain = self.allowPain;
self.melee.wasFlashbangImmune = self.flashBangImmunity;
//disable what makes sense
self disable_pain();
self setFlashbangImmunity( true );
}
Melee_NeedsWeaponSwap()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
return ( isDefined( self.melee.weapon ) && (self.melee.weapon != "none") && (self.weapon != self.melee.weapon) );
}
Melee_DroppedWeaponRestore()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
// Give back the weapon we had initially, if we had one, and we dropped it
if ( self.weapon != "none" && self.lastWeapon != "none" )
return;
// If we did not have one to begin with, not much we can do
if ( !isDefined( self.melee.weapon ) || (self.melee.weapon == "none") )
return;
// Immediately swap the weapon. Can't animate when ending the script, and we don't want to when playing the revive
self forceUseWeapon( self.melee.weapon, self.melee.weaponSlot );
// if we dropped the item, destroy it
if ( isDefined( self.melee.droppedWeaponEnt ) )
{
self.melee.droppedWeaponEnt delete();
self.melee.droppedWeaponEnt = undefined;
}
}
Melee_DroppedWeaponMonitorThread()
{
self endon( "killanimscript" );
self endon( "end_melee" );
assert( isDefined( self.melee ) );
self waittill( "weapon_dropped", droppedWeapon );
// the weapon drop might fail if in solid and such. droppedWeapon would be 'removed entity' in that case.
if ( isDefined( droppedWeapon ) )
{
assert( isDefined( self.melee ) );
self.melee.droppedWeaponEnt = droppedWeapon;
}
}
Melee_PartnerEndedMeleeMonitorThread_ShouldAnimSurvive()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
// Doesn't have a survival animation set
if ( !isDefined( self.melee.surviveAnimName ) )
return false;
// Too early if before they interact
if ( !isDefined( self.melee.surviveAnimAllowed ) )
return false;
return true;
}
Melee_PartnerEndedMeleeMonitorThread()
{
self endon( "killanimscript" );
self endon( "end_melee" );
assert( isDefined( self.melee ) );
self waittill( "partner_end_melee" );
if ( isDefined( self.melee.death ) )
{
// partner ended the melee, and we're supposed to die. end the script
if ( isDefined( self.melee.animatedDeath ) || isDefined( self.melee.interruptDeath ) )
{
self kill();
}
else
{
// don't die!
self.melee.death = undefined;
// partner ended before we decided we'd die, we should revive now
if ( Melee_PartnerEndedMeleeMonitorThread_ShouldAnimSurvive() )
{
assert ( animHasNotetrack( self.melee.animName, NOTETRACK_DEATH ) );
self.melee.survive = true;
}
else
{
self notify( "end_melee" );
}
}
}
else
{
// if we're not doing the last part of the animation, end immediately
if ( !isDefined( self.melee.unsyncHappened ) )
self notify( "end_melee" );
}
}
Melee_Unlink()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
if ( !isDefined( self.melee.linked ) )
return;
// Unlink our sync'd target first, because our own unlink will clear this information
if ( isDefined( self.syncedMeleeTarget ) )
self.syncedMeleeTarget Melee_UnlinkInternal();
self Melee_UnlinkInternal();
}
Melee_UnlinkInternal()
{
assert( isDefined( self ) );
self unlink();
self.syncedMeleeTarget = undefined;
if ( !isAlive( self ) )
return;
assert( isDefined( self.melee ) );
assert( isDefined( self.melee.linked ) );
self.melee.linked = undefined;
self animMode( "zonly_physics" );
self orientMode( "face angle", self.angles[1] );
}
Melee_HandleNoteTracks_Unsync()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
self Melee_Unlink();
// let the AIs know that the unsync happened, which changes the interruption behavior
self.melee.unsyncHappened = true;
if ( isDefined( self.melee.partner ) && isDefined( self.melee.partner.melee ) )
self.melee.partner.melee.unsyncHappened = true;
}
Melee_HandleNoteTracks_ShouldDieAfterUnsync()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
if ( animHasNotetrack( self.melee.animName, NOTETRACK_DEATH ) )
{
assert( isDefined( self.melee.surviveAnimName ) );
return false;
}
return isdefined( self.melee.death );
}
Melee_HandleNoteTracks_Death( interruptAnimation )
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
assert( isdefined( self.melee.death ) );
// set whether we should die immediately if melee were to end, or finish playing the animation
if ( isDefined( interruptAnimation ) && interruptAnimation )
self.melee.interruptDeath = true;
else
self.melee.animatedDeath = true;
}
Melee_HandleNoteTracks( note )
{
if ( isSubStr( note, "ps_" ) )
{
alias = GetSubStr( note, 3 );
self playSound( alias );
return;
}
if ( note == NOTETRACK_SYNC )
{
if ( isDefined( self.melee.syncNoteTrackEnt ) )
{
self Melee_AIvsAI_TargetLink( self.melee.syncNoteTrackEnt );
self.melee.syncNoteTrackEnt = undefined;
}
}
else if ( note == NOTETRACK_UNSYNC )
{
self Melee_HandleNoteTracks_Unsync();
// After the targets unsync, the final 'death' sequence is usually played, and we want to handle the pre-corpse sequence ourself.
// We could add a seperate note track too, if this turns out not to be precise enough.
if ( Melee_HandleNoteTracks_ShouldDieAfterUnsync() )
Melee_HandleNoteTracks_Death();
}
else if ( note == NOTETRACK_INTERACT )
{
// From this point on, it's okay to get revived by the animation
self.melee.surviveAnimAllowed = true;
}
else if ( note == NOTETRACK_DEATH )
{
// Check if we got saved. If we did, play the alternate ending
if ( isDefined( self.melee.survive ) )
{
assert( !isdefined( self.melee.death ) );
assert( isDefined( self.melee.surviveAnimName ) );
// Interrupt the waiting loop so that we may start a new one with the survival animation
return note;
}
assert( isdefined( self.melee.death ) );
Melee_HandleNoteTracks_Death();
if ( isDefined( self.melee.animatedDeath ) )
return note; // abort DoNoteTracks so we do our death immediately.
}
else if ( note == NOTETRACK_ATTACHKNIFE )
{
self attach( KNIFE_ATTACK_MODEL, KNIFE_ATTACK_TAG, true );
self.melee.hasKnife = true;
}
else if ( note == NOTETRACK_DETACTKNIFE )
{
self detach( KNIFE_ATTACK_MODEL, KNIFE_ATTACK_TAG, true );
self.melee.hasKnife = undefined;
}
else if ( note == NOTETRACK_STAB )
{
assert( isDefined( self.melee.hasKnife ) );
// Play the knife effect
self playsound( KNIFE_ATTACK_SOUND );
playfxontag( level._effect[ KNIFE_ATTACK_FX_NAME ], self, KNIFE_ATTACK_FX_TAG );
// make sure the target dies after being stabbed if he's still doing the melee
if ( isDefined( self.melee.partner ) && isDefined( self.melee.partner.melee ) )
self.melee.partner Melee_HandleNoteTracks_Death( true );
}
}
Melee_DeathHandler_Regular()
{
self endon( "end_melee" );
self animscripts\shared::DropAllAIWeapons();
return false; //play regular death
}
Melee_DeathHandler_Delayed()
{
self endon( "end_melee" );
self animscripts\shared::DoNoteTracksWithTimeout( "meleeAnim", 10.0 );
self animscripts\shared::DropAllAIWeapons();
self startRagdoll();
return true; //skip regular death
}
Melee_EndScript_CheckDeath()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
if ( !isAlive( self ) && isDefined( self.melee.death ) )
{
if ( isDefined( self.melee.animatedDeath ) )
self.deathFunction = ::Melee_DeathHandler_Delayed;
else
self.deathFunction = ::Melee_DeathHandler_Regular;
}
}
Melee_EndScript_CheckPositionAndMovement()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
if ( !isAlive( self ) )
return;
// make sure we're not marked as moving anymore
if ( isDefined( self.melee.playingMovementAnim ) )
Melee_StopMovement();
// Adjust Ground Position
newOrigin = self getDropToFloorPosition();
if ( isDefined ( newOrigin ) )
self forceTeleport( newOrigin, self.angles );
else
println( "Warning: Melee animation might have ended up in solid for entity #" + self getentnum() );
}
Melee_EndScript_CheckWeapon()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
// melee ended with the knife equipped, remove it
if ( isDefined( self.melee.hasKnife ) )
self detach( KNIFE_ATTACK_MODEL, KNIFE_ATTACK_TAG, true );
// If we dropped our weapon but we didn't die, restore it
if ( isAlive( self ) )
Melee_DroppedWeaponRestore();
}
Melee_EndScript_CheckStateChanges()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
// Restore interruption-based state changes
if ( isDefined( self.melee.wasAllowingPain ) )
{
if ( self.melee.wasAllowingPain )
self enable_pain();
else
self disable_pain();
}
if ( isDefined( self.melee.wasFlashbangImmune ) )
self setFlashbangImmunity( self.melee.wasFlashbangImmune );
}
Melee_EndScript()
{
assert( isDefined( self ) );
assert( isDefined( self.melee ) );
self Melee_Unlink();
self Melee_EndScript_CheckDeath();
self Melee_EndScript_CheckPositionAndMovement();
self Melee_EndScript_CheckWeapon();
self Melee_EndScript_CheckStateChanges();
// End the melee prematurely for the other sync'd ent when someone dies/script ends
if ( isDefined( self.melee.partner ) )
self.melee.partner notify( "partner_end_melee" );
self Melee_ReleaseMutex( self.melee.target );
}
Melee_AcquireMutex( target )
{
assert( isDefined( self ) );
assert( isDefined( target ) );
// Can't acquire when soemone is targeting us for a melee
if ( isDefined( self.melee ) )
return false;
// Can't acquire enemy mutex if he's already in a melee process
if ( isDefined( target.melee ) )
return false;
self.melee = spawnStruct();
target.melee = spawnStruct();
return true;
}
Melee_ReleaseMutex( target )
{
assert( isDefined( self ) );
self.melee = undefined;
if ( isDefined( target ) )
target.melee = undefined;
}