IW4-Dump-Files/animscripts/init.gsc

753 lines
20 KiB
Plaintext

// Notes about scripts
//=====================
//
// Anim variables
// --------------
// Anim variables keep track of what the character is doing with respect to his
// animations. They know if he's standing, crouching, kneeling, walking, running, etc,
// so that he can play appropriate transitions to get to the animation he wants.
// anim_movement - "stop", "walk", "run"
// anim_pose - "stand", "crouch", "prone", some others for pain poses.
// I'm putting functions to do the basic animations to change these variables in
// SetPoseMovement.gsc,
//
// Error Reporting
// ---------------
// To report a script error condition (similar to assert(0)), I assign a non-existent variable to
// the variable homemade_error I use the name of the non-existent variable to try to explain the
// error. For example:
// homemade_error = Unexpected_anim_pose_value + self.a.pose;
// I also have a kind of assert, called as follows:
// [[anim.assertEX(condition, message_string);
// If condition evaluates to 0, the assert fires, prints message_string and stops the server. Since
// I don't have stack traces of any kind, the message string needs to say from where the assert was
// called.
#include animscripts\Utility;
#include maps\_utility;
#include animscripts\Combat_utility;
#include common_scripts\Utility;
#using_animtree( "generic_human" );
initWeapon( weapon )
{
self.weaponInfo[ weapon ] = spawnstruct();
self.weaponInfo[ weapon ].position = "none";
self.weaponInfo[ weapon ].hasClip = true;
if ( getWeaponClipModel( weapon ) != "" )
self.weaponInfo[ weapon ].useClip = true;
else
self.weaponInfo[ weapon ].useClip = false;
}
isWeaponInitialized( weapon )
{
return isDefined( self.weaponInfo[ weapon ] );
}
// Persistent global aiming limits / tolerances
setGlobalAimSettings()
{
anim.coverCrouchLeanPitch = 55;
// Used by 'Explosed' combat (combat scirpt)
anim.aimYawDiffFarTolerance = 10;
anim.aimYawDiffCloseDistSQ = 64 * 64;
anim.aimYawDiffCloseTolerance = 45;
anim.aimPitchDiffTolerance = 20;
// Used by LastStand (pain script)
anim.painYawDiffFarTolerance = 25;
anim.painYawDiffCloseDistSQ = anim.aimYawDiffCloseDistSQ;
anim.painYawDiffCloseTolerance = anim.aimYawDiffCloseTolerance;
anim.painPitchDiffTolerance = 30;
// Absolute maximum trackLoop angles after which the weights are reset to 0
// These must be greater than the maximum possible aiming limit for all stances
anim.maxAngleCheckYawDelta = 65;
anim.maxAngleCheckPitchDelta = 65;
}
everUsesSecondaryWeapon()
{
if ( isShotgun( self.secondaryweapon ) )
return true;
if ( weaponClass( self.primaryweapon ) == "rocketlauncher" )
return true;
return false;
}
main()
{
prof_begin( "animscript_init" );
self.a = spawnStruct();
self.a.laserOn = false;
self.primaryweapon = self.weapon;
firstInit();
if ( self.primaryweapon == "" )
self.primaryweapon = "none";
if ( self.secondaryweapon == "" )
self.secondaryweapon = "none";
if ( self.sidearm == "" )
self.sidearm = "none";
self initWeapon( self.primaryweapon );
self initWeapon( self.secondaryweapon );
self initWeapon( self.sidearm );
// this will cause us to think we're using our sidearm when we're not. the aitype should not allow this.
assertex( self.primaryweapon != self.sidearm || self.primaryweapon == "none", "AI \"" + self.classname + "\" with export " + self.export + " has both a sidearm and primaryweapon of \"" + self.primaryweapon + "\"." );
assertex( self.secondaryweapon != self.sidearm || self.secondaryweapon == "none" || !self everUsesSecondaryWeapon(), "AI \"" + self.classname + "\" with export " + self.export + " has both a sidearm and secondaryweapon of \"" + self.primaryweapon + "\"." );
self setDefaultAimLimits();
self.a.weaponPos[ "left" ] = "none";
self.a.weaponPos[ "right" ] = "none";
self.a.weaponPos[ "chest" ] = "none";
self.a.weaponPos[ "back" ] = "none";
self.a.weaponPosDropping[ "left" ] = "none";
self.a.weaponPosDropping[ "right" ] = "none";
self.a.weaponPosDropping[ "chest" ] = "none";
self.a.weaponPosDropping[ "back" ] = "none";
self.lastWeapon = self.weapon;
self.root_anim = %root;
self thread beginGrenadeTracking();
hasRocketLauncher = usingRocketLauncher();
self.a.neverLean = hasRocketLauncher;
if ( hasRocketLauncher )
self thread animscripts\shared::rpgPlayerRepulsor();
// TODO: proper ammo tracking
self.a.rockets = 3;
self.a.rocketVisible = true;
// SetWeaponDist();
// Set initial states for poses
self.a.pose = "stand";
self.a.grenadeThrowPose = "stand";
self.a.movement = "stop";
self.a.state = "stop";
self.a.special = "none";
self.a.gunHand = "none"; // Initialize so that PutGunInHand works properly.
self.a.PrevPutGunInHandTime = -1;
self.dropWeapon = true;
self.minExposedGrenadeDist = 750;
animscripts\shared::placeWeaponOn( self.primaryweapon, "right" );
if ( isShotgun( self.secondaryweapon ) )
animscripts\shared::placeWeaponOn( self.secondaryweapon, "back" );
self.a.needsToRechamber = 0;
self.a.combatEndTime = gettime();
self.a.lastEnemyTime = gettime();
self.a.suppressingEnemy = false;
self.a.disableLongDeath = !( self isBadGuy() );
self.a.lookangle = 0;
self.a.painTime = 0;
self.a.lastShootTime = 0;
self.a.nextGrenadeTryTime = 0;
self.a.reactToBulletChance = 0.8;
if ( self.team != "allies" )
{
// only select allies have IR laser and beacon
self.has_no_ir = true;
}
self.a.postScriptFunc = undefined;
self.a.stance = "stand";
self.choosePoseFunc = animscripts\utility::choosePose;
//self.a.state = "idle";
self._animActive = 0;
self._lastAnimTime = 0;
self thread enemyNotify();
self.baseAccuracy = 1;
self.a.missTime = 0;
self.a.nodeath = false;
self.a.missTime = 0;
self.a.missTimeDebounce = 0;
self.a.disablePain = false;
self.accuracyStationaryMod = 1;
self.chatInitialized = false;
self.sightPosTime = 0;
self.sightPosLeft = true;
self.needRecalculateGoodShootPos = true;
self.defaultTurnThreshold = 55;
self.a.nextStandingHitDying = false;
// Makes AI able to throw grenades at other AI.
if ( !isdefined( self.script_forcegrenade ) )
self.script_forcegrenade = 0;
/# self.a.lastDebugPrint = ""; #/
SetupUniqueAnims();
/# thread animscripts\utility::UpdateDebugInfo(); #/
self animscripts\weaponList::RefillClip(); // Start with a full clip.
// state tracking
self.lastEnemySightTime = 0;// last time we saw our current enemy
self.combatTime = 0;// how long we've been in / out of combat
self.suppressed = false;// if we're currently suppressed
self.suppressedTime = 0;// how long we've been in / out of suppression
if ( self.team == "allies" )
self.suppressionThreshold = 0.5;
else
self.suppressionThreshold = 0.0;
// Random range makes the grenades less accurate and do less damage, but also makes it difficult to throw back.
if ( self.team == "allies" )
self.randomGrenadeRange = 0;
else
self.randomGrenadeRange = 256;
self.ammoCheatInterval = 8000; // if out of ammo and it's been this long since last time, do an instant reload
self.ammoCheatTime = 0;
animscripts\animset::set_animset_run_n_gun();
self.exception = [];
self.exception[ "corner" ] = 1;
self.exception[ "cover_crouch" ] = 1;
self.exception[ "stop" ] = 1;
self.exception[ "stop_immediate" ] = 1;
self.exception[ "move" ] = 1;
self.exception[ "exposed" ] = 1;
self.exception[ "corner_normal" ] = 1;
keys = getArrayKeys( self.exception );
for ( i = 0; i < keys.size; i++ )
{
clear_exception( keys[ i ] );
}
self.reacquire_state = 0;
self thread setNameAndRank_andAddToSquad();
self.shouldConserveAmmoTime = 0;
/#
self thread printEyeOffsetFromNode();
self thread showLikelyEnemyPathDir();
#/
self thread monitorFlash();
self thread onDeath();
prof_end( "animscript_init" );
}
weapons_with_ir( weapon )
{
weapons[ 0 ] = "m4_grenadier";
weapons[ 1 ] = "m4_grunt";
weapons[ 2 ] = "m4_silencer";
weapons[ 3 ] = "m4m203";
if ( !isdefined( weapon ) )
return false;
for ( i = 0 ; i < weapons.size ; i++ )
{
if ( issubstr( weapon, weapons[ i ] ) )
return true;
}
return false;
}
/#
printEyeOffsetFromNode()
{
self endon( "death" );
while ( 1 )
{
if ( getdvarint( "scr_eyeoffset" ) == self getentnum() )
{
if ( isdefined( self.coverNode ) )
{
offset = self geteye() - self.coverNode.origin;
forward = anglestoforward( self.coverNode.angles );
right = anglestoright( self.coverNode.angles );
trueoffset = ( vectordot( right, offset ), vectordot( forward, offset ), offset[ 2 ] );
println( trueoffset );
}
}
else
wait 2;
wait .1;
}
}
showLikelyEnemyPathDir()
{
self endon( "death" );
setDvarIfUninitialized( "scr_showlikelyenemypathdir", "-1" );
while ( 1 )
{
if ( getdvarint( "scr_showlikelyenemypathdir" ) == self getentnum() )
{
yaw = self.angles[ 1 ];
dir = self getAnglesToLikelyEnemyPath();
if ( isdefined( dir ) )
yaw = dir[ 1 ];
printpos = self.origin + ( 0, 0, 60 ) + anglestoforward( ( 0, yaw, 0 ) ) * 100;
line( self.origin + ( 0, 0, 60 ), printpos );
if ( isdefined( dir ) )
print3d( printpos, "likelyEnemyPathDir: " + yaw, ( 1, 1, 1 ), 1, 0.5 );
else
print3d( printpos, "likelyEnemyPathDir: undefined", ( 1, 1, 1 ), 1, 0.5 );
wait .05;
}
else
wait 2;
}
}
#/
setNameAndRank_andAddToSquad()
{
self endon( "death" );
if ( !isdefined( level.loadoutComplete ) )
level waittill( "loadout complete" );
self maps\_names::get_name();
// needs to run after the name has been set since bcs changes self.voice from "multilingual"
// to something more specific
self thread animscripts\squadManager::addToSquad();// slooooow
}
// Debug thread to see when stances are being allowed
PollAllowedStancesThread()
{
for ( ;; )
{
if ( self isStanceAllowed( "stand" ) )
{
line[ 0 ] = "stand allowed";
color[ 0 ] = ( 0, 1, 0 );
}
else
{
line[ 0 ] = "stand not allowed";
color[ 0 ] = ( 1, 0, 0 );
}
if ( self isStanceAllowed( "crouch" ) )
{
line[ 1 ] = "crouch allowed";
color[ 1 ] = ( 0, 1, 0 );
}
else
{
line[ 1 ] = "crouch not allowed";
color[ 1 ] = ( 1, 0, 0 );
}
if ( self isStanceAllowed( "prone" ) )
{
line[ 2 ] = "prone allowed";
color[ 2 ] = ( 0, 1, 0 );
}
else
{
line[ 2 ] = "prone not allowed";
color[ 2 ] = ( 1, 0, 0 );
}
aboveHead = self getshootatpos() + ( 0, 0, 30 );
offset = ( 0, 0, -10 );
for ( i = 0 ; i < line.size ; i++ )
{
textPos = ( aboveHead[ 0 ] + ( offset[ 0 ] * i ), aboveHead[ 1 ] + ( offset[ 1 ] * i ), aboveHead[ 2 ] + ( offset[ 2 ] * i ) );
print3d( textPos, line[ i ], color[ i ], 1, 0.75 ); // origin, text, RGB, alpha, scale
}
wait 0.05;
}
}
SetupUniqueAnims()
{
if ( !isDefined( self.animplaybackrate ) || !isDefined( self.moveplaybackrate ) )
{
set_anim_playback_rate();
}
}
set_anim_playback_rate()
{
self.animplaybackrate = 0.9 + randomfloat( 0.2 );
self.moveTransitionRate = 0.9 + randomfloat( 0.2 );
self.moveplaybackrate = 1;
self.sideStepRate = 1.35;
}
infiniteLoop( one, two, three, whatever )
{
anim waittill( "new exceptions" );
}
empty( one, two, three, whatever )
{
}
enemyNotify()
{
self endon( "death" );
if ( 1 ) return;
for ( ;; )
{
self waittill( "enemy" );
if ( !isalive( self.enemy ) )
continue;
while ( isplayer( self.enemy ) )
{
if ( hasEnemySightPos() )
level.lastPlayerSighted = gettime();
wait( 2 );
}
}
}
initWindowTraverse()
{
// used to blend the traverse window_down smoothly at the end
level.window_down_height[ 0 ] = -36.8552;
level.window_down_height[ 1 ] = -27.0095;
level.window_down_height[ 2 ] = -15.5981;
level.window_down_height[ 3 ] = -4.37769;
level.window_down_height[ 4 ] = 17.7776;
level.window_down_height[ 5 ] = 59.8499;
level.window_down_height[ 6 ] = 104.808;
level.window_down_height[ 7 ] = 152.325;
level.window_down_height[ 8 ] = 201.052;
level.window_down_height[ 9 ] = 250.244;
level.window_down_height[ 10 ] = 298.971;
level.window_down_height[ 11 ] = 330.681;
}
firstInit()
{
// Initialization that should happen once per level
if ( isDefined( anim.NotFirstTime ) )// Use this to trigger the first init
return;
anim.NotFirstTime = true;
animscripts\animset::init_anim_sets();
anim.useFacialAnims = false;// remove me when facial anims are fixed
maps\_load::init_level_players();
level.player.invul = false;
level.nextGrenadeDrop = randomint( 3 );
level.lastPlayerSighted = 100;
anim.defaultException = animscripts\init::empty;
initDeveloperDvars();
setdvar( "scr_expDeathMayMoveCheck", "on" );
maps\_names::setup_names();
anim.animFlagNameIndex = 0;
animscripts\init_move_transitions::initMoveStartStopTransitions();
animscripts\reactions::initReactionAnims();
anim.combatMemoryTimeConst = 10000;
anim.combatMemoryTimeRand = 6000;
initGrenades();
initAdvanceToEnemy();
setEnv( "none" );
if ( !isdefined( anim.optionalStepEffectFunction ) )
{
anim.optionalStepEffectSmallFunction = animscripts\shared::playFootStepEffectSmall;
anim.optionalStepEffectFunction = animscripts\shared::playFootStepEffect;
}
if ( !isdefined( anim.optionalStepEffects ) )
anim.optionalStepEffects = [];
if ( !isdefined( anim.optionalStepEffectsSmall ) )
anim.optionalStepEffectsSmall = [];
anim.shootEnemyWrapper_func = ::shootEnemyWrapper_shootNotify;
// scripted mode uses a special function. Faster to use a function pointer based on script than use an if statement in a popular loop.
anim.fire_notetrack_functions[ "scripted" ] = animscripts\shared::fire_straight;
anim.fire_notetrack_functions[ "cover_right" ] = animscripts\shared::shootNotetrack;
anim.fire_notetrack_functions[ "cover_left" ] = animscripts\shared::shootNotetrack;
anim.fire_notetrack_functions[ "cover_crouch" ] = animscripts\shared::shootNotetrack;
anim.fire_notetrack_functions[ "cover_stand" ] = animscripts\shared::shootNotetrack;
anim.fire_notetrack_functions[ "move" ] = animscripts\shared::shootNotetrack;
// string based array for notetracks
animscripts\shared::registerNoteTracks();
/#
setDvarIfUninitialized( "debug_delta", "off" );
#/
if ( !isdefined( level.flag ) )
common_scripts\utility::init_flags();
maps\_gameskill::setSkill();
level.painAI = undefined;
animscripts\SetPoseMovement::InitPoseMovementFunctions();
animscripts\face::InitLevelFace();
// probabilities of burst fire shots
anim.burstFireNumShots = array( 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5 );
anim.fastBurstFireNumShots = array( 2, 3, 3, 3, 4, 4, 4, 5, 5 );
anim.semiFireNumShots = array( 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5 );
anim.badPlaces = [];// queue for animscript badplaces
anim.badPlaceInt = 0;// assigns unique names to animscript badplaces since we cant save a badplace as an entity
anim.player = getentarray( "player", "classname" )[ 0 ];
initBattlechatter();
initWindowTraverse();
animscripts\flashed::initFlashed();
animscripts\cqb::setupCQBPointsOfInterest();
initDeaths();
setGlobalAimSettings();
anim.lastCarExplosionTime = -100000;
setupRandomTable();
level.player thread watchReloading();
thread AITurnNotifies();
}
initDeveloperDvars()
{
/#
if ( getdebugdvar( "debug_noanimscripts" ) == "" )
setdvar( "debug_noanimscripts", "off" );
else if ( getdebugdvar( "debug_noanimscripts" ) == "on" )
anim.defaultException = animscripts\init::infiniteLoop;
if ( getdebugdvar( "debug_grenadehand" ) == "" )
setdvar( "debug_grenadehand", "off" );
if ( getdebugdvar( "anim_dotshow" ) == "" )
setdvar( "anim_dotshow", "-1" );
if ( getdebugdvar( "anim_debug" ) == "" )
setdvar( "anim_debug", "" );
if ( getdebugdvar( "debug_misstime" ) == "" )
setdvar( "debug_misstime", "" );
#/
}
initBattlechatter()
{
animscripts\squadmanager::init_squadManager();
anim.player thread animscripts\squadManager::addPlayerToSquad();
animscripts\battleChatter::init_battleChatter();
anim.player thread animscripts\battleChatter_ai::addToSystem();
anim thread animscripts\battleChatter::bcsDebugWaiter();
}
initDeaths()
{
anim.numDeathsUntilCrawlingPain = randomintrange( 0, 15 );
anim.numDeathsUntilCornerGrenadeDeath = randomintrange( 0, 10 );
anim.nextCrawlingPainTime = gettime() + randomintrange( 0, 20000 );
anim.nextCrawlingPainTimeFromLegDamage = gettime() + randomintrange( 0, 10000 );
anim.nextCornerGrenadeDeathTime = gettime() + randomintrange( 0, 15000 );
}
initGrenades()
{
for ( i = 0; i < level.players.size; i++ )
{
player = level.players[ i ];
player.grenadeTimers[ "fraggrenade" ] = randomIntRange( 1000, 20000 );
player.grenadeTimers[ "flash_grenade" ] = randomIntRange( 1000, 20000 );
player.grenadeTimers[ "double_grenade" ] = randomIntRange( 1000, 60000 );
player.numGrenadesInProgressTowardsPlayer = 0;
player.lastGrenadeLandedNearPlayerTime = -1000000;
player.lastFragGrenadeToPlayerStart = -1000000;
player thread setNextPlayerGrenadeTime();
}
anim.grenadeTimers[ "AI_fraggrenade" ] = randomIntRange( 0, 20000 );
anim.grenadeTimers[ "AI_flash_grenade" ] = randomIntRange( 0, 20000 );
anim.grenadeTimers[ "AI_smoke_grenade_american" ] = randomIntRange( 0, 20000 );
/#
thread animscripts\combat_utility::grenadeTimerDebug();
#/
initGrenadeThrowAnims();
}
initAdvanceToEnemy()
{
// use team ID for now. Should be done per group of AI or something more specific
level.lastAdvanceToEnemyTime = [];
level.lastAdvanceToEnemyTime[ "axis" ] = 0;
level.lastAdvanceToEnemyTime[ "allies" ] = 0;
level.lastAdvanceToEnemyTime[ "team3" ] = 0;
level.lastAdvanceToEnemyTime[ "neutral" ] = 0;
level.lastAdvanceToEnemyDest = [];
level.lastAdvanceToEnemyDest[ "axis" ] = ( 0, 0, 0 );
level.lastAdvanceToEnemyDest[ "allies" ] = ( 0, 0, 0 );
level.lastAdvanceToEnemyDest[ "team3" ] = ( 0, 0, 0 );
level.lastAdvanceToEnemyDest[ "neutral" ] = ( 0, 0, 0 );
level.lastAdvanceToEnemySrc = [];
level.lastAdvanceToEnemySrc[ "axis" ] = ( 0, 0, 0 );
level.lastAdvanceToEnemySrc[ "allies" ] = ( 0, 0, 0 );
level.lastAdvanceToEnemySrc[ "team3" ] = ( 0, 0, 0 );
level.lastAdvanceToEnemySrc[ "neutral" ] = ( 0, 0, 0 );
level.lastAdvanceToEnemyAttacker = [];
level.advanceToEnemyGroup = [];
level.advanceToEnemyGroup[ "axis" ] = 0;
level.advanceToEnemyGroup[ "allies" ] = 0;
level.advanceToEnemyGroup[ "team3" ] = 0;
level.advanceToEnemyGroup[ "neutral" ] = 0;
level.advanceToEnemyInterval = 30000; // how often AI will try to run directly to their enemy if the enemy is not visible
level.advanceToEnemyGroupMax = 3; // group size for AI running to their enemy
}
AITurnNotifies()
{
numTurnsThisFrame = 0;
maxAIPerFrame = 3;
while ( 1 )
{
ai = getAIArray();
if ( ai.size == 0 )
{
wait .05;
numTurnsThisFrame = 0;
continue;
}
for ( i = 0; i < ai.size; i++ )
{
if ( !isdefined( ai[ i ] ) )
continue;
ai[ i ] notify( "do_slow_things" );
numTurnsThisFrame++ ;
if ( numTurnsThisFrame == maxAIPerFrame )
{
wait .05;
numTurnsThisFrame = 0;
}
}
}
}
setNextPlayerGrenadeTime()
{
assert( isPlayer( self ) );
waittillframeend;
// might not be defined if maps\_load::main() wasn't called
if ( isdefined( self.gs.playerGrenadeRangeTime ) )
{
maxTime = int( self.gs.playerGrenadeRangeTime * 0.7 );
if ( maxTime < 1 )
maxTime = 1;
self.grenadeTimers[ "fraggrenade" ] = randomIntRange( 0, maxTime );
self.grenadeTimers[ "flash_grenade" ] = randomIntRange( 0, maxTime );
}
if ( isdefined( self.gs.playerDoubleGrenadeTime ) )
{
maxTime = int( self.gs.playerDoubleGrenadeTime );
minTime = int( maxTime / 2 );
if ( maxTime <= minTime )
maxTime = minTime + 1;
self.grenadeTimers[ "double_grenade" ] = randomIntRange( minTime, maxTime );
}
}
beginGrenadeTracking()
{
self endon( "death" );
for ( ;; )
{
self waittill( "grenade_fire", grenade, weaponName );
grenade thread grenade_earthQuake();
}
}
setupRandomTable()
{
// 60 is chosen because it is divisible by 1,2,3,4,5, and 6,
// and it's also high enough to get some good randomness over different seed values
anim.randomIntTableSize = 60;
// anim.randomIntTable is a permutation of integers 0 through anim.randomIntTableSize - 1
anim.randomIntTable = [];
for ( i = 0; i < anim.randomIntTableSize; i++ )
anim.randomIntTable[ i ] = i;
for ( i = 0; i < anim.randomIntTableSize; i++ )
{
switchwith = randomint( anim.randomIntTableSize );
temp = anim.randomIntTable[ i ];
anim.randomIntTable[ i ] = anim.randomIntTable[ switchwith ];
anim.randomIntTable[ switchwith ] = temp;
}
}
onDeath()
{
self waittill( "death" );
if ( !isdefined( self ) )
{
// we were deleted and we're not running the death script.
// still safe to access our variables as a removed entity though:
if ( isdefined( self.a.usingTurret ) )
self.a.usingTurret delete();
}
}