IW4-Dump-Files/animscripts/shared.gsc

2028 lines
49 KiB
Plaintext
Raw Normal View History

2017-07-08 11:47:21 -07:00
// Shared.gsc - Functions that are shared between animscripts and level scripts.
// Functions in this file can't rely on the animscripts\init function having run, and can't call any
// functions not allowed in level scripts.
#include maps\_utility;
#include animscripts\utility;
#include animscripts\combat_utility;
#include common_scripts\utility;
#using_animtree( "generic_human" );
placeWeaponOn( weapon, position, activeWeapon )
{
//prof_begin( "placeWeaponOn" );
// make sure this it one of our weapons
assert( AIHasWeapon( weapon ) );
self notify( "weapon_position_change" );
curPosition = self.weaponInfo[ weapon ].position;
// make sure we're not out of sync
assert( curPosition == "none" || self.a.weaponPos[ curPosition ] == weapon );
// weapon already in place
if ( position != "none" && self.a.weaponPos[ position ] == weapon )
{
//prof_end( "placeWeaponOn" );
return;
}
self detachAllWeaponModels();
// detach if we're already in a position
if ( curPosition != "none" )
self detachWeapon( weapon );
// nothing more to do
if ( position == "none" )
{
self updateAttachedWeaponModels();
//prof_end( "placeWeaponOn" );
return;
}
if ( self.a.weaponPos[ position ] != "none" )
self detachWeapon( self.a.weaponPos[ position ] );
// to ensure that the correct tags for the active weapon are used, we need to make sure it gets attached first
if ( !isdefined( activeWeapon ) )
activeWeapon = true;
if ( activeWeapon && ( position == "left" || position == "right" ) )
{
self attachWeapon( weapon, position );
self.weapon = weapon;
}
else
{
self attachWeapon( weapon, position );
}
self updateAttachedWeaponModels();
// make sure we don't have a weapon in each hand
//assert( self.a.weaponPos[ "left" ] == "none" || self.a.weaponPos[ "right" ] == "none" );
//prof_end( "placeWeaponOn" );
}
detachWeapon( weapon )
{
self.a.weaponPos[ self.weaponInfo[ weapon ].position ] = "none";
self.weaponInfo[ weapon ].position = "none";
}
attachWeapon( weapon, position )
{
self.weaponInfo[ weapon ].position = position;
self.a.weaponPos[ position ] = weapon;
if ( self.a.weaponPosDropping[ position ] != "none" )
{
// a new weapon has taken the place of the weapon we were dropping, so just stop showing the model for the dropping weapon.
self notify( "end_weapon_drop_" + position );
self.a.weaponPosDropping[ position ] = "none";
}
}
getWeaponForPos( position ) // returns the weapon that should currently be visible in a given location.
{
weapon = self.a.weaponPos[ position ];
if ( weapon == "none" )
return self.a.weaponPosDropping[ position ];
assert( self.a.weaponPosDropping[ position ] == "none" );
return weapon;
}
detachAllWeaponModels()
{
positions = [];
positions[ positions.size ] = "right";
positions[ positions.size ] = "left";
positions[ positions.size ] = "chest";
positions[ positions.size ] = "back";
self laserOff();
foreach ( position in positions )
{
weapon = self getWeaponForPos( position );
if ( weapon == "none" )
continue;
self detach( getWeaponModel( weapon ), getTagForPos( position ) );
}
}
NO_COLLISION = true;
updateAttachedWeaponModels()
{
positions = [];
positions[ positions.size ] = "right";
positions[ positions.size ] = "left";
positions[ positions.size ] = "chest";
positions[ positions.size ] = "back";
foreach ( position in positions )
{
weapon = self getWeaponForPos( position );
if ( weapon == "none" )
continue;
weapon_model = getWeaponModel( weapon );
assertEx( weapon_model != "", "No weapon model for '" + weapon + "', make sure it is precached" );
if ( weapon == "riotshield" )
self attach( weapon_model, getTagForPos( position ) );
else
self attach( weapon_model, getTagForPos( position ), NO_COLLISION );
hideTagList = GetWeaponHideTags( weapon );
for ( i = 0; i < hideTagList.size; i++ )
{
self HidePart( hideTagList[ i ], weapon_model );
}
if ( self.weaponInfo[ weapon ].useClip && !self.weaponInfo[ weapon ].hasClip )
self hidepart( "tag_clip" );
}
self updateLaserStatus();
}
updateLaserStatus()
{
if ( isdefined( self.custom_laser_function ) )
{
[[ self.custom_laser_function ]]();
return;
}
// we have no weapon so there's no laser to turn off or on
if ( self.a.weaponPos[ "right" ] == "none" )
return;
if ( canUseLaser() )
self laserOn();
else
self laserOff();
}
canUseLaser()
{
if ( !self.a.laserOn )
return false;
// shotguns don't have lasers
if ( isShotgun( self.weapon ) )
return false;
return isAlive( self );
}
getTagForPos( position )
{
switch( position )
{
case "chest":
return "tag_weapon_chest";
case "back":
return "tag_stowed_back";
case "left":
return "tag_weapon_left";
case "right":
return "tag_weapon_right";
case "hand":
return "tag_inhand";
default:
assertMsg( "unknown weapon placement position: " + position );
break;
}
}
DropAIWeapon( weapon )
{
if ( !isDefined( weapon ) )
weapon = self.weapon;
if ( weapon == "none" )
return;
if ( isdefined( self.noDrop ) )
return;
self detachAllWeaponModels();
position = self.weaponInfo[ weapon ].position;
if ( self.dropWeapon && position != "none" )
self thread DropWeaponWrapper( weapon, position );
self detachWeapon( weapon );
if ( weapon == self.weapon )
self.weapon = "none";
self updateAttachedWeaponModels();
}
DropAllAIWeapons()
{
if ( isdefined( self.noDrop ) )
return "none";
positions = [];
positions[ positions.size ] = "left";
positions[ positions.size ] = "right";
positions[ positions.size ] = "chest";
positions[ positions.size ] = "back";
self detachAllWeaponModels();
foreach ( position in positions )
{
weapon = self.a.weaponPos[ position ];
if ( weapon == "none" )
continue;
self.weaponInfo[ weapon ].position = "none";
self.a.weaponPos[ position ] = "none";
if ( self.dropWeapon )
self thread DropWeaponWrapper( weapon, position );
}
self.weapon = "none";
self updateAttachedWeaponModels();
}
DropWeaponWrapper( weapon, position )
{
// this must be between calls to detachAllWeaponModels and updateAttachedWeaponModels!
if ( self IsRagdoll() )
return "none"; // too late. our weapon is no longer where it looks like it is.
assert( self.a.weaponPosDropping[ position ] == "none" );
self.a.weaponPosDropping[ position ] = weapon;
actualDroppedWeapon = weapon;
if ( issubstr( tolower( actualDroppedWeapon ), "rpg" ) )
actualDroppedWeapon = "rpg_player";
// unless we're already in the process of dropping more than one weapon,
// this will not actually create the weapon until the next frame, so it can get the tag's velocity.
self DropWeapon( actualDroppedWeapon, position, 0 );
// So we want to wait a bit before detaching the model.
// No waiting before this point!
self endon( "end_weapon_drop_" + position );
wait .1;
if ( !isDefined( self ) )
return;
self detachAllWeaponModels();
self.a.weaponPosDropping[ position ] = "none";
self updateAttachedWeaponModels();
}
/#
showNoteTrack( note )
{
if ( getdebugdvar( "scr_shownotetracks" ) != "on" && getdebugdvarint( "scr_shownotetracks" ) != self getentnum() )
return;
self endon( "death" );
anim.showNotetrackSpeed = 30;// units / sec
anim.showNotetrackDuration = 30;// frames
if ( !isdefined( self.a.shownotetrackoffset ) )
{
thisoffset = 0;
self.a.shownotetrackoffset = 10;
self thread reduceShowNotetrackOffset();
}
else
{
thisoffset = self.a.shownotetrackoffset;
self.a.shownotetrackoffset += 10;
}
duration = anim.showNotetrackDuration + int( 20.0 * thisoffset / anim.showNotetrackSpeed );
color = ( .5, .75, 1 );
if ( note == "end" || note == "finish" )
color = ( .25, .4, .5 );
else if ( note == "undefined" )
color = ( 1, .5, .5 );
for ( i = 0; i < duration; i++ )
{
if ( duration - i <= anim.showNotetrackDuration )
amnt = 1.0 * ( i - ( duration - anim.showNotetrackDuration ) ) / anim.showNotetrackDuration;
else
amnt = 0.0;
time = 1.0 * i / 20;
alpha = 1.0 - amnt * amnt;
pos = self geteye() + ( 0, 0, 20 + anim.showNotetrackSpeed * time - thisoffset );
print3d( pos, note, color, alpha );
wait .05;
}
}
reduceShowNotetrackOffset()
{
self endon( "death" );
while ( self.a.shownotetrackoffset > 0 )
{
wait .05;
self.a.shownotetrackoffset -= anim.showNotetrackSpeed * .05;
}
self.a.shownotetrackoffset = undefined;
}
#/
HandleDogSoundNoteTracks( note )
{
if ( note == "sound_dogstep_run_default" )
{
self playsound( "dogstep_run_default" );
return true;
}
prefix = getsubstr( note, 0, 5 );
if ( prefix != "sound" )
return false;
alias = "anml" + getsubstr( note, 5 );
// if ( growling() && !issubstr( alias, "growl" ) )
// return false;
if ( isalive( self ) )
self thread play_sound_on_tag_endon_death( alias, "tag_eye" );
else
self thread play_sound_in_space( alias, self GetEye() );
return true;
}
growling()
{
return isdefined( self.script_growl );
}
registerNoteTracks()
{
anim.notetracks[ "anim_pose = \"stand\"" ] = ::noteTrackPoseStand;
anim.notetracks[ "anim_pose = \"crouch\"" ] = ::noteTrackPoseCrouch;
anim.notetracks[ "anim_pose = \"prone\"" ] = ::noteTrackPoseProne;
anim.notetracks[ "anim_pose = \"crawl\"" ] = ::noteTrackPoseCrawl;
anim.notetracks[ "anim_pose = \"back\"" ] = ::noteTrackPoseBack;
anim.notetracks[ "anim_movement = \"stop\"" ] = ::noteTrackMovementStop;
anim.notetracks[ "anim_movement = \"walk\"" ] = ::noteTrackMovementWalk;
anim.notetracks[ "anim_movement = \"run\"" ] = ::noteTrackMovementRun;
anim.notetracks[ "anim_aiming = 1" ] = ::noteTrackAlertnessAiming;
anim.notetracks[ "anim_aiming = 0" ] = ::noteTrackAlertnessAlert;
anim.notetracks[ "anim_alertness = causal" ] = ::noteTrackAlertnessCasual;
anim.notetracks[ "anim_alertness = alert" ] = ::noteTrackAlertnessAlert;
anim.notetracks[ "anim_alertness = aiming" ] = ::noteTrackAlertnessAiming;
anim.notetracks[ "gunhand = (gunhand)_left" ] = ::noteTrackGunhand;
anim.notetracks[ "anim_gunhand = \"left\"" ] = ::noteTrackGunhand;
anim.notetracks[ "gunhand = (gunhand)_right" ] = ::noteTrackGunhand;
anim.notetracks[ "anim_gunhand = \"right\"" ] = ::noteTrackGunhand;
anim.notetracks[ "anim_gunhand = \"none\"" ] = ::noteTrackGunhand;
anim.notetracks[ "gun drop" ] = ::noteTrackGunDrop;
anim.notetracks[ "dropgun" ] = ::noteTrackGunDrop;
anim.notetracks[ "gun_2_chest" ] = ::noteTrackGunToChest;
anim.notetracks[ "gun_2_back" ] = ::noteTrackGunToBack;
anim.notetracks[ "pistol_pickup" ] = ::noteTrackPistolPickup;
anim.notetracks[ "pistol_putaway" ] = ::noteTrackPistolPutaway;
anim.notetracks[ "drop clip" ] = ::noteTrackDropClip;
anim.notetracks[ "refill clip" ] = ::noteTrackRefillClip;
anim.notetracks[ "reload done" ] = ::noteTrackRefillClip;
anim.notetracks[ "load_shell" ] = ::noteTrackLoadShell;
anim.notetracks[ "pistol_rechamber" ] = ::noteTrackPistolRechamber;
anim.notetracks[ "gravity on" ] = ::noteTrackGravity;
anim.notetracks[ "gravity off" ] = ::noteTrackGravity;
anim.notetracks[ "footstep_right_large" ] = ::noteTrackFootStep;
anim.notetracks[ "footstep_right_small" ] = ::noteTrackFootStepSmall;
anim.notetracks[ "footstep_left_large" ] = ::noteTrackFootStep;
anim.notetracks[ "footstep_left_small" ] = ::noteTrackFootStepSmall;
anim.notetracks[ "footscrape" ] = ::noteTrackFootScrape;
anim.notetracks[ "land" ] = ::noteTrackLand;
anim.notetracks[ "code_move" ] = ::noteTrackCodeMove;
anim.notetracks[ "face_enemy" ] = ::noteTrackFaceEnemy;
anim.notetracks[ "laser_on" ] = ::noteTrackLaser;
anim.notetracks[ "laser_off" ] = ::noteTrackLaser;
anim.notetracks[ "start_ragdoll" ] = ::noteTrackStartRagdoll;
anim.notetracks[ "fire" ] = ::noteTrackFire;
anim.notetracks[ "fire_spray" ] = ::noteTrackFireSpray;
anim.notetracks[ "bloodpool" ] = animscripts\death::play_blood_pool;
/#
anim.notetracks[ "attach clip left" ] = animscripts\shared::insure_dropping_clip;
anim.notetracks[ "attach clip right" ] = animscripts\shared::insure_dropping_clip;
anim.notetracks[ "detach clip left" ] = animscripts\shared::insure_dropping_clip;
anim.notetracks[ "detach clip right" ] = animscripts\shared::insure_dropping_clip;
#/
if ( isdefined( level._notetrackFX ) )
{
keys = getArrayKeys( level._notetrackFX );
foreach( key in keys )
anim.notetracks[ key ] = ::customNotetrackFX;
}
}
noteTrackFire( note, flagName )
{
if ( isdefined( anim.fire_notetrack_functions[ self.script ] ) )
thread [[ anim.fire_notetrack_functions[ self.script ] ]]();
else
thread [[ animscripts\shared::shootNotetrack ]]();
}
noteTrackLaser( note, flagName )
{
if ( isSubStr( note, "on" ) )
self.a.laserOn = true;
else
self.a.laserOn = false;
self animscripts\shared::updateLaserStatus();
}
noteTrackStopAnim( note, flagName )
{
}
unlinkNextFrame()
{
// by waiting a couple frames, we let ragdoll inherit our velocity.
wait .1;
if ( isdefined( self ) )
self unlink();
}
noteTrackStartRagdoll( note, flagName )
{
if ( isdefined( self.noragdoll ) )
return; // Nate - hack for armless zakhaev who doesn't do ragdoll
if ( !isdefined( self.dont_unlink_ragdoll ) )
self thread unlinkNextFrame();
self startRagdoll();
/#
if ( isalive( self ) )
println( "^4Warning!! Living guy did ragdoll!" );
#/
}
noteTrackMovementStop( note, flagName )
{
self.a.movement = "stop";
}
noteTrackMovementWalk( note, flagName )
{
self.a.movement = "walk";
}
noteTrackMovementRun( note, flagName )
{
self.a.movement = "run";
}
noteTrackAlertnessAiming( note, flagName )
{
//self.a.alertness = "aiming";
}
noteTrackAlertnessCasual( note, flagName )
{
//self.a.alertness = "casual";
}
noteTrackAlertnessAlert( note, flagName )
{
//self.a.alertness = "alert";
}
stopOnBack()
{
self ExitProneWrapper( 1.0 );// make code stop lerping in the prone orientation to ground
self.a.onback = undefined;
}
setPose( pose )
{
self.a.pose = pose;
if ( isdefined( self.a.onback ) )
stopOnBack();
self notify( "entered_pose" + pose );
}
noteTrackPoseStand( note, flagName )
{
if ( self.a.pose == "prone" )
{
self OrientMode( "face default" ); // We were most likely in "face current" while we were prone.
self ExitProneWrapper( 1.0 );// make code stop lerping in the prone orientation to ground
}
setPose( "stand" );
}
noteTrackPoseCrouch( note, flagName )
{
if ( self.a.pose == "prone" )
{
self OrientMode( "face default" ); // We were most likely in "face current" while we were prone.
self ExitProneWrapper( 1.0 );// make code stop lerping in the prone orientation to ground
}
setPose( "crouch" );
}
noteTrackPoseProne( note, flagName )
{
if ( !issentient( self ) )
return;
self setProneAnimNodes( -45, 45, %prone_legs_down, %exposed_aiming, %prone_legs_up );
self EnterProneWrapper( 1.0 );// make code start lerping in the prone orientation to ground
setPose( "prone" );
if ( isdefined( self.a.goingToProneAim ) )
self.a.proneAiming = true;
else
self.a.proneAiming = undefined;
}
noteTrackPoseCrawl( note, flagName )
{
if ( !issentient( self ) )
return;
self setProneAnimNodes( -45, 45, %prone_legs_down, %exposed_aiming, %prone_legs_up );
self EnterProneWrapper( 1.0 );// make code start lerping in the prone orientation to ground
setPose( "prone" );
self.a.proneAiming = undefined;
}
noteTrackPoseBack( note, flagName )
{
if ( !issentient( self ) )
return;
setPose( "crouch" );
self.a.onback = true;
self.a.movement = "stop";
self setProneAnimNodes( -90, 90, %prone_legs_down, %exposed_aiming, %prone_legs_up );
self EnterProneWrapper( 1.0 );// make code start lerping in the prone orientation to ground
}
noteTrackGunHand( note, flagName )
{
if ( isSubStr( note, "left" ) )
{
animscripts\shared::placeWeaponOn( self.weapon, "left" );
self notify( "weapon_switch_done" );
}
else if ( isSubStr( note, "right" ) )
{
animscripts\shared::placeWeaponOn( self.weapon, "right" );
self notify( "weapon_switch_done" );
}
else if ( isSubStr( note, "none" ) )
{
animscripts\shared::placeWeaponOn( self.weapon, "none" );
}
}
noteTrackGunDrop( note, flagName )
{
self DropAIWeapon();
self.lastWeapon = self.weapon;
}
noteTrackGunToChest( note, flagName )
{
//assert( !usingSidearm() );
animscripts\shared::placeWeaponOn( self.weapon, "chest" );
}
noteTrackGunToBack( note, flagName )
{
animscripts\shared::placeWeaponOn( self.weapon, "back" );
// TODO: more asserts and elegant handling of weapon switching here
self.weapon = self getPreferredWeapon();
self.bulletsInClip = weaponClipSize( self.weapon );
}
noteTrackPistolPickup( note, flagName )
{
animscripts\shared::placeWeaponOn( self.sidearm, "right" );
self.bulletsInClip = weaponClipSize( self.weapon );
self notify( "weapon_switch_done" );
}
noteTrackPistolPutaway( note, flagName )
{
animscripts\shared::placeWeaponOn( self.weapon, "none" );
// TODO: more asserts and elegant handling of weapon switching here
self.weapon = self getPreferredWeapon();
self.bulletsInClip = weaponClipSize( self.weapon );
}
noteTrackDropClip( note, flagName )
{
self thread handleDropClip( flagName );
}
noteTrackRefillClip( note, flagName )
{
if ( weaponClass( self.weapon ) == "rocketlauncher" )
self showRocket();
self animscripts\weaponList::RefillClip();
self.a.needsToRechamber = 0;
}
noteTrackLoadShell( note, flagName )
{
self playSound( "weap_reload_shotgun_loop_npc" );
}
noteTrackPistolRechamber( note, flagName )
{
self playSound( "weap_reload_pistol_chamber_npc" );
}
noteTrackGravity( note, flagName )
{
if ( isSubStr( note, "on" ) )
self animMode( "gravity" );
else if ( isSubStr( note, "off" ) )
self animMode( "nogravity" );
}
noteTrackFootStep( note, flagName )
{
if ( isSubStr( note, "left" ) )
playFootStep( "J_Ball_LE" );
else
playFootStep( "J_BALL_RI" );
self playSound( "gear_rattle_run" );
}
noteTrackFootStepSmall( note, flagName )
{
if ( isSubStr( note, "left" ) )
playFootStepSmall( "J_Ball_LE" );
else
playFootStepSmall( "J_BALL_RI" );
self playSound( "gear_rattle_run" );
}
customNotetrackFX( note, flagName )
{
assert( isdefined( level._notetrackFX[ note ] ) );
if ( isDefined( self.groundType ) )
groundType = self.groundType;
else
groundType = "dirt";
fxStruct = undefined;
if ( isdefined( level._notetrackFX[ note ][ groundType ] ) )
fxStruct = level._notetrackFX[ note ][ groundType ];
else if ( isdefined( level._notetrackFX[ note ][ "all" ] ) )
fxStruct = level._notetrackFX[ note ][ "all" ];
if ( !isdefined( fxStruct ) )
return;
if ( isAI( self ) )
playFXOnTag( fxStruct.fx, self, fxStruct.tag );
if ( !isdefined( fxStruct.sound_prefix ) && !isdefined( fxStruct.sound_suffix ) )
return;
soundAlias = "" + fxStruct.sound_prefix + groundType + fxStruct.sound_suffix;
self playsound( soundAlias );
}
noteTrackFootScrape( note, flagName )
{
if ( isDefined( self.groundType ) )
groundType = self.groundType;
else
groundType = "dirt";
self playsound( "step_scrape_" + groundType );
}
noteTrackLand( note, flagName )
{
if ( isDefined( self.groundType ) )
groundType = self.groundType;
else
groundType = "dirt";
self playsound( "land_" + groundType );
}
noteTrackCodeMove( note, flagName )
{
return "code_move";
}
noteTrackFaceEnemy( note, flagName )
{
if ( self.script != "reactions" )
{
self orientmode( "face enemy" );
}
else
{
if ( isdefined( self.enemy ) && distanceSquared( self.enemy.origin, self.reactionTargetPos ) < 64 * 64 )
self orientmode( "face enemy" );
else
self orientmode( "face point", self.reactionTargetPos );
}
}
HandleNoteTrack( note, flagName, customFunction )
{
/#
self thread showNoteTrack( note );
#/
if ( isAI( self ) && self.type == "dog" )
if ( HandleDogSoundNoteTracks( note ) )
return;
notetrackFunc = anim.notetracks[ note ];
if ( isDefined( notetrackFunc ) )
{
return [[ notetrackFunc ]]( note, flagName );
}
switch( note )
{
case "end":
case "finish":
case "undefined":
return note;
case "finish early":
if ( isdefined( self.enemy ) )
return note;
break;
case "swish small":
self thread play_sound_in_space( "melee_swing_small", self gettagorigin( "TAG_WEAPON_RIGHT" ) );
break;
case "swish large":
self thread play_sound_in_space( "melee_swing_large", self gettagorigin( "TAG_WEAPON_RIGHT" ) );
break;
case "rechamber":
if ( weapon_pump_action_shotgun() )
self playSound( "weap_reload_shotgun_pump_npc" );
self.a.needsToRechamber = 0;
break;
case "no death":
// does not play a death anim when he dies
self.a.nodeath = true;
break;
case "no pain":
self.allowpain = false;
break;
case "allow pain":
self.allowpain = true;
break;
case "anim_melee = right":
case "anim_melee = \"right\"":
self.a.meleeState = "right";
break;
case "anim_melee = left":
case "anim_melee = \"left\"":
self.a.meleeState = "left";
break;
case "swap taghelmet to tagleft":
if ( isDefined( self.hatModel ) )
{
if ( isdefined( self.helmetSideModel ) )
{
self detach( self.helmetSideModel, "TAG_HELMETSIDE" );
self.helmetSideModel = undefined;
}
self detach( self.hatModel, "" );
self attach( self.hatModel, "TAG_WEAPON_LEFT" );
self.hatModel = undefined;
}
break;
case "stop anim":
anim_stopanimscripted();
return note;
case "break glass":
level notify( "glass_break", self );
break;
case "break_glass":
level notify( "glass_break", self );
break;
default:
if ( isDefined( customFunction ) )
return [[ customFunction ]]( note );
break;
}
}
// DoNoteTracks waits for and responds to standard noteTracks on the animation, returning when it gets an "end" or a "finish"
// For level scripts, a pointer to a custom function should be passed as the second argument, which handles notetracks not
// already handled by the generic function. This call should take the form DoNoteTracks(flagName, ::customFunction);
// The custom function will be called for each notetrack not recognized, and will pass the notetrack name. Note that this
// function could be called multiple times for a single animation.
DoNoteTracks( flagName, customFunction, debugIdentifier )// debugIdentifier isn't even used. we should get rid of it.
{
for ( ;; )
{
self waittill( flagName, note );
if ( !isDefined( note ) )
note = "undefined";
//prof_begin("HandleNoteTrack");
val = self HandleNoteTrack( note, flagName, customFunction );
//prof_end("HandleNoteTrack");
if ( isDefined( val ) )
return val;
}
}
DoNoteTracksIntercept( flagName, interceptFunction, debugIdentifier )// debugIdentifier isn't even used. we should get rid of it.
{
assert( isDefined( interceptFunction ) );
for ( ;; )
{
self waittill( flagName, note );
if ( !isDefined( note ) )
note = "undefined";
intercepted = [[ interceptFunction ]]( note );
if ( isDefined( intercepted ) && intercepted )
continue;
//prof_begin("HandleNoteTrack");
val = self HandleNoteTrack( note, flagName );
//prof_end("HandleNoteTrack");
if ( isDefined( val ) )
return val;
}
}
DoNoteTracksPostCallback( flagName, postFunction )
{
assert( isDefined( postFunction ) );
for ( ;; )
{
self waittill( flagName, note );
if ( !isDefined( note ) )
note = "undefined";
//prof_begin("HandleNoteTrack");
val = self HandleNoteTrack( note, flagName );
//prof_end("HandleNoteTrack");
[[ postFunction ]]( note );
if ( isDefined( val ) )
return val;
}
}
DoNoteTracksForTimeout( flagName, killString, customFunction, debugIdentifier )
{
DoNoteTracks( flagName, customFunction, debugIdentifier );
}
// Don't call this function except as a thread you're going to kill - it lasts forever.
DoNoteTracksForever( flagName, killString, customFunction, debugIdentifier )
{
DoNoteTracksForeverProc( ::DoNoteTracks, flagName, killString, customFunction, debugIdentifier );
}
DoNoteTracksForeverIntercept( flagName, killString, interceptFunction, debugIdentifier )
{
DoNoteTracksForeverProc( ::DoNoteTracksIntercept, flagName, killString, interceptFunction, debugIdentifier );
}
DoNoteTracksForeverProc( notetracksFunc, flagName, killString, customFunction, debugIdentifier )
{
if ( isdefined( killString ) )
self endon( killString );
self endon( "killanimscript" );
if ( !isDefined( debugIdentifier ) )
debugIdentifier = "undefined";
for ( ;; )
{
//prof_begin( "DoNoteTracksForeverProc" );
time = GetTime();
//prof_begin( "notetracksFunc" );
returnedNote = [[ notetracksFunc ]]( flagName, customFunction, debugIdentifier );
//prof_end( "notetracksFunc" );
timetaken = GetTime() - time;
if ( timetaken < 0.05 )
{
time = GetTime();
//prof_begin( "notetracksFunc" );
returnedNote = [[ notetracksFunc ]]( flagName, customFunction, debugIdentifier );
//prof_end( "notetracksFunc" );
timetaken = GetTime() - time;
if ( timetaken < 0.05 )
{
println( GetTime() + " " + debugIdentifier + " animscripts\shared::DoNoteTracksForever is trying to cause an infinite loop on anim " + flagName + ", returned " + returnedNote + "." );
wait( 0.05 - timetaken );
}
}
//(GetTime()+" "+debugIdentifier+" DoNoteTracksForever returned in "+timetaken+" ms.");#/
//prof_end( "DoNoteTracksForeverProc" );
}
}
// Designed for using DoNoteTracks until "end" is reached, or a specified amount of time, whichever happens first
DoNoteTracksWithTimeout( flagName, time, customFunction, debugIdentifier )
{
ent = spawnstruct();
ent thread doNoteTracksForTimeEndNotify( time );
DoNoteTracksForTimeProc( ::DoNoteTracksForTimeout, flagName, customFunction, debugIdentifier, ent );
}
// Designed for using DoNoteTracks on looping animations, so you can wait for a time instead of the "end" parameter
DoNoteTracksForTime( time, flagName, customFunction, debugIdentifier )
{
ent = spawnstruct();
ent thread doNoteTracksForTimeEndNotify( time );
DoNoteTracksForTimeProc( ::DoNoteTracksForever, flagName, customFunction, debugIdentifier, ent );
}
DoNoteTracksForTimeIntercept( time, flagName, interceptFunction, debugIdentifier )
{
ent = spawnstruct();
ent thread doNoteTracksForTimeEndNotify( time );
DoNoteTracksForTimeProc( ::DoNoteTracksForeverIntercept, flagName, interceptFunction, debugIdentifier, ent );
}
DoNoteTracksForTimeProc( doNoteTracksForeverFunc, flagName, customFunction, debugIdentifier, ent )
{
ent endon( "stop_notetracks" );
[[ doNoteTracksForeverFunc ]]( flagName, undefined, customFunction, debugIdentifier );
}
doNoteTracksForTimeEndNotify( time )
{
wait( time );
self notify( "stop_notetracks" );
}
playFootStep( foot )
{
if ( ! isAI( self ) )
{
self playsound( "step_run_dirt" );
return;
}
groundType = undefined;
// gotta record the groundtype in case it goes undefined on us
if ( !isdefined( self.groundtype ) )
{
if ( !isdefined( self.lastGroundtype ) )
{
self playsound( "step_run_dirt" );
return;
}
groundtype = self.lastGroundtype;
}
else
{
groundtype = self.groundtype;
self.lastGroundtype = self.groundType;
}
self playsound( "step_run_" + groundType );
if ( ![[ anim.optionalStepEffectFunction ]]( foot, groundType ) )
playFootStepEffectSmall( foot, groundType );
}
playFootStepSmall( foot )
{
if ( ! isAI( self ) )
{
self playsound( "step_run_dirt" );
return;
}
groundType = undefined;
// gotta record the groundtype in case it goes undefined on us
if ( !isdefined( self.groundtype ) )
{
if ( !isdefined( self.lastGroundtype ) )
{
self playsound( "step_run_dirt" );
return;
}
groundtype = self.lastGroundtype;
}
else
{
groundtype = self.groundtype;
self.lastGroundtype = self.groundType;
}
self playsound( "step_run_" + groundType );
if ( ![[ anim.optionalStepEffectSmallFunction ]]( foot, groundType ) )
playFootStepEffect( foot, groundType );
}
playFootStepEffect( foot, groundType )
{
for ( i = 0;i < anim.optionalStepEffects.size;i++ )
{
if ( groundType != anim.optionalStepEffects[ i ] )
continue;
org = self gettagorigin( foot );
angles = self.angles;
forward = anglestoforward( angles );
back = forward * - 1;
up = anglestoup( angles );
playfx( level._effect[ "step_" + anim.optionalStepEffects[ i ] ], org, up, back );
return true;
}
return false;
}
playFootStepEffectSmall( foot, groundType )
{
for ( i = 0;i < anim.optionalStepEffectsSmall.size;i++ )
{
if ( groundType != anim.optionalStepEffectsSmall[ i ] )
continue;
org = self gettagorigin( foot );
angles = self.angles;
forward = anglestoforward( angles );
back = forward * - 1;
up = anglestoup( angles );
playfx( level._effect[ "step_small_" + anim.optionalStepEffectsSmall[ i ] ], org, up, back );
return true;
}
return false;
}
shootNotetrack()
{
waittillframeend;// this gives a chance for anything else waiting on "fire" to shoot
if ( isdefined( self ) && gettime() > self.a.lastShootTime )
{
self shootEnemyWrapper();
self decrementBulletsInClip();
if ( weaponClass( self.weapon ) == "rocketlauncher" )
self.a.rockets -- ;
}
}
fire_straight()
{
if ( self.a.weaponPos[ "right" ] == "none" )
return;
if ( isdefined( self.dontShootStraight ) )
{
shootNotetrack();
return;
}
weaporig = self gettagorigin( "tag_weapon" );
dir = anglestoforward( self getMuzzleAngle() );
pos = weaporig + vector_multiply( dir, 1000 );
// note, shootwrapper is not called because shootwrapper applies a random spread, and shots
// fired in a scripted sequence need to go perfectly straight so they get the same result each time.
self shoot( 1, pos );
self decrementBulletsInClip();
}
noteTrackFireSpray( note, flagName )
{
if ( !isalive( self ) && self isBadGuy() )
{
if ( isdefined( self.changed_team ) )
return;
self.changed_team = true;
teams[ "axis" ] = "team3";
teams[ "team3" ] = "axis";
assertex( isdefined( teams[ self.team ] ), "no team for " + self.team );
self.team = teams[ self.team ];
}
// TODO: make AI not use anims with this notetrack if they don't have a weapon
if ( !issentient( self ) )
{
// for drones
self notify( "fire" );
// self shoot();
return;
}
if ( self.a.weaponPos[ "right" ] == "none" )
return;
//prof_begin( "noteTrackFireSpray" );
weaporig = self getMuzzlePos();
dir = anglestoforward( self getMuzzleAngle() );
// rambo set sprays at a wider range than other fire_spray anims
ang = 10;
if ( isdefined( self.isRambo ) )
ang = 20;
hitenemy = false;
// check if we're aiming closish to our enemy
if ( isalive( self.enemy ) && issentient( self.enemy ) && self canShootEnemy() )
{
enemydir = vectornormalize( self.enemy geteye() - weaporig );
if ( vectordot( dir, enemydir ) > cos( ang ) )
{
hitenemy = true;
}
}
if ( hitenemy )
{
self shootEnemyWrapper();
}
else
{
dir += ( ( randomfloat( 2 ) - 1 ) * .1, ( randomfloat( 2 ) - 1 ) * .1, ( randomfloat( 2 ) - 1 ) * .1 );
pos = weaporig + vector_multiply( dir, 1000 );
self shootPosWrapper( pos );
}
self decrementBulletsInClip();
//prof_end( "noteTrackFireSpray" );
}
getPredictedAimYawToShootEntOrPos( time )
{
if ( !isdefined( self.shootEnt ) )
{
if ( !isdefined( self.shootPos ) )
return 0;
return getAimYawToPoint( self.shootPos );
}
predictedPos = self.shootEnt.origin + vector_multiply( self.shootEntVelocity, time );
return getAimYawToPoint( predictedPos );
}
getAimYawToShootEntOrPos()
{
// make use of the fact that shootPos = shootEnt getShootAtPos() if shootEnt is defined
if ( !isdefined( self.shootEnt ) )
{
if ( !isdefined( self.shootPos ) )
return 0;
return getAimYawToPoint( self.shootPos );
}
return getAimYawToPoint( self.shootEnt getShootAtPos() );
}
getAimPitchToShootEntOrPos()
{
pitch = getPitchToShootEntOrPos();
if ( self.script == "cover_crouch" && isdefined( self.a.coverMode ) && self.a.coverMode == "lean" )
pitch -= anim.coverCrouchLeanPitch;
return pitch;
}
getPitchToShootEntOrPos()
{
if ( !isdefined( self.shootEnt ) )
{
// make use of the fact that shootPos = shootEnt getShootAtPos() if shootEnt is defined
if ( !isdefined( self.shootPos ) )
return 0;
return animscripts\combat_utility::getPitchToSpot( self.shootPos );
}
return animscripts\combat_utility::getPitchToSpot( self.shootEnt getShootAtPos() );
}
getShootFromPos()
{
if ( isdefined( self.useMuzzleSideOffset ) )
{
muzzlePos = self getMuzzleSideOffsetPos();
return ( muzzlePos[ 0 ], muzzlePos[ 1 ], self getEye()[ 2 ] );
}
return ( self.origin[ 0 ], self.origin[ 1 ], self getEye()[ 2 ] );
}
getAimYawToPoint( point )
{
yaw = GetYawToSpot( point );
// 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.
dist = distance( self.origin, point );
if ( dist > 3 )
{
angleFudge = asin( -3 / dist );
yaw += angleFudge;
}
yaw = AngleClamp180( yaw );
return yaw;
}
trackShootEntOrPos()
{
self endon( "killanimscript" );
self endon( "stop tracking" );
self endon( "melee" );
/#
assert( !isdefined( self.trackLoopThread ) );
self.trackLoopThread = thisthread;
self.trackLoopThreadType = "trackShootEntOrPos";
#/
trackLoop( %aim_2, %aim_4, %aim_6, %aim_8 );
}
// max change in angle in 1 frame
normalDeltaChangePerFrame = 10;
largeDeltaChangePerFrame = 30;
trackLoop( aim2, aim4, aim6, aim8 )
{
assert( isdefined( self.trackLoopThread ) );
prevYawDelta = 0;
prevPitchDelta = 0;
pitchAdd = 0;
yawAdd = 0;
wasOnStairs = false;
angleDeltas = ( 0, 0, 0 );
firstFrame = true;
prevMotionRelativeDir = 0;
quickTurnFrames = 0;
deltaChangePerFrame = normalDeltaChangePerFrame;
if ( self.type == "dog" )
{
doMaxAngleCheck = false;
self.shootEnt = self.enemy;
}
else
{
doMaxAngleCheck = true;
if ( isdefined( self.coverCrouchLean_aimmode ) )
pitchAdd = anim.coverCrouchLeanPitch;
if ( ( self.script == "cover_left" || self.script == "cover_right" ) && isdefined( self.a.cornerMode ) && self.a.cornerMode == "lean" )
yawAdd = self.coverNode.angles[ 1 ] - self.angles[ 1 ];
}
for ( ;; )
{
//prof_begin("trackLoop");
incrAnimAimWeight();
shootFromPos = getShootFromPos();
shootPos = self.shootPos;
if ( isdefined( self.shootEnt ) )
shootPos = self.shootEnt getShootAtPos();
if ( !isdefined( shootPos ) && self shouldCQB() )
shootPos = trackLoop_CQBShootPos( shootFromPos );
if ( self.stairsState == "up" )
{
pitchAdd = -40;
wasOnStairs = true;
}
else if ( self.stairsState == "down" )
{
pitchAdd = 40;
yawAdd = 12;
wasOnStairs = true;
}
else if ( wasOnStairs )
{
pitchAdd = 0;
yawAdd = 0;
wasOnStairs = false;
}
if ( !isdefined( shootPos ) )
angleDeltas = trackLoop_anglesForNoShootPos( shootFromPos, pitchAdd, yawAdd );
else
angleDeltas = trackLoop_getDesiredAngles( ( shootPos - shootFromPos ), pitchAdd, yawAdd );
angleDeltas = trackLoop_clampAngles( angleDeltas[ 0 ], angleDeltas[ 1 ], doMaxAngleCheck );
pitchDelta = angleDeltas[ 0 ];
yawDelta = angleDeltas[ 1 ];
if ( quickTurnFrames > 0 )
{
quickTurnFrames = quickTurnFrames - 1;
deltaChangePerFrame = max( normalDeltaChangePerFrame, deltaChangePerFrame - 5 );
}
else if ( self.relativeDir && self.relativeDir != prevMotionRelativeDir )
{
quickTurnFrames = 2;
deltaChangePerFrame = largeDeltaChangePerFrame;
}
else
{
deltaChangePerFrame = normalDeltaChangePerFrame;
}
deltaChangePerFrameSq = squared( deltaChangePerFrame );
prevMotionRelativeDir = self.relativeDir;
checkDeltaChange = ( self.moveMode != "stop" ) || !firstFrame;
if ( checkDeltaChange )
{
yawDeltaChange = yawDelta - prevYawDelta;
if ( squared( yawDeltaChange ) > deltaChangePerFrameSq )
{
yawDelta = prevYawDelta + clamp( yawDeltaChange, -1 * deltaChangePerFrame, deltaChangePerFrame );
yawDelta = clamp( yawDelta, self.leftAimLimit, self.rightAimLimit );
}
pitchDeltaChange = pitchDelta - prevPitchDelta;
if ( squared( pitchDeltaChange ) > deltaChangePerFrameSq )
{
pitchDelta = prevPitchDelta + clamp( pitchDeltaChange, -1 * deltaChangePerFrame, deltaChangePerFrame );
pitchDelta = clamp( pitchDelta, self.downAimLimit, self.upAimLimit );
}
}
firstFrame = false;
prevYawDelta = yawDelta;
prevPitchDelta = pitchDelta;
trackLoop_setAnimWeights( aim2, aim4, aim6, aim8, pitchDelta, yawDelta );
//prof_end("trackLoop");
wait( 0.05 );
}
}
trackLoop_CQBShootPos( shootFromPos )
{
shootPos = undefined;
selfForward = anglesToForward( self.angles );
if ( isdefined( self.cqb_target ) )
{
shootPos = self.cqb_target getShootAtPos();
if ( vectorDot( vectorNormalize( shootPos - shootFromPos ), selfForward ) < 0.643 )// 0.643 = cos50
shootPos = undefined;
}
if ( !isdefined( shootPos ) && isdefined( self.cqb_point_of_interest ) )
{
shootPos = self.cqb_point_of_interest;
if ( vectorDot( vectorNormalize( shootPos - shootFromPos ), selfForward ) < 0.643 )// 0.643 = cos50
shootPos = undefined;
}
return shootPos;
}
trackLoop_anglesForNoShootPos( shootFromPos, pitchAdd, yawAdd )
{
assert( !isdefined( self.shootEnt ) );
if ( recentlySawEnemy() )
{
shootAtOffset = ( self.enemy getShootAtPos() - self.enemy.origin );
shootAtPos = ( self lastKnownPos( self.enemy ) + shootAtOffset );
return trackLoop_getDesiredAngles( (shootAtPos - shootFromPos), pitchAdd, yawAdd );
}
pitchDelta = 0;
yawDelta = 0;
if ( isdefined( self.node ) && isdefined( anim.isCombatScriptNode[ self.node.type ] ) && distanceSquared( self.origin, self.node.origin ) < 16 )
{
yawDelta = AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] );
}
else
{
likelyEnemyDir = self getAnglesToLikelyEnemyPath();
if ( isdefined( likelyEnemyDir ) )
{
yawDelta = AngleClamp180( self.angles[ 1 ] - likelyEnemyDir[ 1 ] );
pitchDelta = AngleClamp180( 360 - likelyEnemyDir[ 0 ] );
}
}
return( pitchDelta, yawDelta, 0 );
}
trackLoop_getDesiredAngles( vectorToShootPos, pitchAdd, yawAdd )
{
anglesToShootPos = vectorToAngles( vectorToShootPos );
pitchDelta = 360 - anglesToShootPos[ 0 ];
pitchDelta = AngleClamp180( pitchDelta + pitchAdd );
if ( isDefined( self.stepOutYaw ) )
{
yawDelta = self.stepOutYaw - anglesToShootPos[ 1 ];
}
else
{
yawOffset = AngleClamp180( self.desiredAngle - self.angles[ 1 ] ) * 0.5;
yawDelta = yawOffset + self.angles[ 1 ] - anglesToShootPos[ 1 ];
}
yawDelta = AngleClamp180( yawDelta + yawAdd );
return( pitchDelta, yawDelta, 0 );
}
trackLoop_clampAngles( pitchDelta, yawDelta, doMaxAngleCheck )
{
if ( isdefined( self.onSnowMobile ) )
{
if ( yawDelta > self.rightAimLimit || yawDelta < self.leftAimLimit )
yawDelta = 0;
if ( pitchDelta > self.upAimLimit || pitchDelta < self.downAimLimit )
pitchDelta = 0;
}
else if ( doMaxAngleCheck && ( abs( yawDelta ) > anim.maxAngleCheckYawDelta || abs( pitchDelta ) > anim.maxAngleCheckPitchDelta ) )
{
yawDelta = 0;
pitchDelta = 0;
}
else
{
if ( self.gunBlockedByWall )
yawDelta = clamp( yawDelta, -10, 10 );
else
yawDelta = clamp( yawDelta, self.leftAimLimit, self.rightAimLimit );
pitchDelta = clamp( pitchDelta, self.downAimLimit, self.upAimLimit );
}
return( pitchDelta, yawDelta, 0 );
}
aimBlendTime = .1;
trackLoop_setAnimWeights( aim2, aim4, aim6, aim8, pitchDelta, yawDelta )
{
if ( yawDelta > 0 )
{
assert( yawDelta <= self.rightAimLimit );
weight = yawDelta / self.rightAimLimit * self.a.aimweight;
self setAnimLimited( aim4, 0, aimBlendTime, 1, true );
self setAnimLimited( aim6, weight, aimBlendTime, 1, true );
}
else if ( yawDelta < 0 )
{
assert( yawDelta >= self.leftAimLimit );
weight = yawDelta / self.leftAimLimit * self.a.aimweight;
self setAnimLimited( aim6, 0, aimBlendTime, 1, true );
self setAnimLimited( aim4, weight, aimBlendTime, 1, true );
}
if ( pitchDelta > 0 )
{
assert( pitchDelta <= self.upAimLimit );
weight = pitchDelta / self.upAimLimit * self.a.aimweight;
self setAnimLimited( aim2, 0, aimBlendTime, 1, true );
self setAnimLimited( aim8, weight, aimBlendTime, 1, true );
}
else if ( pitchDelta < 0 )
{
assert( pitchDelta >= self.downAimLimit );
weight = pitchDelta / self.downAimLimit * self.a.aimweight;
self setAnimLimited( aim8, 0, aimBlendTime, 1, true );
self setAnimLimited( aim2, weight, aimBlendTime, 1, true );
}
}
//setAnimAimWeight works just like setanimlimited on an imaginary anim node that affects the four aiming directions.
setAnimAimWeight( goalweight, goaltime )
{
if ( !isdefined( goaltime ) || goaltime <= 0 )
{
self.a.aimweight = goalweight;
self.a.aimweight_start = goalweight;
self.a.aimweight_end = goalweight;
self.a.aimweight_transframes = 0;
}
else
{
if ( !isdefined( self.a.aimweight ) )
self.a.aimweight = 0;
self.a.aimweight_start = self.a.aimweight;
self.a.aimweight_end = goalweight;
self.a.aimweight_transframes = int( goaltime * 20 );
}
self.a.aimweight_t = 0;
}
incrAnimAimWeight()
{
if ( self.a.aimweight_t < self.a.aimweight_transframes )
{
self.a.aimweight_t++ ;
t = 1.0 * self.a.aimweight_t / self.a.aimweight_transframes;
self.a.aimweight = self.a.aimweight_start * ( 1 - t ) + self.a.aimweight_end * t;
}
}
ramboAim( baseYaw )
{
self endon( "killanimscript" );
ramboAimInternal( baseYaw );
self clearAnim( %generic_aim_left, 0.5 );
self clearAnim( %generic_aim_right, 0.5 );
}
ramboAimInternal( baseYaw )
{
self endon( "rambo_aim_end" );
waittillframeend; // in case a previous ramboAim call is still doing its clearanims
self clearAnim( %generic_aim_left, 0.2 );
self clearAnim( %generic_aim_right, 0.2 );
self setAnimLimited( %generic_aim_45l, 1, 0.2 );
self setAnimLimited( %generic_aim_45r, 1, 0.2 );
interval = 0.2;
yaw = 0;
for ( ;; )
{
if ( isDefined( self.shootPos ) )
{
newyaw = GetYaw( self.shootPos ) - self.coverNode.angles[1];
newyaw = AngleClamp180( newyaw - baseYaw );
if ( abs( newyaw - yaw ) > 10 )
{
if ( newyaw > yaw )
newyaw = yaw + 10;
else
newyaw = yaw - 10;
}
yaw = newyaw;
}
// otherwise reuse old yaw
if ( yaw < 0 )
{
weight = yaw / -45;
if ( weight > 1 )
weight = 1;
self setAnimLimited( %generic_aim_right, weight, interval );
self setAnimLimited( %generic_aim_left, 0, interval );
}
else
{
weight = yaw / 45;
if ( weight > 1 )
weight = 1;
self setAnimLimited( %generic_aim_left, weight, interval );
self setAnimLimited( %generic_aim_right, 0, interval );
}
wait interval;
}
}
// decides on the number of shots to do in a burst.
decideNumShotsForBurst()
{
numShots = 0;
fixedBurstCount = weaponBurstCount( self.weapon );
if ( fixedBurstCount )
numShots = fixedBurstCount;
else if ( animscripts\weaponList::usingSemiAutoWeapon() )
numShots = anim.semiFireNumShots[ randomint( anim.semiFireNumShots.size ) ];
else if ( self.fastBurst )
numShots = anim.fastBurstFireNumShots[ randomint( anim.fastBurstFireNumShots.size ) ];
else
numShots = anim.burstFireNumShots[ randomint( anim.burstFireNumShots.size ) ];
if ( numShots <= self.bulletsInClip )
return numShots;
assertex( self.bulletsInClip >= 0, self.bulletsInClip );
if ( self.bulletsInClip <= 0 )
return 1;
return self.bulletsInClip;
}
decideNumShotsForFull()
{
numShots = self.bulletsInClip;
if ( weaponClass( self.weapon ) == "mg" )
{
choice = randomfloat( 10 );
if ( choice < 3 )
numShots = randomIntRange( 2, 6 );
else if ( choice < 8 )
numShots = randomIntRange( 6, 12 );
else
numShots = randomIntRange( 12, 20 );
}
return numShots;
}
insure_dropping_clip( note, flagName )
{
/#
// will turn this assert on after the current anims get fixed
//assertex( isdefined( self.last_drop_clip_time ) && self.last_drop_clip_time > gettime() - 5000, "Tried to do attach clip notetrack without doing drop clip notetrack first, do /g_dumpanims " + self getentnum() + " and report erroneous anim." );
#/
}
handleDropClip( flagName )
{
self endon( "killanimscript" );
self endon( "abort_reload" );
/#
// make sure that we don't do clip anims without drop clip first
self.last_drop_clip_time = gettime();
#/
//prof_begin( "handleDropClip" );
clipModel = undefined;
if ( self.weaponInfo[ self.weapon ].useClip )
clipModel = getWeaponClipModel( self.weapon );
/#
if ( isdefined( clipModel ) )
self thread assertDropClipCleanedUp( 4, clipModel );
#/
if ( self.weaponInfo[ self.weapon ].hasClip )
{
if ( usingSidearm() )
self playsound( "weap_reload_pistol_clipout_npc" );
else
self playsound( "weap_reload_smg_clipout_npc" );
if ( isDefined( clipModel ) )
{
self hidepart( "tag_clip" );
self thread dropClipModel( clipModel, "tag_clip" );
self.weaponInfo[ self.weapon ].hasClip = false;
self thread resetClipOnAbort( clipModel );
}
}
//prof_end( "handleDropClip" );
for ( ;; )
{
self waittill( flagName, noteTrack );
switch( noteTrack )
{
case "attach clip left":
case "attach clip right":
if ( isdefined( clipModel ) )
{
self attach( clipModel, "tag_inhand" );
self thread resetClipOnAbort( clipModel, "tag_inhand" );
}
// if we abort the reload after this point, we don't want to have to do it again
self animscripts\weaponList::RefillClip();
break;
case "detach clip nohand":
if ( isdefined( clipModel ) )
self detach( clipModel, "tag_inhand" );
break;
case "detach clip right":
case "detach clip left":
if ( isdefined( clipModel ) )
{
self detach( clipModel, "tag_inhand" );
self showpart( "tag_clip" );
self notify( "clip_detached" );
self.weaponInfo[ self.weapon ].hasClip = true;
}
if ( usingSidearm() )
self playsound( "weap_reload_pistol_clipin_npc" );
else
self playsound( "weap_reload_smg_clipin_npc" );
self.a.needsToRechamber = 0;
return;
}
}
}
resetClipOnAbort( clipModel, currentTag )
{
self notify( "clip_detached" );
self endon( "clip_detached" );
//self endon ( "death" ); // don't end on death or we won't delete the clip when we die!
self waittill_any( "killanimscript", "abort_reload" );
// we can be dead but still defined. if we're undefined we got deleted.
if ( !isDefined( self ) )
return;
if ( isDefined( currentTag ) )
self detach( clipModel, currentTag );
if ( isAlive( self ) )
{
self showpart( "tag_clip" );
self.weaponInfo[ self.weapon ].hasClip = true;
}
else
{
if ( isDefined( currentTag ) )
self dropClipModel( clipModel, currentTag );
}
}
dropClipModel( clipModel, tagName )
{
clip = spawn( "script_model", self getTagOrigin( tagName ) );
clip setModel( clipModel );
clip.angles = self getTagAngles( tagName );
clip PhysicsLaunchClient( clip.origin, (0,0,0) );
wait 10;
if ( isDefined( clip ) )
clip delete();
}
/#
assertDropClipCleanedUp( waitTime, clipModel )
{
self endon( "death" );
self endon( "abort_reload" );
self endon( "clip_detached" );
wait waitTime;
// this assert can be fixed by adding an "abort_reload" notify from whatever interrupted the reload.
assertmsg( "AI " + self getEntityNumber() + " started a reload and didn't reset clip models after " + waitTime + " seconds" );
}
#/
moveToOriginOverTime( origin, time )
{
self endon( "killanimscript" );
distSq = distanceSquared( self.origin, origin );
if ( distSq < 1 )
{
self safeTeleport( origin );
return;
}
if ( distSq > 16 * 16 && !self mayMoveToPoint( origin ) )
{
/# println( "^1Warning: AI starting behavior for node at " + origin + " but could not move to that point." ); #/
return;
}
self.keepClaimedNodeIfValid = true;
offset = self.origin - origin;
frames = int( time * 20 );
offsetreduction = vector_multiply( offset, 1.0 / frames );
for ( i = 0; i < frames; i++ )
{
offset -= offsetreduction;
self safeTeleport( origin + offset );
wait .05;
}
self.keepClaimedNodeIfValid = false;
}
returnTrue() { return true; }
playLookAnimation( lookAnim, lookTime, canStopCallback )
{
if ( !isdefined( canStopCallback ) )
canStopCallback = ::returnTrue;
for ( i = 0; i < lookTime * 10; i++ )
{
// Break out if you saw somebody lately
if ( isalive( self.enemy ) )
{
if ( self canSeeEnemy() && [[ canStopCallback ]]() )
return;
}
if ( self isSuppressedWrapper() && [[ canStopCallback ]]() )
return;
self setAnimKnobAll( lookAnim, %body, 1, .1 );
wait( 0.1 );
}
}
throwDownWeapon( swapAnim )
{
self endon( "killanimscript" );
// Too many issues right now
// self animMode( "angle deltas" );
// self setFlaggedAnimKnobAllRestart( "weapon swap", swapAnim, %body, 1, .1, 1 );
// self DoNoteTracks( "weapon swap" );
self animscripts\shared::placeWeaponOn( self.secondaryweapon, "right" );
self maps\_gameskill::didSomethingOtherThanShooting();
}
rpgPlayerRepulsor()
{
// Creates a repulsor on the player when shooting at the player
// After a couple freebe misses the repulsor is removed
MISSES_REMAINING = rpgPlayerRepulsor_getNumMisses();
if ( MISSES_REMAINING == 0 )
return;
self endon( "death" );
for(;;)
{
level waittill( "an_enemy_shot", guy );
if ( guy != self )
continue;
if ( !isdefined( guy.enemy ) )
continue;
if ( guy.enemy != level.player )
continue;
if ( ( isdefined( level.createRpgRepulsors ) ) && ( level.createRpgRepulsors == false ) )
continue;
thread rpgPlayerRepulsor_create();
MISSES_REMAINING--;
if ( MISSES_REMAINING <= 0 )
return;
}
}
rpgPlayerRepulsor_getNumMisses()
{
skill = getdifficulty();
switch( skill )
{
case "gimp":
case "easy":
return 2;
case "medium":
case "hard":
case "difficult":
return 1;
case "fu":
return 0;
}
return 2;
}
rpgPlayerRepulsor_create()
{
repulsor = Missile_CreateRepulsorEnt( level.player, 5000, 800 );
wait 4.0;
Missile_DeleteAttractor( repulsor );
}