1159 lines
28 KiB
Plaintext
1159 lines
28 KiB
Plaintext
#include animscripts\SetPoseMovement;
|
|
#include animscripts\combat_utility;
|
|
#include animscripts\utility;
|
|
#include animscripts\shared;
|
|
#include animscripts\melee;
|
|
#include common_scripts\utility;
|
|
#include maps\_utility;
|
|
|
|
#using_animtree( "generic_human" );
|
|
|
|
main()
|
|
{
|
|
self endon( "killanimscript" );
|
|
|
|
[[ self.exception[ "move" ] ]]();
|
|
|
|
moveInit();
|
|
getUpIfProne();
|
|
animscripts\utility::initialize( "move" );
|
|
|
|
wasInCover = self wasPreviouslyInCover();
|
|
|
|
if ( wasInCover && isdefined( self.shuffleMove ) )
|
|
{
|
|
moveCoverToCover();
|
|
moveCoverToCoverFinish();
|
|
}
|
|
else if ( IsDefined( self.battleChatter ) && self.battleChatter )
|
|
{
|
|
self moveStartBattleChatter( wasInCover );
|
|
self animscripts\battlechatter::playBattleChatter();
|
|
}
|
|
|
|
self thread stairsCheck();
|
|
self thread pathChangeCheck();
|
|
self thread animDodgeObstacle();
|
|
|
|
self animscripts\cover_arrival::startMoveTransition();
|
|
self.doingReacquireStep = undefined;
|
|
self.ignorePathChange = undefined;
|
|
|
|
self thread startThreadsToRunWhileMoving();
|
|
|
|
self thread animscripts\cover_arrival::setupApproachNode( true );
|
|
|
|
self.shoot_while_moving_thread = undefined;
|
|
self.aim_while_moving_thread = undefined;
|
|
|
|
/#
|
|
assert( !isdefined( self.trackLoopThread ) );
|
|
self.trackLoopThread = undefined;
|
|
#/
|
|
|
|
self.runNGun = undefined;
|
|
|
|
MoveMainLoop( true );
|
|
}
|
|
|
|
|
|
// called by code on ending this script
|
|
end_script()
|
|
{
|
|
if ( isdefined( self.oldGrenadeWeapon ) )
|
|
{
|
|
self.grenadeWeapon = self.oldGrenadeWeapon;
|
|
self.oldGrenadeWeapon = undefined;
|
|
}
|
|
|
|
self.teamFlashbangImmunity = undefined;
|
|
self.minInDoorTime = undefined;
|
|
self.ignorePathChange = undefined;
|
|
self.shuffleMove = undefined;
|
|
self.shuffleNode = undefined;
|
|
self.runNGun = undefined;
|
|
self.reactingToBullet = undefined;
|
|
self.requestReactToBullet = undefined;
|
|
|
|
self.currentDodgeAnim = undefined;
|
|
self.moveLoopOverrideFunc = undefined;
|
|
}
|
|
|
|
|
|
moveInit()
|
|
{
|
|
self.reactingToBullet = undefined;
|
|
self.requestReactToBullet = undefined;
|
|
self.update_move_anim_type = undefined;
|
|
self.update_move_front_bias = undefined;
|
|
self.runNGunWeight = 0;
|
|
self.arrivalStartDist = undefined;
|
|
}
|
|
|
|
getUpIfProne()
|
|
{
|
|
if ( self.a.pose == "prone" )
|
|
{
|
|
newPose = self animscripts\utility::choosePose( "stand" );
|
|
|
|
if ( newPose != "prone" )
|
|
{
|
|
self orientMode( "face current" );
|
|
self animMode( "zonly_physics", false );
|
|
rate = 1;
|
|
if ( isdefined( self.grenade ) )
|
|
rate = 2;
|
|
self animscripts\cover_prone::proneTo( newPose, rate );
|
|
self animMode( "none", false );
|
|
self orientMode( "face default" );
|
|
}
|
|
}
|
|
}
|
|
|
|
wasPreviouslyInCover()
|
|
{
|
|
switch( self.prevScript )
|
|
{
|
|
case "cover_crouch":
|
|
case "cover_left":
|
|
case "cover_prone":
|
|
case "cover_right":
|
|
case "cover_stand":
|
|
case "concealment_crouch":
|
|
case "concealment_prone":
|
|
case "concealment_stand":
|
|
case "cover_wide_left":
|
|
case "cover_wide_right":
|
|
case "hide":
|
|
case "turret":
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
moveStartBattleChatter( wasInCover )
|
|
{
|
|
if ( self.moveMode == "run" )
|
|
{
|
|
// SRS 10/30/08: removed a bunch of unnecessary logic here
|
|
self animscripts\battleChatter_ai::evaluateMoveEvent( wasInCover );
|
|
}
|
|
}
|
|
|
|
MoveMainLoop( doWalkCheck )
|
|
{
|
|
MoveMainLoopInternal( doWalkCheck );
|
|
self notify( "abort_reload" ); // in case a reload was going and MoveMainLoopInternal hit an endon
|
|
}
|
|
|
|
ChangeMoveMode( moveMode )
|
|
{
|
|
if ( moveMode != self.prevMoveMode )
|
|
{
|
|
if ( isdefined( self.customMoveAnimSet ) && isdefined( self.customMoveAnimSet[ moveMode ] ) )
|
|
{
|
|
self.a.moveAnimSet = self.customMoveAnimSet[ moveMode ];
|
|
}
|
|
else
|
|
{
|
|
self.a.moveAnimSet = anim.animsets.move[ moveMode ];
|
|
|
|
if ( ( self.combatMode == "ambush" || self.combatMode == "ambush_nodes_only" ) &&
|
|
( isdefined( self.pathGoalPos ) && distanceSquared( self.origin, self.pathGoalPos ) > squared( 100 ) ) )
|
|
{
|
|
self.sideStepRate = 1;
|
|
animscripts\animset::set_ambush_sidestep_anims();
|
|
}
|
|
else
|
|
{
|
|
self.sideStepRate = 1.35;
|
|
}
|
|
}
|
|
|
|
self.prevMoveMode = moveMode;
|
|
}
|
|
}
|
|
|
|
MoveMainLoopInternal( doWalkCheck )
|
|
{
|
|
self endon( "killanimscript" );
|
|
self endon( "move_interrupt" );
|
|
|
|
prevLoopTime = self getAnimTime( %walk_and_run_loops );
|
|
self.a.runLoopCount = randomint( 10000 );// integer that is incremented each time we complete a run loop
|
|
|
|
self.prevMoveMode = "none";
|
|
|
|
self.moveLoopCleanupFunc = undefined;
|
|
|
|
// if initial destination is closer than 64 walk to it.
|
|
for ( ;; )
|
|
{
|
|
loopTime = self getAnimTime( %walk_and_run_loops );
|
|
if ( loopTime < prevLoopTime )
|
|
self.a.runLoopCount++ ;
|
|
prevLoopTime = loopTime;
|
|
|
|
ChangeMoveMode( self.moveMode );
|
|
MoveMainLoopProcess( self.moveMode );
|
|
|
|
if ( isDefined( self.moveLoopCleanupFunc ) )
|
|
{
|
|
self [[self.moveLoopCleanupFunc]]();
|
|
self.moveLoopCleanupFunc = undefined;
|
|
}
|
|
|
|
self notify( "abort_reload" ); // in case a reload was going and MoveMainLoopProcess hit an endon
|
|
}
|
|
}
|
|
|
|
MoveMainLoopProcess( moveMode )
|
|
{
|
|
self endon( "move_loop_restart" );
|
|
|
|
//prof_begin("MoveMainLoop");
|
|
|
|
self animscripts\face::SetIdleFaceDelayed( anim.alertface );
|
|
|
|
if ( isdefined( self.moveLoopOverrideFunc ) )
|
|
{
|
|
self [[ self.moveLoopOverrideFunc ]]();
|
|
}
|
|
else if ( self shouldCQB() )
|
|
{
|
|
self animscripts\cqb::MoveCQB();
|
|
}
|
|
else
|
|
{
|
|
if ( moveMode == "run" )
|
|
{
|
|
self animscripts\run::MoveRun();
|
|
}
|
|
else
|
|
{
|
|
assert( moveMode == "walk" );
|
|
self animscripts\walk::MoveWalk();
|
|
}
|
|
}
|
|
|
|
self.requestReactToBullet = undefined;
|
|
//prof_end("MoveMainLoop");
|
|
}
|
|
|
|
|
|
MayShootWhileMoving()
|
|
{
|
|
if ( self.weapon == "none" )
|
|
return false;
|
|
|
|
weapclass = weaponClass( self.weapon );
|
|
if ( !usingRifleLikeWeapon() )
|
|
return false;
|
|
|
|
if ( self isSniper() )
|
|
{
|
|
if ( !( self isCQBWalking() ) && self.faceMotion )
|
|
return false;
|
|
}
|
|
|
|
if ( isdefined( self.dontShootWhileMoving ) )
|
|
{
|
|
assert( self.dontShootWhileMoving );// true or undefined
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
shootWhileMoving()
|
|
{
|
|
self endon( "killanimscript" );
|
|
|
|
// it's possible for this to be called by CQB while it's already running from run.gsc,
|
|
// even though run.gsc will kill it on the next frame. We can't let it run twice at once.
|
|
self notify( "doing_shootWhileMoving" );
|
|
self endon( "doing_shootWhileMoving" );
|
|
|
|
self.a.array[ "fire" ] = %exposed_shoot_auto_v3;
|
|
|
|
if ( isdefined( self.weapon ) && weapon_pump_action_shotgun() )
|
|
self.a.array[ "single" ] = array( %shotgun_stand_fire_1A, %shotgun_stand_fire_1B );
|
|
else
|
|
self.a.array[ "single" ] = array( %exposed_shoot_semi1 );
|
|
|
|
self.a.array[ "burst2" ] = %exposed_shoot_burst3;
|
|
self.a.array[ "burst3" ] = %exposed_shoot_burst3;
|
|
self.a.array[ "burst4" ] = %exposed_shoot_burst4;
|
|
self.a.array[ "burst5" ] = %exposed_shoot_burst5;
|
|
self.a.array[ "burst6" ] = %exposed_shoot_burst6;
|
|
|
|
self.a.array[ "semi2" ] = %exposed_shoot_semi2;
|
|
self.a.array[ "semi3" ] = %exposed_shoot_semi3;
|
|
self.a.array[ "semi4" ] = %exposed_shoot_semi4;
|
|
self.a.array[ "semi5" ] = %exposed_shoot_semi5;
|
|
|
|
while ( 1 )
|
|
{
|
|
if ( !self.bulletsInClip )
|
|
{
|
|
if ( self isCQBWalkingOrFacingEnemy() )
|
|
{
|
|
self.ammoCheatTime = 0;
|
|
cheatAmmoIfNecessary();
|
|
}
|
|
|
|
if ( !self.bulletsInClip )
|
|
{
|
|
wait 0.5;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
self shootUntilShootBehaviorChange();
|
|
// can't clear %exposed_modern because there are transition animations within it that we might play when going to prone
|
|
self clearAnim( %exposed_aiming, 0.2 );
|
|
}
|
|
}
|
|
|
|
|
|
startThreadsToRunWhileMoving()
|
|
{
|
|
self endon( "killanimscript" );
|
|
|
|
// wait a frame so MoveMainLoop can start. Otherwise one of the following threads could unsuccesfully try to interrupt movement before it starts
|
|
wait 0.05;
|
|
|
|
self thread bulletWhizbyCheck_whileMoving();
|
|
self thread meleeAttackCheck_whileMoving();
|
|
self thread animscripts\door::inDoorCqbToggleCheck();
|
|
self thread animscripts\door::doorEnterExitCheck();
|
|
}
|
|
|
|
stairsCheck()
|
|
{
|
|
self endon( "killanimscript" );
|
|
|
|
self.prevStairsState = self.stairsState;
|
|
|
|
while ( 1 )
|
|
{
|
|
wait .05;
|
|
if ( self.prevStairsState != self.stairsState )
|
|
{
|
|
// don't interrupt path change animation if getting off stairs to flat ground
|
|
if ( !isdefined( self.ignorePathChange ) || self.stairsState != "none" )
|
|
self notify( "move_loop_restart" );
|
|
}
|
|
|
|
self.prevStairsState = self.stairsState;
|
|
}
|
|
}
|
|
|
|
|
|
restartMoveLoop( skipMoveTransition )
|
|
{
|
|
self endon( "killanimscript" );
|
|
|
|
if ( !skipMoveTransition )
|
|
animscripts\cover_arrival::startMoveTransition();
|
|
|
|
self.ignorePathChange = undefined;
|
|
|
|
self clearanim( %root, 0.1 );
|
|
self OrientMode( "face default" );
|
|
self animMode( "none", false );
|
|
|
|
self.requestArrivalNotify = true;
|
|
MoveMainLoop( !skipMoveTransition );
|
|
}
|
|
|
|
|
|
pathChangeCheck()
|
|
{
|
|
self endon( "killanimscript" );
|
|
self endon( "move_interrupt" );
|
|
|
|
self.ignorePathChange = true; // this will be turned on / off in other threads at appropriate times
|
|
|
|
while ( 1 )
|
|
{
|
|
// no other thread should end on "path_changed"
|
|
self waittill( "path_changed", doingReacquire, newDir );
|
|
|
|
// no need to check for doingReacquire since faceMotion should be a good check
|
|
|
|
assert( !isdefined( self.ignorePathChange ) || self.ignorePathChange ); // should be true or undefined
|
|
|
|
if ( isdefined( self.ignorePathChange ) || isdefined( self.noTurnAnims ) )
|
|
continue;
|
|
|
|
if ( !self.faceMotion || abs( self getMotionAngle() ) > 15 )
|
|
continue;
|
|
|
|
if ( self.a.movement != "run" && self.a.movement != "walk" )
|
|
continue;
|
|
|
|
if ( self.a.pose != "stand" )
|
|
continue;
|
|
|
|
self notify( "stop_move_anim_update" );
|
|
self.update_move_anim_type = undefined;
|
|
|
|
angleDiff = AngleClamp180( self.angles[ 1 ] - vectortoyaw( newDir ) );
|
|
|
|
turnAnim = pathChange_getTurnAnim( angleDiff );
|
|
|
|
if ( isdefined( turnAnim ) )
|
|
{
|
|
self.turnAnim = turnAnim;
|
|
self.turnTime = getTime();
|
|
self.moveLoopOverrideFunc = ::pathChange_doTurnAnim;
|
|
|
|
self notify( "move_loop_restart" );
|
|
self animscripts\run::endFaceEnemyAimTracking();
|
|
}
|
|
}
|
|
}
|
|
|
|
pathChange_getTurnAnim( angleDiff )
|
|
{
|
|
if ( isdefined( self.pathTurnAnimOverrideFunc ) )
|
|
return [[ self.pathTurnAnimOverrideFunc ]]( angleDiff );
|
|
|
|
turnAnim = undefined;
|
|
secondTurnAnim = undefined;
|
|
|
|
if ( self shouldCQB() || self.movemode == "walk" )
|
|
animArray = anim.cqbTurnAnims;
|
|
else
|
|
animArray = anim.runTurnAnims;
|
|
|
|
if ( angleDiff < -30 )
|
|
{
|
|
if ( angleDiff > -60 ) // bias for 45 turns
|
|
{
|
|
turnAnim = animArray[ "L45" ];
|
|
|
|
// awkward pivot turn after doing animation
|
|
//if ( angleDiff < -45 )
|
|
// secondTurnAnim = animArray[ "L90" ];
|
|
}
|
|
else if ( angleDiff > -112.5 )
|
|
{
|
|
turnAnim = animArray[ "L90" ];
|
|
if ( angleDiff > -90 )
|
|
secondTurnAnim = animArray[ "L45" ];
|
|
else
|
|
secondTurnAnim = animArray[ "L135" ];
|
|
}
|
|
else if ( angleDiff > -157.5 )
|
|
{
|
|
turnAnim = animArray[ "L135" ];
|
|
if ( angleDiff > -135 )
|
|
secondTurnAnim = animArray[ "L90" ];
|
|
else
|
|
secondTurnAnim = animArray[ "180" ];
|
|
}
|
|
else
|
|
{
|
|
turnAnim = animArray[ "180" ];
|
|
secondTurnAnim = animArray[ "L135" ];
|
|
}
|
|
}
|
|
else if ( angleDiff > 30 )
|
|
{
|
|
if ( angleDiff < 60 )
|
|
{
|
|
turnAnim = animArray[ "R45" ];
|
|
|
|
// awkward pivot turn after doing animation
|
|
//if ( angleDiff > 45 )
|
|
// secondTurnAnim = animArray[ "R90" ];
|
|
}
|
|
else if ( angleDiff < 112.5 )
|
|
{
|
|
turnAnim = animArray[ "R90" ];
|
|
if ( angleDiff < 90 )
|
|
secondTurnAnim = animArray[ "R45" ];
|
|
else
|
|
secondTurnAnim = animArray[ "R135" ];
|
|
}
|
|
else if ( angleDiff < 157.5 )
|
|
{
|
|
turnAnim = animArray[ "R135" ];
|
|
if ( angleDiff < 135 )
|
|
secondTurnAnim = animArray[ "R90" ];
|
|
else
|
|
secondTurnAnim = animArray[ "180" ];
|
|
}
|
|
else
|
|
{
|
|
turnAnim = animArray[ "180" ];
|
|
secondTurnAnim = animArray[ "R135" ];
|
|
}
|
|
}
|
|
|
|
if ( isdefined( turnAnim ) )
|
|
{
|
|
if ( pathChange_canDoTurnAnim( turnAnim ) )
|
|
return turnAnim;
|
|
}
|
|
|
|
if ( isdefined( secondTurnAnim ) )
|
|
{
|
|
if ( pathChange_canDoTurnAnim( secondTurnAnim ) )
|
|
return secondTurnAnim;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
pathChange_canDoTurnAnim( turnAnim )
|
|
{
|
|
if ( !isdefined( self.pathgoalpos ) )
|
|
return false;
|
|
|
|
codeMoveTimes = getNotetrackTimes( turnAnim, "code_move" );
|
|
assert( codeMoveTimes.size == 1 );
|
|
|
|
codeMoveTime = codeMoveTimes[ 0 ];
|
|
assert( codeMoveTime <= 1 );
|
|
|
|
moveDelta = getMoveDelta( turnAnim, 0, codeMoveTime );
|
|
codeMovePoint = self localToWorldCoords( moveDelta );
|
|
|
|
/#
|
|
animscripts\utility::drawDebugLine( self.origin, codeMovePoint, ( 1, 1, 0 ), 20 );
|
|
animscripts\utility::drawDebugLine( self.origin, self.pathgoalpos, ( 0, 1, 0 ), 20 );
|
|
#/
|
|
|
|
//if ( distanceSquared( self.origin, codeMovePoint ) > distanceSquared( self.origin, self.pathgoalpos ) )
|
|
if ( isdefined( self.arrivalStartDist ) && ( squared( self.arrivalStartDist ) > distanceSquared( self.pathgoalpos, codeMovePoint ) ) )
|
|
return false;
|
|
|
|
moveDelta = getMoveDelta( turnAnim, 0, 1 );
|
|
endPoint = self localToWorldCoords( moveDelta );
|
|
|
|
endPoint = codeMovePoint + vectornormalize( endPoint - codeMovePoint ) * 20;
|
|
|
|
/# animscripts\utility::drawDebugLine( codeMovePoint, endPoint, ( 1, 1, 0 ), 20 ); #/
|
|
|
|
return self mayMoveFromPointToPoint( codeMovePoint, endPoint, true, true );
|
|
}
|
|
|
|
pathChange_doTurnAnim()
|
|
{
|
|
self endon( "killanimscript" );
|
|
|
|
self.moveLoopOverrideFunc = undefined;
|
|
|
|
turnAnim = self.turnAnim;
|
|
|
|
if ( gettime() > self.turnTime + 50 )
|
|
return; // too late
|
|
|
|
self animMode( "zonly_physics", false );
|
|
self clearanim( %body, 0.1 );
|
|
|
|
self.moveLoopCleanupFunc = ::pathChange_cleanupTurnAnim;
|
|
|
|
self.ignorePathChange = true;
|
|
|
|
blendTime = 0.05;
|
|
if ( isdefined( self.pathTurnAnimBlendTime ) )
|
|
blendTime = isdefined( self.pathTurnAnimBlendTime );
|
|
|
|
self setflaggedanimrestart( "turnAnim", turnAnim, 1, blendTime, self.movePlaybackRate );
|
|
self OrientMode( "face current" );
|
|
|
|
assert( animHasNotetrack( turnAnim, "code_move" ) );
|
|
self animscripts\shared::DoNoteTracks( "turnAnim" ); // until "code_move"
|
|
|
|
self.ignorePathChange = undefined;
|
|
self OrientMode( "face motion" ); // want to face motion, don't do l / r / b anims
|
|
self animmode( "none", false );
|
|
|
|
//assert( animHasNotetrack( turnAnim, "finish" ) );
|
|
self animscripts\shared::DoNoteTracks( "turnAnim" );
|
|
}
|
|
|
|
pathChange_doMoveTransition()
|
|
{
|
|
self.moveLoopOverrideFunc = undefined;
|
|
if ( gettime() > self.turnTime + 50 )
|
|
return; // too late
|
|
|
|
self.moveLoopCleanupFunc = ::pathChange_cleanupTurnAnim;
|
|
|
|
animscripts\cover_arrival::startMoveTransition();
|
|
}
|
|
|
|
pathChange_cleanupTurnAnim()
|
|
{
|
|
self.ignorePathChange = undefined;
|
|
|
|
self OrientMode( "face default" );
|
|
self clearanim( %root, 0.1 );
|
|
self animMode( "none", false );
|
|
}
|
|
|
|
dodgeMoveLoopOverride()
|
|
{
|
|
self pushplayer( true );
|
|
self animMode( "zonly_physics", false );
|
|
self clearanim( %body, 0.2 );
|
|
|
|
self setflaggedanimrestart( "dodgeAnim", self.currentDodgeAnim, 1, 0.2, 1 );
|
|
self animscripts\shared::DoNoteTracks( "dodgeAnim" );
|
|
|
|
self animmode( "none", false );
|
|
self orientMode( "face default" );
|
|
|
|
if ( animHasNotetrack( self.currentDodgeAnim, "code_move" ) )
|
|
self animscripts\shared::DoNoteTracks( "dodgeAnim" ); // return on code_move
|
|
|
|
self clearanim( %civilian_dodge, 0.2 );
|
|
|
|
self pushplayer( false );
|
|
self.currentDodgeAnim = undefined;
|
|
self.moveLoopOverrideFunc = undefined;
|
|
return true;
|
|
}
|
|
|
|
|
|
tryDodgeWithAnim( dodgeAnim, dodgeAnimDelta )
|
|
{
|
|
rightDir = ( self.lookAheadDir[1], -1 * self.lookAheadDir[0], 0 );
|
|
|
|
forward = self.lookAheadDir * dodgeAnimDelta[0];
|
|
right = rightDir * dodgeAnimDelta[1];
|
|
|
|
dodgePos = self.origin + forward - right;
|
|
|
|
self pushPlayer( true );
|
|
if ( self mayMoveToPoint( dodgePos ) )
|
|
{
|
|
self.currentDodgeAnim = dodgeAnim;
|
|
self.moveLoopOverrideFunc = ::dodgeMoveLoopOverride;
|
|
self notify( "move_loop_restart" );
|
|
|
|
/#
|
|
if ( getdvar( "scr_debugdodge" ) == "1" )
|
|
thread debugline( self.origin, dodgePos, ( 0, 1, 0 ), 3 );
|
|
#/
|
|
|
|
return true;
|
|
}
|
|
|
|
/#
|
|
if ( getdvar( "scr_debugdodge" ) == "1" )
|
|
thread debugline( self.origin, dodgePos, ( 0.5, 0.5, 0 ), 3 );
|
|
#/
|
|
|
|
self pushPlayer( false );
|
|
return false;
|
|
}
|
|
|
|
animDodgeObstacle()
|
|
{
|
|
if ( !isdefined( self.dodgeLeftAnim ) || !isdefined( self.dodgeRightAnim ) )
|
|
return;
|
|
|
|
self endon( "killanimscript" );
|
|
self endon( "move_interrupt" );
|
|
|
|
while ( 1 )
|
|
{
|
|
// no other thread should end on "path_changed"
|
|
self waittill( "path_need_dodge", dodgeEnt, dodgeEntPos );
|
|
|
|
if ( self animscripts\utility::IsInCombat() )
|
|
{
|
|
self.noDodgeMove = false;
|
|
return;
|
|
}
|
|
|
|
if ( !isSentient( dodgeEnt ) )
|
|
continue;
|
|
|
|
/#
|
|
if ( getdvar( "scr_debugdodge" ) == "1" )
|
|
{
|
|
thread debugline( dodgeEnt.origin + (0, 0, 10), dodgeEntPos, ( 1, 1, 0 ), 3 );
|
|
thread debugline( self.origin, dodgeEntPos, ( 1, 0, 0 ), 3 );
|
|
}
|
|
#/
|
|
|
|
dirToDodgeEnt = vectorNormalize( dodgeEntPos - self.origin );
|
|
|
|
if ( ( self.lookAheadDir[0] * dirToDodgeEnt[1] ) - ( dirToDodgeEnt[0] * self.lookAheadDir[1] ) > 0 )
|
|
{
|
|
// right first
|
|
if ( !tryDodgeWithAnim( self.dodgeRightAnim, self.dodgeRightAnimOffset ) )
|
|
tryDodgeWithAnim( self.dodgeLeftAnim, self.dodgeLeftAnimOffset );
|
|
}
|
|
else
|
|
{
|
|
// left first
|
|
if ( !tryDodgeWithAnim( self.dodgeLeftAnim, self.dodgeLeftAnimOffset ) )
|
|
tryDodgeWithAnim( self.dodgeRightAnim, self.dodgeRightAnimOffset );
|
|
}
|
|
|
|
if ( isdefined( self.currentDodgeAnim ) )
|
|
wait getanimlength( self.currentDodgeAnim );
|
|
else
|
|
wait 0.1;
|
|
}
|
|
}
|
|
|
|
setDodgeAnims( leftAnim, rightAnim )
|
|
{
|
|
self.noDodgeMove = true; // don't let code path around obstacle to dodge
|
|
//self pushplayer( true );
|
|
|
|
self.dodgeLeftAnim = leftAnim;
|
|
self.dodgeRightAnim = rightAnim;
|
|
|
|
time = 1;
|
|
if ( animHasNoteTrack( leftAnim, "code_move" ) )
|
|
time = getNotetrackTimes( leftAnim, "code_move" )[0];
|
|
|
|
self.dodgeLeftAnimOffset = getMoveDelta( leftAnim, 0, time );
|
|
|
|
time = 1;
|
|
if ( animHasNoteTrack( rightAnim, "code_move" ) )
|
|
time = getNotetrackTimes( rightAnim, "code_move" )[0];
|
|
|
|
self.dodgeRightAnimOffset = getMoveDelta( rightAnim, 0, time );
|
|
|
|
self.interval = 80; // good value for civilian dodge animations
|
|
}
|
|
|
|
clearDodgeAnims()
|
|
{
|
|
self.noDodgeMove = false;
|
|
self.dodgeLeftAnim = undefined;
|
|
self.dodgeRightAnim = undefined;
|
|
self.dodgeLeftAnimOffset = undefined;
|
|
self.dodgeRightAnimOffset = undefined;
|
|
}
|
|
|
|
meleeAttackCheck_whileMoving()
|
|
{
|
|
self endon( "killanimscript" );
|
|
|
|
while ( 1 )
|
|
{
|
|
// Try to melee our enemy if it's another AI
|
|
if ( isDefined( self.enemy ) && ( isAI( self.enemy ) || isdefined( self.meleePlayerWhileMoving ) ) )
|
|
{
|
|
if ( abs( self GetMotionAngle() ) <= 135 ) // only when moving forward or sideways
|
|
animscripts\melee::Melee_TryExecuting();
|
|
}
|
|
|
|
wait 0.1;
|
|
}
|
|
}
|
|
|
|
bulletWhizbyCheck_whileMoving()
|
|
{
|
|
self endon( "killanimscript" );
|
|
|
|
if ( isdefined( self.disableBulletWhizbyReaction ) )
|
|
return;
|
|
|
|
while ( 1 )
|
|
{
|
|
self waittill( "bulletwhizby", shooter );
|
|
|
|
if ( self.moveMode != "run" || !self.faceMotion || self.a.pose != "stand" || isdefined( self.reactingToBullet ) )
|
|
continue;
|
|
|
|
if ( self.stairsState != "none" )
|
|
continue;
|
|
|
|
if ( !isdefined( self.enemy ) && !self.ignoreAll && isDefined( shooter.team ) && isEnemyTeam( self.team, shooter.team ) )
|
|
{
|
|
self.whizbyEnemy = shooter;
|
|
self animcustom( animscripts\reactions::bulletWhizbyReaction ); // this will end move script
|
|
continue;
|
|
}
|
|
|
|
if ( self.lookaheadHitsStairs || self.lookaheadDist < 100 )
|
|
continue;
|
|
|
|
if ( isdefined( self.pathGoalPos ) && distanceSquared( self.origin, self.pathGoalPos ) < 10000 )
|
|
{
|
|
wait 0.2;
|
|
continue;
|
|
}
|
|
|
|
self.requestReactToBullet = gettime();
|
|
self notify( "move_loop_restart" );
|
|
self animscripts\run::endFaceEnemyAimTracking();
|
|
}
|
|
}
|
|
|
|
|
|
get_shuffle_to_corner_start_anim( shuffleLeft, startNode )
|
|
{
|
|
if ( startNode.type == "Cover Left" )
|
|
{
|
|
assert( !shuffleLeft );
|
|
return %CornerCrL_alert_2_shuffle;
|
|
}
|
|
else if ( startNode.type == "Cover Right" )
|
|
{
|
|
assert( shuffleLeft );
|
|
return %CornerCrR_alert_2_shuffle;
|
|
}
|
|
else
|
|
{
|
|
if ( shuffleLeft )
|
|
return %covercrouch_hide_2_shuffleL;
|
|
else
|
|
return %covercrouch_hide_2_shuffleR;
|
|
}
|
|
}
|
|
|
|
|
|
setup_shuffle_anim_array( shuffleLeft, startNode, endNode )
|
|
{
|
|
anim_array = [];
|
|
|
|
assert( isdefined( startNode ) );
|
|
assert( isdefined( endNode ) );
|
|
|
|
if ( endNode.type == "Cover Left" )
|
|
{
|
|
assert( shuffleLeft );
|
|
anim_array[ "shuffle_start" ] = get_shuffle_to_corner_start_anim( shuffleLeft, startNode );
|
|
anim_array[ "shuffle" ] = %covercrouch_shuffleL;
|
|
anim_array[ "shuffle_end" ] = %CornerCrL_shuffle_2_alert;
|
|
}
|
|
else if ( endNode.type == "Cover Right" )
|
|
{
|
|
assert( !shuffleLeft );
|
|
anim_array[ "shuffle_start" ] = get_shuffle_to_corner_start_anim( shuffleLeft, startNode );
|
|
anim_array[ "shuffle" ] = %covercrouch_shuffleR;
|
|
anim_array[ "shuffle_end" ] = %CornerCrR_shuffle_2_alert;
|
|
}
|
|
else if ( endNode.type == "Cover Stand" && startNode.type == endNode.type )
|
|
{
|
|
if ( shuffleLeft )
|
|
{
|
|
anim_array[ "shuffle_start" ] = %coverstand_hide_2_shuffleL;
|
|
anim_array[ "shuffle" ] = %coverstand_shuffleL;
|
|
anim_array[ "shuffle_end" ] = %coverstand_shuffleL_2_hide;
|
|
}
|
|
else
|
|
{
|
|
anim_array[ "shuffle_start" ] = %coverstand_hide_2_shuffleR;
|
|
anim_array[ "shuffle" ] = %coverstand_shuffleR;
|
|
anim_array[ "shuffle_end" ] = %coverstand_shuffleR_2_hide;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//assert( endNode.type == "Cover Crouch" || endNode.type == "Cover Crouch Window" );
|
|
if ( shuffleLeft )
|
|
{
|
|
anim_array[ "shuffle_start" ] = get_shuffle_to_corner_start_anim( shuffleLeft, startNode );
|
|
anim_array[ "shuffle" ] = %covercrouch_shuffleL;
|
|
|
|
if ( endNode.type == "Cover Stand" )
|
|
anim_array[ "shuffle_end" ] = %coverstand_shuffleL_2_hide;
|
|
else
|
|
anim_array[ "shuffle_end" ] = %covercrouch_shuffleL_2_hide;
|
|
}
|
|
else
|
|
{
|
|
anim_array[ "shuffle_start" ] = get_shuffle_to_corner_start_anim( shuffleLeft, startNode );
|
|
anim_array[ "shuffle" ] = %covercrouch_shuffleR;
|
|
|
|
if ( endNode.type == "Cover Stand" )
|
|
anim_array[ "shuffle_end" ] = %coverstand_shuffleR_2_hide;
|
|
else
|
|
anim_array[ "shuffle_end" ] = %covercrouch_shuffleR_2_hide;
|
|
}
|
|
}
|
|
|
|
self.a.array = anim_array;
|
|
}
|
|
|
|
moveCoverToCover_checkStartPose( startNode, endNode )
|
|
{
|
|
if ( self.a.pose == "stand" && ( endNode.type != "Cover Stand" || startNode.type != "Cover Stand" ) )
|
|
{
|
|
self.a.pose = "crouch";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
moveCoverToCover_checkEndPose( endNode )
|
|
{
|
|
if ( self.a.pose == "crouch" && endNode.type == "Cover Stand" )
|
|
{
|
|
self.a.pose = "stand";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
serverFPS = 20;
|
|
serverSPF = 0.05;
|
|
|
|
moveCoverToCover()
|
|
{
|
|
self endon( "killanimscript" );
|
|
self endon( "goal_changed" );
|
|
|
|
shuffleNode = self.shuffleNode;
|
|
|
|
self.shuffleMove = undefined;
|
|
self.shuffleNode = undefined;
|
|
self.shuffleMoveInterrupted = true;
|
|
|
|
if ( !isdefined( self.prevNode ) )
|
|
return;
|
|
|
|
if ( !isdefined( self.node ) || !isdefined( shuffleNode ) || self.node != shuffleNode )
|
|
return;
|
|
|
|
shuffleNodeType = self.prevNode;
|
|
|
|
node = self.node;
|
|
|
|
moveDir = node.origin - self.origin;
|
|
if ( lengthSquared( moveDir ) < 1 )
|
|
return;
|
|
|
|
moveDir = vectornormalize( moveDir );
|
|
forward = anglestoforward( node.angles );
|
|
|
|
shuffleLeft = ( ( forward[ 0 ] * moveDir[ 1 ] ) - ( forward[ 1 ] * moveDir[ 0 ] ) ) > 0;
|
|
|
|
if ( moveDoorSideToSide( shuffleLeft, shuffleNodeType, node ) )
|
|
return;
|
|
|
|
if ( moveCoverToCover_checkStartPose( shuffleNodeType, node ) )
|
|
blendTime = 0.1;
|
|
else
|
|
blendTime = 0.4;
|
|
|
|
setup_shuffle_anim_array( shuffleLeft, shuffleNodeType, node );
|
|
|
|
self animMode( "zonly_physics", false );
|
|
|
|
self clearanim( %body, blendTime );
|
|
|
|
startAnim = animarray( "shuffle_start" );
|
|
shuffleAnim = animarray( "shuffle" );
|
|
endAnim = animarray( "shuffle_end" );
|
|
|
|
//assertEx( animhasnotetrack( startAnim, "finish" ), "animation doesn't have finish notetrack " + startAnim );
|
|
if ( animhasnotetrack( startAnim, "finish" ) )
|
|
startEndTime = getNotetrackTimes( startAnim, "finish" )[ 0 ];
|
|
else
|
|
startEndTime = 1;
|
|
|
|
startDist = length( getMoveDelta( startAnim, 0, startEndTime ) );
|
|
shuffleDist = length( getMoveDelta( shuffleAnim, 0, 1 ) );
|
|
endDist = length( getMoveDelta( endAnim, 0, 1 ) );
|
|
|
|
remainingDist = distance( self.origin, node.origin );
|
|
|
|
if ( remainingDist > startDist )
|
|
{
|
|
self OrientMode( "face angle", getNodeForwardYaw( shuffleNodeType ) );
|
|
|
|
self setflaggedanimrestart( "shuffle_start", startAnim, 1, blendTime );
|
|
self animscripts\shared::DoNoteTracks( "shuffle_start" );
|
|
self clearAnim( startAnim, 0.2 );
|
|
remainingDist -= startDist;
|
|
|
|
blendTime = 0.2; // reset blend for looping move
|
|
}
|
|
else
|
|
{
|
|
self OrientMode( "face angle", node.angles[1] );
|
|
}
|
|
|
|
playEnd = false;
|
|
if ( remainingDist > endDist )
|
|
{
|
|
playEnd = true;
|
|
remainingDist -= endDist;
|
|
}
|
|
|
|
loopTime = getAnimLength( shuffleAnim );
|
|
playTime = loopTime * ( remainingDist / shuffleDist ) * 0.9;
|
|
playTime = floor( playTime * serverFPS ) * serverSPF;
|
|
|
|
self setflaggedanim( "shuffle", shuffleAnim, 1, blendTime );
|
|
self animscripts\shared::DoNoteTracksForTime( playTime, "shuffle" );
|
|
|
|
// account for loopTime not being exact since loop animation delta isn't uniform over time
|
|
for ( i = 0; i < 2; i++ )
|
|
{
|
|
remainingDist = distance( self.origin, node.origin );
|
|
if ( playEnd )
|
|
remainingDist -= endDist;
|
|
|
|
if ( remainingDist < 4 )
|
|
break;
|
|
|
|
playTime = loopTime * ( remainingDist / shuffleDist ) * 0.9; // don't overshoot
|
|
playTime = floor( playTime * serverFPS ) * serverSPF;
|
|
|
|
if ( playTime < 0.05 )
|
|
break;
|
|
|
|
self animscripts\shared::DoNoteTracksForTime( playTime, "shuffle" );
|
|
}
|
|
|
|
if ( playEnd )
|
|
{
|
|
if ( moveCoverToCover_checkEndPose( node ) )
|
|
blendTime = 0.2;
|
|
else
|
|
blendTime = 0.4;
|
|
|
|
self clearAnim( shuffleAnim, blendTime );
|
|
self setflaggedanim( "shuffle_end", endAnim, 1, blendTime );
|
|
self animscripts\shared::DoNoteTracks( "shuffle_end" );
|
|
|
|
// clear animation in moveCoverToCoverFinish if needed
|
|
}
|
|
|
|
self safeTeleport( node.origin );
|
|
self animMode( "normal" );
|
|
|
|
self.shuffleMoveInterrupted = undefined;
|
|
}
|
|
|
|
|
|
moveCoverToCoverFinish()
|
|
{
|
|
if ( isdefined( self.shuffleMoveInterrupted ) )
|
|
{
|
|
self clearanim( %cover_shuffle, 0.2 );
|
|
|
|
self.shuffleMoveInterrupted = undefined;
|
|
self animmode( "none", false );
|
|
self orientmode( "face default" );
|
|
}
|
|
else
|
|
{
|
|
wait 0.2; // don't clear animation, wait for cover script to take over
|
|
|
|
self clearanim( %cover_shuffle, 0.2 );
|
|
}
|
|
}
|
|
|
|
moveDoorSideToSide( shuffleLeft, startNode, endNode )
|
|
{
|
|
sideToSideAnim = undefined;
|
|
|
|
if ( startNode.type == "Cover Right" && endNode.type == "Cover Left" && !shuffleLeft )
|
|
sideToSideAnim = %corner_standR_Door_R2L;
|
|
else if ( startNode.type == "Cover Left" && endNode.type == "Cover Right" && shuffleLeft )
|
|
sideToSideAnim = %corner_standL_Door_L2R;
|
|
|
|
if ( !isdefined( sideToSideAnim ) )
|
|
return false;
|
|
|
|
self animMode( "zonly_physics", false );
|
|
self orientmode( "face current" );
|
|
|
|
self setflaggedanimrestart( "sideToSide", sideToSideAnim, 1, 0.2 );
|
|
|
|
assert( animHasNoteTrack( sideToSideAnim, "slide_start" ) );
|
|
assert( animHasNoteTrack( sideToSideAnim, "slide_end" ) );
|
|
|
|
self animscripts\shared::DoNoteTracks( "sideToSide", ::handleSideToSideNotetracks );
|
|
|
|
slideStartTime = self getAnimTime( sideToSideAnim );
|
|
slideDir = endNode.origin - startNode.origin;
|
|
slideDir = vectornormalize( ( slideDir[0], slideDir[1], 0 ) );
|
|
|
|
animDelta = getMoveDelta( sideToSideAnim, slideStartTime, 1 );
|
|
remainingVec = endNode.origin - self.origin;
|
|
remainingVec = ( remainingVec[0], remainingVec[1], 0 );
|
|
slideDist = vectordot( remainingVec, slideDir ) - abs( animDelta[1] );
|
|
|
|
if ( slideDist > 2 )
|
|
{
|
|
slideEndTime = getNoteTrackTimes( sideToSideAnim, "slide_end" )[0];
|
|
slideTime = ( slideEndTime - slideStartTime ) * getAnimLength( sideToSideAnim );
|
|
assert( slideTime > 0 );
|
|
|
|
slideFrames = int( ceil( slideTime / 0.05 ) );
|
|
slideIncrement = slideDir * slideDist / slideFrames;
|
|
self thread slideForTime( slideIncrement, slideFrames );
|
|
}
|
|
|
|
self animscripts\shared::DoNoteTracks( "sideToSide" );
|
|
|
|
self safeTeleport( endNode.origin );
|
|
self animMode( "none" );
|
|
self orientmode( "face default" );
|
|
|
|
self.shuffleMoveInterrupted = undefined;
|
|
wait 0.2;
|
|
|
|
return true;
|
|
}
|
|
|
|
handleSideToSideNotetracks( note )
|
|
{
|
|
if ( note == "slide_start" )
|
|
return true;
|
|
}
|
|
|
|
slideForTime( slideIncrement, slideFrames )
|
|
{
|
|
self endon( "killanimscript" );
|
|
self endon( "goal_changed" );
|
|
|
|
while ( slideFrames > 0 )
|
|
{
|
|
self safeTeleport( self.origin + slideIncrement );
|
|
slideFrames--;
|
|
wait 0.05;
|
|
}
|
|
}
|
|
|
|
MoveStandMoveOverride( override_anim, weights )
|
|
{
|
|
self endon( "movemode" );
|
|
self clearanim( %combatrun, 0.6 );
|
|
self setanimknoball( %combatrun, %body, 1, 0.5, self.moveplaybackrate );
|
|
|
|
if ( isdefined( self.requestReactToBullet ) && gettime() - self.requestReactToBullet < 100 && isdefined( self.run_overrideBulletReact ) && randomFloat( 1 ) < self.a.reactToBulletChance )
|
|
{
|
|
animscripts\run::CustomRunningReactToBullets();
|
|
return;
|
|
}
|
|
|
|
if ( isarray( override_anim ) )
|
|
{
|
|
if ( isdefined( self.run_override_weights ) )
|
|
moveAnim = choose_from_weighted_array( override_anim, weights );
|
|
else
|
|
moveAnim = override_anim[ randomint( override_anim.size ) ];
|
|
}
|
|
else
|
|
{
|
|
moveAnim = override_anim;
|
|
}
|
|
|
|
self setflaggedanimknob( "moveanim", moveAnim, 1, 0.2 );
|
|
animscripts\shared::DoNoteTracks( "moveanim" );
|
|
} |