#include animscripts\Utility; #include animscripts\SetPoseMovement; #include animscripts\Combat_utility; #include animscripts\shared; #include animscripts\animset; #include common_scripts\Utility; #using_animtree( "generic_human" ); sqr512 = 512 * 512; sqr285 = 285 * 285; sqr100 = 100 * 100; pistolPullOutDistSq = sqr512 * 0.64; pistolPutBackDistSq = sqr512; main() { if ( isdefined( self.no_ai ) ) return; if ( isdefined( self.onSnowMobile ) ) { animscripts\snowmobile::main(); return; } //prof_begin("combat_init"); self endon( "killanimscript" ); [[ self.exception[ "exposed" ] ]](); animscripts\utility::initialize( "combat" ); self.a.arrivalType = undefined; if ( isdefined( self.node ) && self.node.type == "Ambush" && self nearNode( self.node ) ) self.ambushNode = self.node; /# if ( getdvar( "scr_testgrenadethrows" ) == "on" ) testGrenadeThrowAnimOffsets(); #/ self transitionToCombat(); self do_friendly_fire_reaction(); animscripts\stop::specialIdleLoop(); self setup(); //prof_end("combat_init"); self exposedCombatMainLoop(); self notify( "stop_deciding_how_to_shoot" ); } end_script() { self.ambushNode = undefined; } do_friendly_fire_reaction() { if ( self.team != "allies" ) return; if ( self IsMoveSuppressed() && self.prevScript == "move" && self.a.pose == "stand" && !isdefined( self.disableFriendlyFireReaction ) ) { if ( isdefined( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) < squared( 128 ) ) return; self animmode( "zonly_physics" ); self setFlaggedAnimKnobAllRestart( "react", %surprise_stop_v1, %root, 1, 0.2, self.animplaybackrate ); self animscripts\shared::DoNoteTracks( "react" ); } } transitionToCombat() { if ( isdefined( self.specialIdleAnim ) || isdefined( self.customIdleAnimSet ) ) return; if ( isdefined( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) < 512 * 512 ) return; if ( self.prevScript == "stop" && !self isCQBWalking() && self.a.pose == "stand" ) { self animmode( "zonly_physics" ); self setFlaggedAnimKnobAllRestart( "transition", %casual_stand_idle_trans_out, %root, 1, 0.2, 1.2 * self.animplaybackrate ); self animscripts\shared::DoNoteTracks( "transition" ); } } /# testGrenadeThrowAnimOffsets() { model = getGrenadeModel(); self animmode( "zonly_physics" ); self OrientMode( "face angle", self.angles[ 1 ] ); self.keepClaimedNodeIfValid = true; foreach ( throwAnim in anim.grenadeThrowAnims ) { forward = anglestoforward( self.angles ); right = anglestoright( self.angles ); startpos = self.origin; tag = "TAG_INHAND"; self setFlaggedAnimKnobAllRestart( "grenadetest", throwAnim, %root, 1, 0, 1 ); for ( ;; ) { self waittill( "grenadetest", notetrack ); if ( notetrack == "grenade_left" || notetrack == "grenade_right" ) self attach( model, tag ); if ( notetrack == "grenade_throw" || notetrack == "grenade throw" ) break; assert( notetrack != "end" );// we shouldn't hit "end" until after we've hit "grenade_throw"! if ( notetrack == "end" )// failsafe break; } pos = self getTagOrigin( tag ); baseoffset = pos - startpos; offset = ( vectordot( baseoffset, forward ), -1 * vectordot( baseoffset, right ), baseoffset[ 2 ] ); // check our answer =) endpos = startpos + forward * offset[ 0 ] - right * offset[ 1 ] + ( 0, 0, 1 ) * offset[ 2 ]; thread debugLine( startpos, endpos, ( 1, 1, 1 ), 20 ); println( "addGrenadeThrowAnimOffset( %", throwAnim, ", ", offset, " );" ); self detach( model, tag ); wait 1; } self.keepClaimedNodeIfValid = false; } #/ setup_anim_array() { if ( self.a.pose == "stand" ) { self set_animarray_standing(); } else if ( self.a.pose == "crouch" ) { self set_animarray_crouching(); } else if ( self.a.pose == "prone" ) { self set_animarray_prone(); } else { assertMsg( "Unsupported self.a.pose: " + self.a.pose ); } } // SETUP FUNCTIONS setup() { if ( usingSidearm() && self isStanceAllowed( "stand" ) ) transitionTo( "stand" ); setup_anim_array(); set_aim_and_turn_limits(); self thread stopShortly(); self.previousPitchDelta = 0.0; self clearAnim( %root, .2 ); setupAim( .2 ); self thread aimIdleThread(); self.a.meleeState = "aim"; self delayStandardMelee(); } stopShortly() { self endon( "killanimscript" ); // we want to stop at about the time we blend out of whatever we were just doing. wait .2; self.a.movement = "stop"; } set_aim_and_turn_limits() { //We have a slightly greater pitch reach when stand_exposed self setDefaultAimLimits(); if ( self.a.pose == "stand" ) { self.upAimLimit = 60; self.downAimLimit = -60; } self.turnThreshold = self.defaultTurnThreshold; } setupExposedCombatLoop() { self thread trackShootEntOrPos(); self thread ReacquireWhenNecessary(); self thread animscripts\shoot_behavior::decideWhatAndHowToShoot( "normal" ); self thread watchShootEntVelocity(); self resetGiveUpOnEnemyTime(); if ( isdefined( self.a.magicReloadWhenReachEnemy ) ) { self animscripts\weaponList::RefillClip(); self.a.magicReloadWhenReachEnemy = undefined; } // Hesitate to crouch. Crouching too early can look stupid because we'll tend to stand right back up in a lot of cases. self.a.dontCrouchTime = gettime() + randomintrange( 500, 1500 ); } exposedCombatStopUsingRPGCheck( distSqToShootPos ) { // too close for RPG or out of ammo if ( usingRocketLauncher() && ( distSqToShootPos < sqr512 || self.a.rockets < 1 ) ) { if ( self.a.pose != "stand" && self.a.pose != "crouch" ) transitionTo( "crouch" ); if ( self.a.pose == "stand" ) animscripts\shared::throwDownWeapon( %RPG_stand_throw ); else animscripts\shared::throwDownWeapon( %RPG_crouch_throw ); self clearAnim( %root, 0.2 ); self endFireAndAnimIdleThread(); self setup_anim_array(); self startFireAndAimIdleThread(); return true; } return false; } exposedCombatCheckStance( distSqToShootPos ) { if ( self.a.pose != "stand" && self isStanceAllowed( "stand" ) ) { if ( distSqToShootPos < sqr285 ) { transitionTo( "stand" ); return true; } if ( standIfMakesEnemyVisible() ) return true; } if ( distSqToShootPos > sqr512 && self.a.pose != "crouch" && self isStanceAllowed( "crouch" ) && !usingSidearm() && !isdefined( self.heat ) && gettime() >= self.a.dontCrouchTime && lengthSquared( self.shootEntVelocity ) < sqr100 ) { if ( !isdefined( self.shootPos ) || sightTracePassed( self.origin + ( 0, 0, 36 ), self.shootPos, false, undefined ) ) { transitionTo( "crouch" ); return true; } } return false; } exposedCombatCheckReloadOrUsePistol( distSqToShootPos ) { if ( !usingSidearm() ) { if ( isdefined( self.forceSideArm ) && self.a.pose == "stand" ) { if ( self tryUsingSidearm() ) return true; } if ( isSniper() && distSqToShootPos < pistolPullOutDistSq ) { if ( self tryUsingSidearm() ) return true; } } if ( NeedToReload( 0 ) ) { // TODO: tweak prone exposed reloading to be considered safer // requiring self.weapon == self.primaryweapon because we dont want him to drop his shotgun and then, if wantshotgun = false, decide to pick up his rifle when he's done if ( !usingSidearm() && cointoss() && !usingRocketLauncher() && usingPrimary() && distSqToShootPos < pistolPullOutDistSq && self isStanceAllowed( "stand" ) ) { // we need to be standing to switch weapons if ( self.a.pose != "stand" ) { transitionTo( "stand" ); return true; } if ( self tryUsingSidearm() ) return true; } if ( self exposedReload( 0 ) ) return true; } return false; } exposedCombatCheckPutAwayPistol( distSqToShootPos ) { if ( usingSidearm() && self.a.pose == "stand" && !isdefined( self.forceSideArm ) ) if ( ( distSqToShootPos > pistolPutBackDistSq ) || ( self.combatMode == "ambush_nodes_only" && ( !isdefined( self.enemy ) || !self cansee( self.enemy ) ) ) ) switchToLastWeapon( %pistol_stand_switch ); } exposedCombatPositionAdjust() { if ( isdefined( self.heat ) && self nearClaimNodeAndAngle() ) { assert( isdefined( self.node ) ); self safeTeleport( self.nodeoffsetpos, self.node.angles ); } } exposedCombatNeedToTurn() { if ( needToTurn() ) { predictTime = 0.25; if ( isdefined( self.shootEnt ) && !isSentient( self.shootEnt ) ) predictTime = 1.5; yawToShootEntOrPos = getPredictedAimYawToShootEntOrPos( predictTime );// yaw to where we think our enemy will be in x seconds if ( TurnToFaceRelativeYaw( yawToShootEntOrPos ) ) return true; } return false; } exposedCombatMainLoop() { self endon( "killanimscript" ); self endon( "combat_restart" ); self setupExposedCombatLoop(); self animMode( "zonly_physics", false ); self OrientMode( "face angle", self.angles[ 1 ] ); // // just face current immediately and rely on turning. for ( ;; ) { if ( usingRocketLauncher() ) self.deathFunction = undefined; self IsInCombat();// reset our in - combat state // it is important for this to be *after* the set_animarray calls! if ( WaitForStanceChange() ) continue; tryMelee(); exposedCombatPositionAdjust(); if ( !isdefined( self.shootPos ) ) { assert( !isdefined( self.shootEnt ) ); cantSeeEnemyBehavior(); if ( !isdefined( self.enemy ) ) justWaited = true; continue; } // we can use self.shootPos after this point. assert( isdefined( self.shootPos ) ); self resetGiveUpOnEnemyTime(); distSqToShootPos = lengthsquared( self.origin - self.shootPos ); if ( exposedCombatStopUsingRPGCheck( distSqToShootPos ) ) continue; if ( exposedCombatNeedToTurn() ) continue; if ( considerThrowGrenade() )// TODO: make considerThrowGrenade work with shootPos rather than only self.enemy continue; if ( exposedCombatCheckReloadOrUsePistol( distSqToShootPos ) ) continue; if ( usingRocketLauncher() && self.a.pose != "crouch" && randomFloat( 1 ) > 0.65 ) self.deathFunction = ::rpgDeath; exposedCombatCheckPutAwayPistol( distSqToShootPos ); if ( exposedCombatCheckStance( distSqToShootPos ) ) continue; if ( aimedAtShootEntOrPos() ) { self shootUntilNeedToTurn(); self hideFireShowAimIdle(); continue; } exposedWait(); } } exposedWait() { if ( !isdefined( self.enemy ) || !self cansee( self.enemy ) ) { self endon( "enemy" ); self endon( "shoot_behavior_change" ); wait 0.2 + randomfloat( 0.1 ); self waittill( "do_slow_things" ); } else { wait 0.05; } } standIfMakesEnemyVisible() { assert( self.a.pose != "stand" ); assert( self isStanceAllowed( "stand" ) ); if ( isdefined( self.enemy ) && ( !self cansee( self.enemy ) || !self canShootEnemy() ) && sightTracePassed( self.origin + ( 0, 0, 64 ), self.enemy getShootAtPos(), false, undefined ) ) { self.a.dontCrouchTime = gettime() + 3000; transitionTo( "stand" ); return true; } return false; } needToTurn() { // Old way, slower /* yawToShootEntOrPos = getAimYawToShootEntOrPos(); // yaw to where we think our enemy will be in x seconds return (abs( yawToShootEntOrPos ) > self.turnThreshold); */ // New way point = self.shootPos; if ( !isdefined( point ) ) return false; yaw = self.angles[ 1 ] - VectorToYaw( point - self.origin ); // need to have fudge factor because the gun's origin is different than our origin, // the closer our distance, the more we need to fudge. distsq = distanceSquared( self.origin, point ); if ( distsq < 256 * 256 ) { dist = sqrt( distsq ); if ( dist > 3 ) yaw += asin( -3 / dist ); } return AbsAngleClamp180( yaw ) > self.turnThreshold; } WaitForStanceChange() { curstance = self.a.pose; if ( isdefined( self.a.onback ) ) { wait 0.1; // something else should correct the stance from "back", or this AI will die on its back, so wait. return true; } if ( curstance == "stand" && isdefined( self.heat ) ) return false; if ( !self isStanceAllowed( curstance ) ) { assert( curstance == "stand" || curstance == "crouch" || curstance == "prone" ); otherstance = "crouch"; if ( curstance == "crouch" ) otherstance = "stand"; if ( self isStanceAllowed( otherstance ) ) { if ( curstance == "stand" && usingSidearm() ) return false; transitionTo( otherstance ); return true; } } return false; } cantSeeEnemyBehavior() { //prof_begin("combat_cantSeeEnemyBehavior"); if ( self.a.pose != "stand" && self isStanceAllowed( "stand" ) && standIfMakesEnemyVisible() ) return true; time = gettime(); self.a.dontCrouchTime = time + 1500; if ( isdefined( self.group ) && isdefined( self.group.forward ) ) { relYaw = AngleClamp180( self.angles[ 1 ] - vectorToYaw( self.group.forward ) ); if ( self TurnToFaceRelativeYaw( relYaw ) ) return true; } if ( isdefined( self.node ) && isdefined( anim.isCombatScriptNode[ self.node.type ] ) ) { relYaw = AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] ); if ( self TurnToFaceRelativeYaw( relYaw ) ) return true; } else if ( (isdefined( self.enemy ) && self seeRecently( self.enemy, 2 )) || time > self.a.scriptStartTime + 1200 ) { relYaw = undefined; likelyEnemyDir = self getAnglesToLikelyEnemyPath(); if ( isdefined( likelyEnemyDir ) ) { relYaw = AngleClamp180( self.angles[ 1 ] - likelyEnemyDir[ 1 ] ); } else if ( isdefined( self.node ) ) { relYaw = AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] ); } else if ( isdefined( self.enemy ) ) { likelyEnemyDir = vectorToAngles( self lastKnownPos( self.enemy ) - self.origin ); relYaw = AngleClamp180( self.angles[ 1 ] - likelyEnemyDir[ 1 ] ); } if ( isdefined( relYaw ) && self TurnToFaceRelativeYaw( relYaw ) ) return true; } // the above likely enemy path is more important than node angles else if ( isdefined( self.heat ) && self nearClaimNode() ) { relYaw = AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] ); if ( self TurnToFaceRelativeYaw( relYaw ) ) return true; } if ( considerThrowGrenade() ) return true; givenUpOnEnemy = ( self.a.nextGiveUpOnEnemyTime < time ); threshold = 0; if ( givenUpOnEnemy ) threshold = 0.99999; if ( self exposedReload( threshold ) ) return true; if ( givenUpOnEnemy && usingSidearm() ) { // switch back to main weapon so we can reload it too before another enemy appears switchToLastWeapon( %pistol_stand_switch ); return true; } /*if ( shouldSwapShotgun() ) { self swapShotgun(); return true; }*/ cantSeeEnemyWait(); return true; } cantSeeEnemyWait() { self endon( "shoot_behavior_change" ); wait 0.4 + randomfloat( 0.4 ); self waittill( "do_slow_things" ); } resetGiveUpOnEnemyTime() { self.a.nextGiveUpOnEnemyTime = gettime() + randomintrange( 2000, 4000 ); } TurnToFaceRelativeYaw( faceYaw ) { if ( faceYaw < 0 - self.turnThreshold ) { if ( self.a.pose == "prone" ) { self animscripts\cover_prone::proneTo( "crouch" ); self set_animarray_crouching(); } self doTurn( "left", 0 - faceYaw ); self maps\_gameskill::didSomethingOtherThanShooting(); return true; } if ( faceYaw > self.turnThreshold ) { if ( self.a.pose == "prone" ) { self animscripts\cover_prone::proneTo( "crouch" ); self set_animarray_crouching(); } self doTurn( "right", faceYaw ); self maps\_gameskill::didSomethingOtherThanShooting(); return true; } return false; } watchShootEntVelocity() { self endon( "killanimscript" ); self.shootEntVelocity = ( 0, 0, 0 ); prevshootent = undefined; prevpos = self.origin; interval = .15; while ( 1 ) { if ( isdefined( self.shootEnt ) && isdefined( prevshootent ) && self.shootEnt == prevshootent ) { curpos = self.shootEnt.origin; self.shootEntVelocity = vector_multiply( curpos - prevpos, 1 / interval ); prevpos = curpos; } else { if ( isdefined( self.shootEnt ) ) prevpos = self.shootEnt.origin; else prevpos = self.origin; prevshootent = self.shootEnt; self.shootEntVelocity = ( 0, 0, 0 ); } wait interval; } } shouldSwapShotgun() { return false;// anims aren't set up yet /* if ( self.a.pose != "stand" ) return false; if ( self usingSidearm() ) return false; usingShotgun = isShotgun( self.primaryweapon ); wantShotgun = isdefined( self.wantShotgun ) && self.wantShotgun ); if ( wantShotgun == usingShotgun ) return false; if ( !wantShotgun ) // there is no standing shotgun putaway animation return false; return true;*/ } /*swapShotgun() { assert( self shouldSwapShotgun() ); assert( isdefined( self.wantShotgun ) ); if ( self.wantShotgun ) { self setFlaggedAnimKnobAllRestart( "weapon_swap", %shotgun_stand_pullout, %body, 1, .2, 1 ); self thread DoNoteTracksWithEndon( "weapon_swap" ); self waittill( "weapon_swap", "" ); self waittill( "weapon_swap", "end" ); } else { assert(false); // we don't have a standing shotgun putaway animation } }*/ DoNoteTracksWithEndon( animname ) { self endon( "killanimscript" ); self animscripts\shared::DoNoteTracks( animname ); } // does turntable movement to face the enemy; // should be used sparingly because turn animations look better. faceEnemyImmediately() { self endon( "killanimscript" ); self notify( "facing_enemy_immediately" ); self endon( "facing_enemy_immediately" ); maxYawChange = 5;// degrees per frame while ( 1 ) { yawChange = 0 - GetYawToEnemy(); if ( abs( yawChange ) < 2 ) break; if ( abs( yawChange ) > maxYawChange ) yawChange = maxYawChange * sign( yawChange ); self OrientMode( "face angle", self.angles[ 1 ] + yawChange ); wait .05; } self OrientMode( "face current" ); self notify( "can_stop_turning" ); } isDeltaAllowed( theanim ) { delta = getMoveDelta( theanim, 0, 1 ); endPoint = self localToWorldCoords( delta ); return self isInGoal( endPoint ) && self mayMoveToPoint( endPoint ); } isAnimDeltaInGoal( theanim ) { delta = getMoveDelta( theanim, 0, 1 ); endPoint = self localToWorldCoords( delta ); return self isInGoal( endPoint ); } doTurn( direction, amount ) { knowWhereToShoot = isdefined( self.shootPos ); rate = 1; transTime = 0.2; mustFaceEnemy = ( isdefined( self.enemy ) && !isdefined( self.turnToMatchNode ) && self seeRecently( self.enemy, 2 ) && distanceSquared( self.enemy.origin, self.origin ) < sqr512 ); if ( self.a.scriptStartTime + 500 > gettime() ) { transTime = 0.25;// if it's the first thing we're doing, always blend slowly if ( mustFaceEnemy ) self thread faceEnemyImmediately(); } else if ( mustFaceEnemy ) { urgency = 1.0 - ( distance( self.enemy.origin, self.origin ) / 512 ); rate = 1 + urgency * 1; // ( ensure transTime <= 0.2 / rate ) if ( rate > 2 ) transTime = .05; else if ( rate > 1.3 ) transTime = .1; else transTime = .15; } angle = 0; if ( amount > 157.5 ) angle = 180; else if ( amount > 112.5 ) angle = 135; else if ( amount > 67.5 ) angle = 90; else angle = 45; animname = "turn_" + direction + "_" + angle; turnanim = animarray( animname ); if ( isdefined( self.turnToMatchNode ) ) self animmode( "angle deltas", false ); else if ( isdefined( self.node ) && isdefined( anim.isCombatPathNode[ self.node.type ] ) && distanceSquared( self.origin, self.node.origin ) < 16 * 16 ) self animmode( "angle deltas", false ); else if ( isAnimDeltaInGoal( turnanim ) ) self animMode( "zonly_physics", false ); else self animmode( "angle deltas", false ); self setAnimKnobAll( %exposed_aiming, %body, 1, transTime ); if ( !isdefined( self.turnToMatchNode ) ) self TurningAimingOn( transTime ); self setAnimLimited( %turn, 1, transTime ); if ( isdefined( self.heat ) ) rate = min( 1.0, rate ); // TEMP 1.0, adjust animations first else if ( isdefined( self.turnToMatchNode ) ) rate = max( 1.5, rate ); self setFlaggedAnimKnobLimitedRestart( "turn", turnanim, 1, transTime, rate ); self notify( "turning" ); if ( knowWhereToShoot && !isdefined( self.turnToMatchNode ) && !isdefined( self.heat ) ) self thread shootWhileTurning(); doTurnNotetracks(); self setanimlimited( %turn, 0, .2 ); if ( !isdefined( self.turnToMatchNode ) ) self TurningAimingOff( .2 ); if ( !isdefined( self.turnToMatchNode ) ) { self clearanim( %turn, .2 ); self setanimknob( %exposed_aiming, 1, .2, 1 ); } else { self clearanim( %exposed_modern, .3 ); } // if we didn't actually turn, code prevented us from doing so. // give up and turntable. if ( isdefined( self.turnLastResort ) ) { self.turnLastResort = undefined; self thread faceEnemyImmediately(); } self animMode( "zonly_physics", false ); self notify( "done turning" ); } doTurnNotetracks() { //self endon( "turning_isnt_working" ); self endon( "can_stop_turning" ); //self thread makeSureTurnWorks(); self animscripts\shared::DoNoteTracks( "turn" ); } makeSureTurnWorks() { self endon( "killanimscript" ); self endon( "done turning" ); startAngle = self.angles[ 1 ]; wait .3; if ( self.angles[ 1 ] == startAngle ) { self notify( "turning_isnt_working" ); self.turnLastResort = true; } } TurningAimingOn( transTime ) { self setAnimLimited( animarray( "straight_level" ), 0, transTime ); self setAnim( %add_idle, 0, transTime ); if ( !weapon_pump_action_shotgun() ) self clearAnim( %add_fire, .2 ); } TurningAimingOff( transTime ) { self setAnimLimited( animarray( "straight_level" ), 1, transTime ); self setAnim( %add_idle, 1, transTime ); } shootWhileTurning() { self endon( "killanimscript" ); self endon( "done turning" ); if ( usingRocketLauncher() ) return; shootUntilShootBehaviorChange(); self clearAnim( %add_fire, .2 ); } shootUntilNeedToTurn() { self thread watchForNeedToTurnOrTimeout(); self endon( "need_to_turn" ); self thread keepTryingToMelee(); shootUntilShootBehaviorChange(); self notify( "stop_watching_for_need_to_turn" ); self notify( "stop_trying_to_melee" ); } watchForNeedToTurnOrTimeout() { self endon( "killanimscript" ); self endon( "stop_watching_for_need_to_turn" ); endtime = gettime() + 4000 + randomint( 2000 ); while ( 1 ) { if ( gettime() > endtime || needToTurn() ) { self notify( "need_to_turn" ); break; } wait .1; } } considerThrowGrenade() { if ( !myGrenadeCoolDownElapsed() )// early out for efficiency return false; if ( isdefined( anim.throwGrenadeAtPlayerASAP ) && isAlive( level.player ) ) { if ( tryExposedThrowGrenade( level.player, 200 ) ) return true; } if ( isdefined( self.enemy ) && tryExposedThrowGrenade( self.enemy, self.minExposedGrenadeDist ) ) return true; self.a.nextGrenadeTryTime = gettime() + 500;// don't try this too often return false; } tryExposedThrowGrenade( throwAt, minDist ) { threw = false; if ( isdefined( self.dontEverShoot ) || isdefined( throwAt.dontAttackMe ) ) return false; if ( !isdefined( self.a.array[ "exposed_grenade" ] ) ) return false; throwSpot = throwAt.origin; if ( !self canSee( throwAt ) ) { if ( isdefined( self.enemy ) && throwAt == self.enemy && isdefined( self.shootPos ) ) throwSpot = self.shootPos; } if ( !self canSee( throwAt ) ) minDist = 100; if ( distanceSquared( self.origin, throwSpot ) > minDist * minDist && self.a.pose == self.a.grenadeThrowPose ) { self setActiveGrenadeTimer( throwAt ); if ( !grenadeCoolDownElapsed( throwAt ) ) return false; yaw = GetYawToSpot( throwSpot ); if ( abs( yaw ) < 60 ) { throwAnims = []; foreach( throwAnim in ( self.a.array[ "exposed_grenade" ] ) ) { if ( isDeltaAllowed( throwAnim ) ) throwAnims[ throwAnims.size ] = throwAnim; } if ( throwAnims.size > 0 ) { self setanim( %exposed_aiming, 0, .1 ); self animMode( "zonly_physics" ); setAnimAimWeight( 0, 0 ); threw = TryGrenade( throwAt, throwAnims[ randomint( throwAnims.size ) ] ); self setanim( %exposed_aiming, 1, .1 ); if ( threw ) setAnimAimWeight( 1, .5 );// ease into aiming else setAnimAimWeight( 1, 0 ); } } } if ( threw ) self maps\_gameskill::didSomethingOtherThanShooting(); return threw; } transitionTo( newPose ) { if ( newPose == self.a.pose ) return; // allow using pistol but it might look bad, put a new animation if so // assert( !usingSidearm() ); transAnimName = self.a.pose + "_2_" + newPose; if ( !isdefined( self.a.array ) ) return; transAnim = self.a.array[ transAnimName ]; if ( !isdefined( transAnim ) ) return; self clearanim( %root, .3 ); self endFireAndAnimIdleThread(); if ( newPose == "stand" ) rate = 2;// gotta stand up fast! else rate = 1.5; if ( !animHasNoteTrack( transAnim, "anim_pose = \"" + newPose + "\"" ) ) { println( "error: " + self.a.pose + "_2_" + newPose + " missing notetrack to set pose!" ); } self setFlaggedAnimKnobAllRestart( "trans", transanim, %body, 1, .2, rate ); transTime = getAnimLength( transanim ) / rate; playTime = transTime - 0.3; if ( playTime < 0.2 ) playTime = 0.2; self animscripts\shared::DoNoteTracksForTime( playTime, "trans" ); self.a.pose = newPose; setup_anim_array(); self startFireAndAimIdleThread(); self maps\_gameskill::didSomethingOtherThanShooting(); } keepTryingToMelee() { self endon( "killanimscript" ); self endon( "stop_trying_to_melee" ); self endon( "done turning" ); self endon( "need_to_turn" ); self endon( "shoot_behavior_change" ); while ( 1 ) { wait .2 + randomfloat( .3 ); // this function is running when we're doing something like shooting or reloading. // we only want to melee if we would look really stupid by continuing to do what we're trying to get done. // only melee if our enemy is very close. if ( isdefined( self.enemy ) ) { if ( isPlayer( self.enemy ) ) checkDistSq = 200 * 200; else checkDistSq = 100 * 100; if ( distanceSquared( self.enemy.origin, self.origin ) < checkDistSq ) tryMelee(); } } } tryMelee() { animscripts\melee::Melee_TryExecuting(); // will start a new anim script and stop combat when successful } delayStandardMelee() { if ( isdefined( self.noMeleeChargeDelay ) ) return; if ( isPlayer( self.enemy ) ) return; // give the AI a chance to charge the player if he forced him out of cover //if ( isPlayer( self.enemy ) && isDefined( self.meleeCoverChargeGraceEndTime ) && (self.meleeCoverChargeGraceEndTime > getTime()) ) // return; animscripts\melee::Melee_Standard_DelayStandardCharge( self.enemy ); } exposedReload( threshold ) { if ( NeedToReload( threshold ) ) { self.a.exposedReloading = true; self endFireAndAnimIdleThread(); reloadAnim = undefined; if ( isdefined( self.specialReloadAnimFunc ) ) { reloadAnim = self [[ self.specialReloadAnimFunc ]](); self.keepClaimedNode = true; } else { reloadAnim = animArrayPickRandom( "reload" ); if ( self.a.pose == "stand" && animArrayAnyExist( "reload_crouchhide" ) && cointoss() ) reloadAnim = animArrayPickRandom( "reload_crouchhide" ); } self thread keepTryingToMelee(); self.finishedReload = false; // pistol reload looks weird pointing at fixed current angle if ( weaponClass( self.weapon ) == "pistol" ) self orientmode( "face default" ); self doReloadAnim( reloadAnim, threshold > .05 );// this will return at the time when we should start aiming self notify( "abort_reload" );// make sure threads that doReloadAnim() started finish self orientmode( "face current" ); if ( self.finishedReload ) self animscripts\weaponList::RefillClip(); self clearanim( %reload, .2 ); self.keepClaimedNode = false; self notify( "stop_trying_to_melee" ); self.a.exposedReloading = false; self maps\_gameskill::didSomethingOtherThanShooting(); self startFireAndAimIdleThread(); return true; } return false; } doReloadAnim( reloadAnim, stopWhenCanShoot ) { self endon( "abort_reload" ); if ( stopWhenCanShoot ) self thread abortReloadWhenCanShoot(); animRate = 1; if ( !self usingSidearm() && !isShotgun( self.weapon ) && isdefined( self.enemy ) && self canSee( self.enemy ) && distanceSquared( self.enemy.origin, self.origin ) < 1024*1024 ) animRate = 1.2; flagName = "reload_" + getUniqueFlagNameIndex(); self clearanim( %root, 0.2 ); self setflaggedanimrestart( flagName, reloadAnim, 1, .2, animRate ); self thread notifyOnStartAim( "abort_reload", flagName ); self endon( "start_aim" ); self animscripts\shared::DoNoteTracks( flagName ); self.finishedReload = true; } abortReloadWhenCanShoot() { self endon( "abort_reload" ); self endon( "killanimscript" ); while ( 1 ) { if ( isdefined( self.shootEnt ) && self canSee( self.shootEnt ) ) break; wait .05; } self notify( "abort_reload" ); } notifyOnStartAim( endonStr, flagName ) { self endon( endonStr ); self waittillmatch( flagName, "start_aim" ); self.finishedReload = true; self notify( "start_aim" ); } finishNoteTracks( animname ) { self endon( "killanimscript" ); animscripts\shared::DoNoteTracks( animname ); } drop_turret() { maps\_mgturret::dropTurret(); // level.theturret = turret; // throwVel = 75 + randomInt(50); // self animscripts\shared::PutGunInHand("right"); self animscripts\weaponList::RefillClip(); self.a.needsToRechamber = 0; self notify( "dropped_gun" ); maps\_mgturret::restoreDefaults(); } exception_exposed_mg42_portable() { drop_turret(); } tryUsingSidearm() { // temp fix! we run out of bones on a particular friendly model with a shotgun who tries to pull out his pistol. if ( isdefined( self.secondaryWeapon ) && isShotgun( self.secondaryweapon ) ) return false; if ( isdefined( self.no_pistol_switch ) ) return false; // TEMP no pistol crouch pullout yet self.a.pose = "stand"; switchToSidearm( %pistol_stand_pullout ); return true; } switchToSidearm( swapAnim ) { self endon( "killanimscript" ); assert( self.sidearm != "" ); self thread putGunBackInHandOnKillAnimScript(); self endFireAndAnimIdleThread(); self.swapAnim = swapAnim; self setFlaggedAnimKnobAllRestart( "weapon swap", swapAnim, %body, 1, .2, fasterAnimSpeed() ); self DoNoteTracksPostCallbackWithEndon( "weapon swap", ::handlePickup, "end_weapon_swap" ); self clearAnim( self.swapAnim, 0.2 ); self notify( "facing_enemy_immediately" ); // notify for level script self notify( "switched_to_sidearm" ); self maps\_gameskill::didSomethingOtherThanShooting(); } DoNoteTracksPostCallbackWithEndon( flagName, interceptFunction, endonMsg ) { self endon( endonMsg ); self animscripts\shared::DoNoteTracksPostCallback( flagName, interceptFunction ); } faceEnemyDelay( delay ) { self endon( "killanimscript" ); wait delay; self faceEnemyImmediately(); } handlePickup( notetrack ) { if ( notetrack == "pistol_pickup" ) { self clearAnim( animarray( "straight_level" ), 0 ); self set_animarray_standing(); self thread faceEnemyDelay( 0.25 ); } else if ( notetrack == "start_aim" ) { startFireAndAimIdleThread(); if ( self needToTurn() ) self notify( "end_weapon_swap" ); } } // %pistol_stand_switch switchToLastWeapon( swapAnim, cleanUp ) { self endon( "killanimscript" ); assertex( self.lastWeapon != getAISidearmWeapon() || self.lastWeapon == "none", "AI \"" + self.classname + "\" using sidearm trying to switch back to sidearm. lastweapon = \"" + self.lastWeapon + "\", primaryweapon = \"" + self.primaryweapon + "\"" ); assertex( self.lastWeapon == getAIPrimaryWeapon() || self.lastWeapon == getAISecondaryWeapon() ); self endFireAndAnimIdleThread(); self.swapAnim = swapAnim; self setFlaggedAnimKnobAllRestart( "weapon swap", swapAnim, %body, 1, .1, 1 ); if ( isdefined( cleanUp ) ) self DoNoteTracksPostCallbackWithEndon( "weapon swap", ::handleCleanUpPutaway, "end_weapon_swap" ); else self DoNoteTracksPostCallbackWithEndon( "weapon swap", ::handlePutaway, "end_weapon_swap" ); self clearanim( self.swapAnim, 0.2 ); self notify( "switched_to_lastweapon" ); self maps\_gameskill::didSomethingOtherThanShooting(); } handlePutaway( notetrack ) { if ( notetrack == "pistol_putaway" ) { self clearAnim( animarray( "straight_level" ), 0 ); self set_animarray_standing(); self thread putGunBackInHandOnKillAnimScript(); } else if ( notetrack == "start_aim" ) { startFireAndAimIdleThread(); if ( self needToTurn() ) self notify( "end_weapon_swap" ); } } handleCleanUpPutaway( notetrack ) { if ( notetrack == "pistol_putaway" ) self thread putGunBackInHandOnKillAnimScript(); else if ( issubstr( notetrack, "anim_gunhand" ) ) self notify( "end_weapon_swap" ); } rpgDeath() { if ( !usingRocketLauncher() || self.bulletsInClip == 0 ) return false; if ( randomFloat( 1 ) > 0.5 ) self SetFlaggedAnimKnobAll( "deathanim", %RPG_stand_death, %root, 1, .05, 1 ); else self SetFlaggedAnimKnobAll( "deathanim", %RPG_stand_death_stagger, %root, 1, .05, 1 ); self animscripts\shared::DoNoteTracks( "deathanim" ); self animscripts\shared::DropAllAIWeapons(); return; } ReacquireWhenNecessary() { self endon( "killanimscript" ); self.a.exposedReloading = false; while ( 1 ) { wait .2; if ( isdefined( self.enemy ) && !self seeRecently( self.enemy, 2 ) ) { if ( self.combatMode == "ambush" || self.combatMode == "ambush_nodes_only" ) continue; } TryExposedReacquire(); } } // this function is meant to be called many times in succession. // each time it tries another option, until eventually it finds something it can do. TryExposedReacquire() { if ( self.fixedNode ) return; //prof_begin( "TryExposedReacquire" ); if ( !isdefined( self.enemy ) ) { self.reacquire_state = 0; return; } // don't do reacquire move when temporarily blocked by teammate if ( gettime() < self.teamMoveWaitTime ) { self.reacquire_state = 0; return; } if ( isdefined( self.prevEnemy ) && self.prevEnemy != self.enemy ) { self.reacquire_state = 0; self.prevEnemy = undefined; return; } self.prevEnemy = self.enemy; if ( self canSee( self.enemy ) && self canShootEnemy() ) { self.reacquire_state = 0; return; } if ( isdefined( self.finishedReload ) && !self.finishedReload ) { self.reacquire_state = 0; return; } // don't do reacquire unless facing enemy dirToEnemy = vectornormalize( self.enemy.origin - self.origin ); forward = anglesToForward( self.angles ); if ( vectordot( dirToEnemy, forward ) < 0.5 ) // (0.5 = cos60) { self.reacquire_state = 0; return; } if ( self.a.exposedReloading && NeedToReload( .25 ) && self.enemy.health > self.enemy.maxhealth * .5 ) { self.reacquire_state = 0; return; } if ( shouldHelpAdvancingTeammate() && self.reacquire_state < 3 ) self.reacquire_state = 3; switch( self.reacquire_state ) { case 0: if ( self ReacquireStep( 32 ) ) return; break; case 1: if ( self ReacquireStep( 64 ) ) { self.reacquire_state = 0; return; } break; case 2: if ( self ReacquireStep( 96 ) ) { self.reacquire_state = 0; return; } break; case 3: if ( tryRunningToEnemy( false ) ) { self.reacquire_state = 0; return; } break; case 4: if ( !( self canSee( self.enemy ) ) || !( self canShootEnemy() ) ) self FlagEnemyUnattackable(); break; default: // don't do anything for a while if ( self.reacquire_state > 15 ) { self.reacquire_state = 0; return; } break; } self.reacquire_state++; }