2028 lines
49 KiB
Plaintext
2028 lines
49 KiB
Plaintext
// 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 );
|
|
} |