545 lines
13 KiB
Plaintext
545 lines
13 KiB
Plaintext
#include animscripts\combat_utility;
|
|
#include animscripts\shared;
|
|
#include animscripts\utility;
|
|
#include maps\_utility;
|
|
#using_animtree( "generic_human" );
|
|
|
|
main()
|
|
{
|
|
anim.boatanims = [];
|
|
anim.boatanims[ "left" ] = spawnstruct();
|
|
anim.boatanims[ "left" ].base = %zodiac_aim_left;
|
|
anim.boatanims[ "left" ].trans = %zodiac_trans_R2L; // Why would we use the "R_2_L" animation to transition from the "left" pose to the "right" pose, you ask? I don't know, my friend. I don't know.
|
|
anim.boatanims[ "left" ].aim = spawnstruct();
|
|
anim.boatanims[ "left" ].aim.left = %zodiac_rightside_aim4;
|
|
anim.boatanims[ "left" ].aim.center = %zodiac_rightside_aim5;
|
|
anim.boatanims[ "left" ].aim.right = %zodiac_rightside_aim6;
|
|
anim.boatanims[ "left" ].reload = array( %zodiac_rightside_reload );
|
|
anim.boatanims[ "left" ].leftAimLimit = -49;
|
|
anim.boatanims[ "left" ].rightAimLimit = 48;
|
|
anim.boatanims[ "left" ].idle = %zodiac_rightside_idle;
|
|
anim.boatanims[ "left" ].twitch = array( %zodiac_rightside_shift, %zodiac_rightside_react );
|
|
|
|
anim.boatanims[ "right" ] = spawnstruct();
|
|
anim.boatanims[ "right" ].base = %zodiac_aim_right;
|
|
anim.boatanims[ "right" ].trans = %zodiac_trans_L2R;
|
|
anim.boatanims[ "right" ].aim = spawnstruct();
|
|
anim.boatanims[ "right" ].aim.left = %zodiac_leftside_aim4;
|
|
anim.boatanims[ "right" ].aim.center = %zodiac_leftside_aim5;
|
|
anim.boatanims[ "right" ].aim.right = %zodiac_leftside_aim6;
|
|
anim.boatanims[ "right" ].reload = array( %zodiac_leftside_reload, %zodiac_leftside_reloadB );
|
|
anim.boatanims[ "right" ].idle = %zodiac_leftside_idle;
|
|
anim.boatanims[ "right" ].twitch = array( %zodiac_leftside_duck );
|
|
anim.boatanims[ "right" ].leftAimLimit = -51;
|
|
anim.boatanims[ "right" ].rightAimLimit = 51;
|
|
}
|
|
|
|
draw_line_toshootpos()
|
|
{
|
|
while(1)
|
|
{
|
|
if( isdefined( self.shootpos ) )
|
|
Line( self.shootpos, self.origin, (1,0,0), 1 );
|
|
if( isdefined( self.favoriteenemy ) )
|
|
Line( self.favoriteenemy.origin, self.origin, (0,0,1), 1 );
|
|
if( isdefined( self.zodiac_enemy ) )
|
|
Line( self.zodiac_enemy.origin, self.origin, (0,1,0), 1 );
|
|
wait .05;
|
|
}
|
|
|
|
}
|
|
|
|
endthink() // this function is not called right now, but it should be if an AI ever gets off a zodiac.
|
|
{
|
|
self.a.specialShootBehavior = undefined;
|
|
}
|
|
|
|
think()
|
|
{
|
|
self endon ( "killanimscript" ); // (includes death)
|
|
|
|
if( !ent_flag_exist( "transitioning_positions" ) )
|
|
ent_flag_init( "transitioning_positions" );
|
|
else
|
|
ent_flag_clear( "transitioning_positions" );
|
|
|
|
animscripts\utility::initialize( "zodiac" );
|
|
|
|
self.a.boatAimYaw = 0;
|
|
if( !isdefined( self.a.boat_pose ) )
|
|
self.a.boat_pose = "right";
|
|
|
|
self.a.last_boat_pose_switch = gettime();
|
|
|
|
self.a.lastBoatTwitchTime = gettime();
|
|
|
|
self childthread animscripts\shoot_behavior::decideWhatAndHowToShoot( "normal" );
|
|
|
|
self setup_anim_array_boat();
|
|
|
|
self.a.wantBoatReloadTime = undefined;
|
|
|
|
self.a.specialShootBehavior = ::zodiacShootBehavior;
|
|
|
|
self childthread watchVelocity();
|
|
self childthread idleAimDir();
|
|
// self childthread draw_line_toshootpos();
|
|
|
|
for ( ;; )
|
|
{
|
|
self thread disableBoatIdle();
|
|
|
|
if ( self shouldReload() )
|
|
{
|
|
self boatReload();
|
|
continue;
|
|
}
|
|
|
|
newPose = needToChangePose();
|
|
if ( newPose != "none" )
|
|
{
|
|
assert( newPose != self.a.boat_pose );
|
|
|
|
transanim = anim.boatanims[ self.a.boat_pose ].trans;
|
|
self.a.boat_pose = newPose;
|
|
ent_flag_set( "transitioning_positions" );
|
|
self setFlaggedAnimKnobAllRestart( "trans", transanim, %body, 1, 0.2 );
|
|
self animscripts\shared::DoNoteTracksForTime( getAnimLength( transanim ) - 0.3, "trans" );
|
|
self.a.last_boat_pose_switch = gettime();
|
|
ent_flag_clear( "transitioning_positions" );
|
|
|
|
theanim = anim.boatanims[ self.a.boat_pose ].aim.center;
|
|
self setAnimKnobAllRestart( theanim, %body, 1, 0.2 );
|
|
self notify( "boat_pose_change" );
|
|
|
|
self.a.boatAimYaw = 0;
|
|
self setup_anim_array_boat();
|
|
|
|
continue;
|
|
}
|
|
|
|
if ( shouldDoTwitch() )
|
|
{
|
|
doBoatTwitch();
|
|
continue;
|
|
}
|
|
|
|
// we want the additive idle for anything after this point in the loop (shooting or aiming)
|
|
self thread enableBoatIdle();
|
|
|
|
if ( aimedAtShootEntOrPos() )
|
|
{
|
|
self shootUntilNeedToChangePose();
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
self updateBoatAim();
|
|
}
|
|
|
|
wait .1;
|
|
|
|
}
|
|
|
|
self waittill( "forever" );
|
|
}
|
|
|
|
shouldReload()
|
|
{
|
|
if ( NeedToReload( 0 ) )
|
|
{
|
|
// it looks bad to reload when we have targets.
|
|
// we'll reload when we get a chance.
|
|
|
|
if ( !isDefined( self.a.wantBoatReloadTime ) )
|
|
self.a.wantBoatReloadTime = gettime();
|
|
self animscripts\weaponList::RefillClip();
|
|
}
|
|
|
|
if ( isDefined( self.a.wantBoatReloadTime ) )
|
|
{
|
|
// don't wait too long.
|
|
if ( gettime() - self.a.wantBoatReloadTime > 2500 )
|
|
return true;
|
|
|
|
if ( !canAimAtEnemy() )
|
|
return true;
|
|
|
|
if ( self.a.lastShootTime < gettime() - 1500 )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
boatReload()
|
|
{
|
|
reloads = anim.boatanims[ self.a.boat_pose ].reload;
|
|
reloadanim = reloads[ randomint( reloads.size ) ];
|
|
|
|
self.a.wantBoatReloadTime = undefined;
|
|
|
|
self setFlaggedAnimKnobAllRestart( "reload", reloadanim, %body, 1, 0.2 );
|
|
self animscripts\shared::DoNoteTracks( "reload" );
|
|
|
|
self animscripts\weaponList::RefillClip();
|
|
}
|
|
|
|
disableBoatIdle()
|
|
{
|
|
if ( !isDefined( self.a.boatIdle ) )
|
|
return;
|
|
|
|
self endon( "killanimscript" );
|
|
|
|
// actually wait a bit before clearing it in case we still want it
|
|
self endon( "want_boat_idle" );
|
|
wait .05;
|
|
|
|
self notify( "end_boat_idle" );
|
|
self.a.boatIdle = undefined;
|
|
|
|
self clearAnim( %zodiac_idle, 0.2 );
|
|
}
|
|
|
|
enableBoatIdle()
|
|
{
|
|
self notify( "want_boat_idle" );
|
|
|
|
if ( isdefined( self.a.boatIdle ) )
|
|
return;
|
|
self.a.boatIdle = true;
|
|
|
|
self endon( "end_boat_idle" );
|
|
|
|
idleAnim = anim.boatanims[ self.a.boat_pose ].idle;
|
|
if ( isDefined( idleAnim ) )
|
|
self setAnimKnob( idleAnim, 1, 0.2 );
|
|
}
|
|
|
|
shouldDoTwitch()
|
|
{
|
|
if ( self.a.lastShootTime > gettime() - 2000 )
|
|
return false;
|
|
|
|
if ( gettime() < self.a.lastBoatTwitchTime + 1500 )
|
|
return false;
|
|
|
|
if ( isDefined( self.enemy ) && self.enemy sightconetrace( self getEye() ) )
|
|
return false;
|
|
|
|
if ( !isDefined( anim.boatanims[ self.a.boat_pose ].twitch ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
doBoatTwitch()
|
|
{
|
|
twitches = anim.boatanims[ self.a.boat_pose ].twitch;
|
|
|
|
twitchAnim = twitches[ randomint( twitches.size ) ];
|
|
for ( i = 0; i < 5; i++ )
|
|
{
|
|
if ( !isdefined( self.a.lastBoatTwitchAnim ) || twitchAnim != self.a.lastBoatTwitchAnim )
|
|
break;
|
|
twitchAnim = twitches[ randomint( twitches.size ) ];
|
|
}
|
|
|
|
self setFlaggedAnimKnobAllRestart( "twitch", twitchAnim, %body, 1, 0.2 );
|
|
self animscripts\shared::DoNoteTracks( "twitch" );
|
|
|
|
self.a.lastBoatTwitchAnim = twitchAnim;
|
|
self.a.lastBoatTwitchTime = gettime();
|
|
}
|
|
|
|
zodiacShootBehavior()
|
|
{
|
|
if ( !isdefined( self.enemy ) || !self.enemy sightconetrace( self getEye() ) )
|
|
{
|
|
self.shootent = undefined;
|
|
self.shootpos = undefined;
|
|
self.shootstyle = "none";
|
|
return;
|
|
}
|
|
|
|
self.shootent = self.enemy;
|
|
self.shootpos = self.enemy getShootAtPos();
|
|
distSq = distanceSquared( self.origin, self.enemy.origin );
|
|
|
|
if ( distSq < 4000*4000 )
|
|
self.shootstyle = "burst";
|
|
else
|
|
self.shootstyle = "single";
|
|
}
|
|
|
|
|
|
watchVelocity()
|
|
{
|
|
self endon( "killanimscript" );
|
|
self.prevpos = self.origin;
|
|
self.boatvelocity = (0,0,0);
|
|
|
|
for ( ;; )
|
|
{
|
|
wait .05;
|
|
self.boatvelocity = (self.origin - self.prevpos) / .05;
|
|
self.prevpos = self.origin;
|
|
}
|
|
}
|
|
|
|
waitRandomTimeBoat()
|
|
{
|
|
self endon( "boat_pose_change" );
|
|
wait randomfloatrange( 0.5, 3.5 );
|
|
}
|
|
|
|
idleAimDir()
|
|
{
|
|
self endon( "killanimscript" );
|
|
|
|
for ( ;; )
|
|
{
|
|
if ( self.a.boat_pose == "left" )
|
|
self.idleAimYaw = randomfloatrange( -20, 40 );
|
|
else
|
|
self.idleAimYaw = randomfloatrange( -40, 20 );
|
|
|
|
self waitRandomTimeBoat();
|
|
}
|
|
}
|
|
|
|
|
|
getBoatAimYawToShootPos( predictionTime )
|
|
{
|
|
if ( !isDefined( self.shootPos ) )
|
|
return 0;
|
|
|
|
predictedShootPos = self.shootPos - self.boatvelocity * predictionTime;
|
|
|
|
aimYaw = getAimYawToPoint( predictedShootPos );
|
|
return aimYaw;
|
|
}
|
|
|
|
|
|
canAimAtEnemy()
|
|
{
|
|
if ( !isDefined( self.shootPos ) )
|
|
return false;
|
|
|
|
aimYaw = getDesiredBoatAimYaw();
|
|
anims = anim.boatanims[ self.a.boat_pose ];
|
|
return ( aimYaw >= anims.leftAimLimit && aimYaw <= anims.rightAimLimit );
|
|
}
|
|
|
|
getDesiredBoatAimYaw()
|
|
{
|
|
aimYaw = 0;
|
|
|
|
if ( isDefined( self.shootPos ) )
|
|
{
|
|
aimYaw = getBoatAimYawToShootPos( .1 );
|
|
if ( self.a.boat_pose == "left" )
|
|
aimYaw = AngleClamp180( aimYaw + 40.5 );
|
|
else
|
|
aimYaw = AngleClamp180( aimYaw - 36 );
|
|
}
|
|
else
|
|
{
|
|
aimYaw = self.idleAimYaw;
|
|
}
|
|
|
|
return aimYaw;
|
|
}
|
|
|
|
updateBoatAim()
|
|
{
|
|
// need to be able to aim quickly because we're moving quickly, so don't cap too much
|
|
maxTurn = 15;
|
|
if ( !isDefined( self.shootPos ) )
|
|
maxTurn = 5;
|
|
|
|
aimYaw = getDesiredBoatAimYaw();
|
|
|
|
if ( abs( aimYaw - self.a.boatAimYaw ) > maxTurn )
|
|
{
|
|
if ( aimYaw < self.a.boatAimYaw )
|
|
aimYaw = self.a.boatAimYaw - maxTurn;
|
|
else
|
|
aimYaw = self.a.boatAimYaw + maxTurn;
|
|
}
|
|
|
|
anims = anim.boatanims[ self.a.boat_pose ];
|
|
if ( aimYaw < 0 )
|
|
{
|
|
frac = aimYaw / anims.leftAimLimit;
|
|
if ( frac > 1 )
|
|
frac = 1;
|
|
self setAnimKnob( anims.aim.center, 1 - frac, 0.1 );
|
|
self setAnim ( anims.aim.left , frac, 0.1 );
|
|
}
|
|
else
|
|
{
|
|
frac = aimYaw / anims.rightAimLimit;
|
|
if ( frac > 1 )
|
|
frac = 1;
|
|
self setAnimKnob( anims.aim.center, 1 - frac, 0.1 );
|
|
self setAnim ( anims.aim.right , frac, 0.1 );
|
|
}
|
|
self setAnimKnobAll( anims.base, %zodiac_actions, 1, 0.2 );
|
|
|
|
self.a.boatAimYaw = aimYaw;
|
|
}
|
|
|
|
updateBoatAimThread()
|
|
{
|
|
self endon( "killanimscript" );
|
|
self endon( "end_shootUntilNeedToChangePose" );
|
|
|
|
for ( ;; )
|
|
{
|
|
updateBoatAim();
|
|
wait .1;
|
|
}
|
|
}
|
|
|
|
|
|
shootUntilNeedToChangePose()
|
|
{
|
|
self thread watchForNeedToChangePoseOrTimeout();
|
|
self endon( "end_shootUntilNeedToChangePose" );
|
|
|
|
self thread updateBoatAimThread();
|
|
|
|
shootUntilShootBehaviorChange();
|
|
|
|
self notify( "end_shootUntilNeedToChangePose" );
|
|
}
|
|
|
|
watchForNeedToChangePoseOrTimeout()
|
|
{
|
|
self endon( "killanimscript" );
|
|
self endon( "end_shootUntilNeedToChangePose" );
|
|
|
|
endtime = gettime() + 4000 + randomint( 2000 );
|
|
|
|
while ( 1 )
|
|
{
|
|
if ( gettime() > endtime || needToChangePose() != "none" )
|
|
break;
|
|
|
|
if ( self shouldReload() )
|
|
break;
|
|
|
|
wait .1;
|
|
}
|
|
|
|
self notify( "end_shootUntilNeedToChangePose" );
|
|
}
|
|
|
|
needToChangePose_other()
|
|
{
|
|
if ( self.a.last_boat_pose_switch > gettime() - 2000 )
|
|
return "none";
|
|
|
|
if ( self.a.lastShootTime > gettime() - 2000 )
|
|
return "none";
|
|
|
|
if ( !isDefined( self.shootPos ) )
|
|
return "none";
|
|
|
|
aimYaw = getBoatAimYawToShootPos( 0.5 ); // half second prediction
|
|
|
|
if ( self.a.boat_pose == "left" )
|
|
{
|
|
if ( aimYaw > 15 && aimYaw < 160 )
|
|
return "right";
|
|
}
|
|
else if ( self.a.boat_pose == "right" )
|
|
{
|
|
if ( aimYaw < -15 && aimYaw > -160 )
|
|
return "left";
|
|
}
|
|
|
|
return "none";
|
|
}
|
|
|
|
needToChangePose()
|
|
{
|
|
if ( isdefined( self.use_auto_pose ) )
|
|
return needToChangePose_other();
|
|
|
|
if ( isDefined( self.scripted_boat_pose ) )
|
|
{
|
|
if ( self.a.boat_pose == self.scripted_boat_pose )
|
|
return "none";
|
|
|
|
return self.scripted_boat_pose;
|
|
}
|
|
|
|
// we always want the "left" pose now
|
|
if ( self.a.boat_pose == "right" )
|
|
return "left";
|
|
|
|
return "none";
|
|
|
|
/*
|
|
if ( !isDefined( self.shootPos ) )
|
|
return "none";
|
|
|
|
aimYaw = getBoatAimYawToShootPos( 0.5 ); // half second prediction
|
|
|
|
if ( self.a.boat_pose == "left" )
|
|
{
|
|
if ( aimYaw > 15 && aimYaw < 160 )
|
|
return "right";
|
|
}
|
|
else if ( self.a.boat_pose == "right" )
|
|
{
|
|
if ( aimYaw < -15 && aimYaw > -160 )
|
|
return "left";
|
|
}
|
|
|
|
return "none";
|
|
*/
|
|
}
|
|
|
|
|
|
setup_anim_array_boat()
|
|
{
|
|
self.a.array = [];
|
|
|
|
self.a.array[ "fire" ] = %exposed_shoot_auto_v3;
|
|
|
|
if ( self.a.boat_pose == "left" )
|
|
{
|
|
self.a.array[ "single" ] = array( %zodiac_rightside_fire_single );
|
|
self.a.array[ "burst2" ] = %zodiac_rightside_fire_burst;
|
|
self.a.array[ "burst3" ] = %zodiac_rightside_fire_burst;
|
|
self.a.array[ "burst4" ] = %zodiac_rightside_fire_burst;
|
|
self.a.array[ "burst5" ] = %zodiac_rightside_fire_burst;
|
|
self.a.array[ "burst6" ] = %zodiac_rightside_fire_burst;
|
|
self.a.array[ "semi2" ] = %zodiac_rightside_fire_burst;
|
|
self.a.array[ "semi3" ] = %zodiac_rightside_fire_burst;
|
|
self.a.array[ "semi4" ] = %zodiac_rightside_fire_burst;
|
|
self.a.array[ "semi5" ] = %zodiac_rightside_fire_burst;
|
|
self.a.array[ "semi6" ] = %zodiac_rightside_fire_burst;
|
|
}
|
|
else
|
|
{
|
|
self.a.array[ "single" ] = array( %zodiac_leftside_fire_single );
|
|
self.a.array[ "burst2" ] = %zodiac_leftside_fire_burst;
|
|
self.a.array[ "burst3" ] = %zodiac_leftside_fire_burst;
|
|
self.a.array[ "burst4" ] = %zodiac_leftside_fire_burst;
|
|
self.a.array[ "burst5" ] = %zodiac_leftside_fire_burst;
|
|
self.a.array[ "burst6" ] = %zodiac_leftside_fire_burst;
|
|
self.a.array[ "semi2" ] = %zodiac_leftside_fire_burst;
|
|
self.a.array[ "semi3" ] = %zodiac_leftside_fire_burst;
|
|
self.a.array[ "semi4" ] = %zodiac_leftside_fire_burst;
|
|
self.a.array[ "semi5" ] = %zodiac_leftside_fire_burst;
|
|
self.a.array[ "semi6" ] = %zodiac_leftside_fire_burst;
|
|
}
|
|
}
|
|
|
|
|