473 lines
14 KiB
Plaintext
473 lines
14 KiB
Plaintext
|
#include maps\_utility;
|
||
|
#include maps\_anim;
|
||
|
#using_animtree( "generic_human" );
|
||
|
main()
|
||
|
{
|
||
|
// a trigger with targetname "mortar_team" targets a spawner.
|
||
|
// The spawner targets a node or nodes. The script will randomly pick one for his
|
||
|
// destination. The node targets script origins which the mortar will fire at.
|
||
|
// The spawner can also target a second spawner which will spawn a secondary mortar operator.
|
||
|
|
||
|
anims();
|
||
|
array_thread( getentarray( "mortar_team", "targetname" ), ::mortarTrigger );
|
||
|
}
|
||
|
|
||
|
mortarTeam( spawners, node, mortar_targets, delay_base, delay_range )
|
||
|
{
|
||
|
// This command can be called directly from script
|
||
|
ent = spawnStruct();
|
||
|
ent.delay_base = delay_base;
|
||
|
ent.delay_range = delay_range;
|
||
|
ent thread mortarTeamSpawn( spawners, node, mortar_targets );
|
||
|
return ent;
|
||
|
}
|
||
|
|
||
|
mortarTrigger()
|
||
|
{
|
||
|
spawner = getent( self.target, "targetname" );
|
||
|
spawner endon( "death" );
|
||
|
self waittill( "trigger" );
|
||
|
spawner mortarSpawner( self );
|
||
|
}
|
||
|
|
||
|
mortarSpawner( delayEnt )
|
||
|
{
|
||
|
if ( !isdefined( delayEnt ) )
|
||
|
delayEnt = self;
|
||
|
|
||
|
// wrapper that interfaces with radiant to make mortar guys easier to setup
|
||
|
spawners[ 0 ] = self;
|
||
|
|
||
|
// Optionally target a spawner for an aimguy
|
||
|
aimguySpawner = getent( self.target, "targetname" );
|
||
|
if ( isdefined( aimguySpawner ) )
|
||
|
spawners[ 1 ] = aimGuySpawner;
|
||
|
|
||
|
// required target of a destination node or nodes
|
||
|
node = random( getnodearray( self.target, "targetname" ) );
|
||
|
assertEx( isdefined( node ), "Mortar_team spawner at origin " + self.origin + " must target a node or nodes" );
|
||
|
assertEx( isdefined( node.target ), "Mortar node at origin " + node.origin + " must target script origins for mortar targetting" );
|
||
|
|
||
|
mortar_targets = getentarray( node.target, "targetname" );
|
||
|
|
||
|
delay_base = 0;
|
||
|
delay_range = 0;
|
||
|
if ( isdefined( delayEnt.script_delay ) )
|
||
|
delay_base = delayEnt.script_delay;
|
||
|
else
|
||
|
if ( isdefined( delayEnt.script_delay_min ) )
|
||
|
{
|
||
|
delay_base = delayEnt.script_delay_min;
|
||
|
delay_range = delayEnt.script_delay_max - delayEnt.script_delay_min;
|
||
|
}
|
||
|
|
||
|
ent = mortarTeam( spawners, node, mortar_targets, delay_base, delay_range );
|
||
|
return ent;
|
||
|
}
|
||
|
|
||
|
mortarTeamSpawn( spawners, node, mortar_targets )
|
||
|
{
|
||
|
assertex( isdefined( level.scr_anim[ "loadguy" ] ), "Add maps\_mortarteam::anims(); to the top of your script" );
|
||
|
assertex( isdefined( level.mortar ), "Define the level.mortar effect that should be used" );
|
||
|
assertex( spawners.size <= 2, "Mortarteams can't support more than 2 spawners" );
|
||
|
assertex( spawners.size, "Mortarteams need at least 1 spawner" );
|
||
|
name[ 0 ] = "loadguy";
|
||
|
name[ 1 ] = "aimguy";
|
||
|
|
||
|
if ( !isdefined( node.mortarSetup ) )
|
||
|
node.mortarSetup = false;// for making followup spawners not bring a mortar
|
||
|
mortarThink[ 0 ] = ::loadGuy;
|
||
|
mortarThink[ 1 ] = ::aimGuy;
|
||
|
|
||
|
self.objectivePositionEntity = undefined;
|
||
|
self.setup = false;
|
||
|
|
||
|
if ( !isdefined( node.mortarTeamActive ) )
|
||
|
node.mortarTeamActive = false;
|
||
|
assertEx( !node.mortarTeamActive, "Mortarteam that runs to " + node.origin + " has multiple mortar teams active on it. Can only have 1 team at a time operating each unique mortar." );
|
||
|
|
||
|
index = 0;
|
||
|
for ( ;; )
|
||
|
{
|
||
|
spawners[ index ].count = 1;
|
||
|
spawners[ index ].script_moveoverride = 1;
|
||
|
spawn = spawners[ index ] dospawn();
|
||
|
if ( spawn_failed( spawn ) )
|
||
|
{
|
||
|
wait( 1 );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
node.mortarTeamActive = true;
|
||
|
|
||
|
self.guy[ index ] = spawn;
|
||
|
spawn.animname = name[ index ];
|
||
|
if ( spawn.health < 5000 )
|
||
|
spawn.health = 1;
|
||
|
spawn thread [[ mortarThink[ index ] ]]( self, node );
|
||
|
index++ ;
|
||
|
if ( index >= spawners.size )
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
self waittill( "loadguy_done" );
|
||
|
if ( !isalive( self.loadGuy ) )
|
||
|
{
|
||
|
// if the carrier dies, the whole sequence ends there
|
||
|
node.mortarTeamActive = false;
|
||
|
self notify( "mortar_done" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
node.mortarEnt = self;
|
||
|
node.mortarEnt endon( "stop_mortar" );
|
||
|
self.node = node;// so we can externally refer to the node to make the scene stop
|
||
|
node.mortar_targets = mortar_targets;
|
||
|
if ( isalive( self.aimGuy ) )
|
||
|
self thread transferObjectivePositionEntity();
|
||
|
for ( ;; )
|
||
|
{
|
||
|
if ( isalive( self.loadGuy ) )
|
||
|
{
|
||
|
if ( isalive( self.aimGuy ) && self.aimGuy.ready )
|
||
|
dualMortarUntilDeath( node );
|
||
|
else
|
||
|
singleMortarOneRep( node );
|
||
|
}
|
||
|
else
|
||
|
if ( isalive( self.aimGuy ) )
|
||
|
aimGuyMortarsUntilDeath( node );
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
node notify( "stopIdle" );
|
||
|
|
||
|
// wait until the end of the frame in case the guy dies while playing the firing animation on the exact same frame
|
||
|
// that the fire notetrack gets hit and thus potentially causing the mortarEnt to become undefined just as it needs
|
||
|
// it for firing it.
|
||
|
waittillframeend;
|
||
|
|
||
|
node.mortarEnt = undefined;
|
||
|
node.mortar_targets = undefined;
|
||
|
node.mortarTeamActive = false; // was true
|
||
|
self notify( "mortar_done" );
|
||
|
}
|
||
|
|
||
|
transferObjectivePositionEntity()
|
||
|
{
|
||
|
self.loadGuy waittill( "death" );
|
||
|
if ( isalive( self.aimGuy ) )
|
||
|
self.objectivePositionEntity = self.aimGuy;
|
||
|
}
|
||
|
|
||
|
singleMortarOneRep( node )
|
||
|
{
|
||
|
// Make the loadguy fire the mortar once
|
||
|
loadGuy = self.loadGuy;
|
||
|
loadGuy endon( "death" );
|
||
|
if ( loadGuy.health < 5000 )
|
||
|
loadGuy.health = 1;
|
||
|
|
||
|
node notify( "stopIdle" );// in case we broke abruptly from a previous loop to start this one
|
||
|
|
||
|
loadGuy animscripts\shared::placeWeaponOn( self.weapon, "none" );
|
||
|
node thread anim_loop_solo( loadGuy, "wait_idle", "stopIdle" );
|
||
|
wait( self.delay_base + randomfloat( self.delay_range ) );
|
||
|
node notify( "stopIdle" );
|
||
|
node anim_single_solo( loadGuy, "pickup" );
|
||
|
node anim_single_solo( loadGuy, "fire" );
|
||
|
}
|
||
|
|
||
|
aimGuyMortarsUntilDeath( node )
|
||
|
{
|
||
|
// make the aimguy fire the mortar until he dies
|
||
|
aimGuy = self.aimGuy;
|
||
|
if ( aimGuy.health < 5000 )
|
||
|
aimGuy.health = 1;
|
||
|
aimGuy endon( "death" );
|
||
|
aimGuy endon( "stop_mortar" );
|
||
|
|
||
|
node notify( "stopIdle" );
|
||
|
node anim_reach_solo( aimGuy, "pickup" );
|
||
|
aimGuy animscripts\shared::placeWeaponOn( self.weapon, "none" );
|
||
|
aimGuy.deathanim = %exposed_crouch_death_fetal;
|
||
|
for ( ;; )
|
||
|
{
|
||
|
node notify( "stopIdle" );// in case we broke abruptly from a previous loop to start this one
|
||
|
node thread anim_loop_solo( aimGuy, "wait_idle", "stopIdle" );
|
||
|
wait( self.delay_base + randomfloat( self.delay_range ) );
|
||
|
node notify( "stopIdle" );
|
||
|
node anim_single_solo( aimGuy, "pickup_alone" );
|
||
|
node anim_single_solo( aimGuy, "fire_alone" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dualMortarUntilDeath( node )
|
||
|
{
|
||
|
// make the loadguy and aimguy fire the mortar until either dies
|
||
|
loadGuy = self.loadGuy;
|
||
|
aimGuy = self.aimGuy;
|
||
|
guy = self.guy;
|
||
|
if ( loadGuy.health < 5000 )
|
||
|
loadGuy.health = 1;
|
||
|
if ( aimGuy.health < 5000 )
|
||
|
aimGuy.health = 1;
|
||
|
|
||
|
loadGuy endon( "death" );
|
||
|
aimGuy endon( "death" );
|
||
|
loadGuy endon( "stop_mortar" );
|
||
|
aimGuy endon( "stop_mortar" );
|
||
|
|
||
|
node notify( "stopIdle" );// in case we broke abruptly from a previous loop to start this one
|
||
|
node thread anim_loop_solo( loadGuy, "wait_idle", "stopIdle" );
|
||
|
node anim_reach_solo( aimGuy, "fire" );
|
||
|
node notify( "stopIdle" );
|
||
|
|
||
|
loadGuy animscripts\shared::placeWeaponOn( self.weapon, "none" );
|
||
|
aimGuy animscripts\shared::placeWeaponOn( self.weapon, "none" );
|
||
|
aimGuy.deathanim = %exposed_crouch_death_fetal;
|
||
|
for ( ;; )
|
||
|
{
|
||
|
node thread anim_loop( guy, "wait_idle", "stopIdle" );
|
||
|
wait( self.delay_base + randomfloat( self.delay_range ) );
|
||
|
node notify( "stopIdle" );
|
||
|
node anim_single( guy, "pickup" );
|
||
|
node anim_single( guy, "fire" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
aimGuy( ent, node )
|
||
|
{
|
||
|
// self thread debugOrigin();
|
||
|
ent.aimGuy = self;
|
||
|
|
||
|
self endon( "death" );
|
||
|
self.ready = false;
|
||
|
|
||
|
self.allowDeath = true;
|
||
|
self setgoalnode( node );
|
||
|
if ( node.radius > 0 )
|
||
|
self.goalradius = node.radius;
|
||
|
else
|
||
|
self.goalradius = 350;
|
||
|
|
||
|
self waittill( "goal" );
|
||
|
self.ready = true;
|
||
|
thread detachMortarOnDeath();
|
||
|
}
|
||
|
|
||
|
deathNotify( ent )
|
||
|
{
|
||
|
ent endon( "loadguy_done" );
|
||
|
self waittill( "death" );
|
||
|
ent notify( "loadguy_done" );
|
||
|
}
|
||
|
|
||
|
loadGuy( ent, node )
|
||
|
{
|
||
|
// self thread debugOrigin();
|
||
|
|
||
|
ent.loadGuy = self;
|
||
|
ent.objectivePositionEntity = self;
|
||
|
|
||
|
ent notify( "objective_created" );
|
||
|
|
||
|
|
||
|
self endon( "death" );
|
||
|
self thread deathNotify( ent );
|
||
|
self.allowDeath = true;
|
||
|
self.run_overrideanim = %mortar_loadguy_run;
|
||
|
self.deathanim = %exposed_crouch_death_fetal;
|
||
|
|
||
|
thread detachMortarOnDeath();
|
||
|
|
||
|
if ( node.mortarSetup )
|
||
|
{
|
||
|
// if the mortar is already setup
|
||
|
node anim_reach_solo( self, "pickup" );
|
||
|
ent.mortar = node.mortar;
|
||
|
ent.setup = true;
|
||
|
ent notify( "loadguy_done" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
animscripts\shared::placeWeaponOn( self.weapon, "none" );
|
||
|
self attach( "prop_mortar", "TAG_WEAPON_LEFT" );
|
||
|
self setanimknob( %mortar_closed_setup, 1, 0, 1 );
|
||
|
setupAnim[ 0 ] = %mortar_loadguy_setup;
|
||
|
setupString[ 0 ] = "setup_straight";
|
||
|
setupAnim[ 1 ] = %mortar_loadguy_setup_left;
|
||
|
setupString[ 1 ] = "setup_left";
|
||
|
setupAnim[ 2 ] = %mortar_loadguy_setup_right;
|
||
|
setupString[ 2 ] = "setup_right";
|
||
|
dist = undefined;
|
||
|
for ( i = 0;i < setupAnim.size;i++ )
|
||
|
dist[ i ] = distance( self.origin, getstartorigin( node.origin, node.angles, setupAnim[ i ] ) );
|
||
|
|
||
|
index = 0;
|
||
|
current_dist = dist[ 0 ];
|
||
|
for ( i = 1;i < dist.size;i++ )
|
||
|
{
|
||
|
if ( dist[ i ] >= current_dist )
|
||
|
continue;
|
||
|
index = i;
|
||
|
current_dist = dist[ i ];
|
||
|
}
|
||
|
|
||
|
node anim_reach_solo( self, setupString[ index ] );
|
||
|
|
||
|
ent notify( "loadguy_starting" );
|
||
|
|
||
|
node thread anim_single_solo( self, setupString[ index ] );
|
||
|
self waittillmatch( "single anim", "open_mortar" );
|
||
|
if ( soundexists( "weapon_setup" ) )
|
||
|
thread play_sound_in_space( "weapon_setup" );
|
||
|
self setanimknob( %mortar_open_setup, 1, 0, 1 );
|
||
|
node waittill( setupString[ index ] );
|
||
|
|
||
|
mortar = spawn( "script_model", ( 0, 0, 0 ) );
|
||
|
mortar.origin = self gettagorigin( "TAG_WEAPON_LEFT" );
|
||
|
mortar.angles = self gettagangles( "TAG_WEAPON_LEFT" );
|
||
|
mortar setmodel( "prop_mortar" );
|
||
|
|
||
|
node.mortarSetup = true;
|
||
|
ent.mortar = mortar;
|
||
|
node.mortar = mortar;
|
||
|
self detach( "prop_mortar", "TAG_WEAPON_LEFT" );
|
||
|
ent.setup = true;
|
||
|
ent notify( "loadguy_done" );
|
||
|
ent notify( "mortar_setup_finished", self.script_squadname );
|
||
|
}
|
||
|
|
||
|
fire( guy )
|
||
|
{
|
||
|
if ( !isalive( guy ) )
|
||
|
return;
|
||
|
|
||
|
mortarEnt = self.mortarEnt;
|
||
|
mortar_targets = self.mortar_targets;
|
||
|
mortar = mortarEnt.mortar;
|
||
|
org = guy.origin;
|
||
|
wait( 0.25 );
|
||
|
|
||
|
switch( randomint( 3 ) )
|
||
|
{
|
||
|
case 1:
|
||
|
thread play_sound_in_space( "weap_mortar_fire", org );
|
||
|
break;
|
||
|
case 2:
|
||
|
thread play_sound_in_space( "weap_mortar_fire_alt", org );
|
||
|
break;
|
||
|
default:
|
||
|
thread play_sound_in_space( "weap_mortar_fire", org );
|
||
|
break;
|
||
|
}
|
||
|
wait( 0.4 );
|
||
|
|
||
|
playfxontag( level._effect[ "mortar_flash" ], mortar, "TAG_flash" );
|
||
|
target = random( mortar_targets );
|
||
|
|
||
|
if ( isdefined( mortarEnt ) )
|
||
|
mortarEnt notify( "mortar_fired" );
|
||
|
|
||
|
if ( isdefined( level.timetoimpact ) )
|
||
|
{
|
||
|
wait level.timetoimpact;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
wait( distance( self.origin, target.origin ) * 0.0008 );
|
||
|
switch( randomint( 4 ) )
|
||
|
{
|
||
|
case 1:
|
||
|
play_sound_in_space( "mortar_incoming1", target.origin );
|
||
|
break;
|
||
|
case 2:
|
||
|
play_sound_in_space( "mortar_incoming2", target.origin );
|
||
|
break;
|
||
|
case 3:
|
||
|
play_sound_in_space( "mortar_incoming3", target.origin );
|
||
|
break;
|
||
|
default:
|
||
|
play_sound_in_space( "mortar_incoming1", target.origin );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
wait 0.35;
|
||
|
}
|
||
|
|
||
|
if ( !isdefined( level.explosionhide ) )
|
||
|
{
|
||
|
thread play_sound_in_space( "mortar_explosion", target.origin );
|
||
|
playfx( level.mortar, target.origin );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
attachMortar( guy )
|
||
|
{
|
||
|
if ( !isdefined( guy.mortarAmmo ) )
|
||
|
guy.mortarAmmo = false;
|
||
|
if ( !guy.mortarAmmo )
|
||
|
guy attach( "prop_mortar_ammunition", "TAG_WEAPON_RIGHT" );
|
||
|
guy.mortarAmmo = true;
|
||
|
}
|
||
|
|
||
|
detachMortar( guy )
|
||
|
{
|
||
|
if ( guy.mortarAmmo )
|
||
|
{
|
||
|
guy detach( "prop_mortar_ammunition", "TAG_WEAPON_RIGHT" );
|
||
|
thread fire( guy );
|
||
|
}
|
||
|
guy.mortarAmmo = false;
|
||
|
}
|
||
|
|
||
|
detachMortarOnDeath()
|
||
|
{
|
||
|
self waittill( "death" );
|
||
|
if ( !isdefined( self.mortarAmmo ) )
|
||
|
return;
|
||
|
if ( !self.mortarAmmo )
|
||
|
return;
|
||
|
|
||
|
self detach( "prop_mortar_ammunition", "TAG_WEAPON_RIGHT" );
|
||
|
self.mortarAmmo = false;
|
||
|
}
|
||
|
|
||
|
anims()
|
||
|
{
|
||
|
precacheModel( "prop_mortar" );
|
||
|
precacheModel( "prop_mortar_ammunition" );
|
||
|
level._effect[ "mortar_flash" ] = loadfx( "muzzleflashes/mortar_flash" );
|
||
|
|
||
|
level.scr_anim[ "loadguy" ][ "ready_idle" ][ 0 ] = %mortar_loadguy_readyidle;
|
||
|
level.scr_anim[ "loadguy" ][ "wait_idle" ][ 0 ] = %mortar_loadguy_waitidle;
|
||
|
level.scr_anim[ "loadguy" ][ "wait_idle" ][ 1 ] = %mortar_loadguy_waittwitch;
|
||
|
level.scr_anim[ "loadguy" ][ "fire" ] = %mortar_loadguy_fire;
|
||
|
level.scr_anim[ "loadguy" ][ "pickup" ] = %mortar_loadguy_pickup;
|
||
|
level.scr_anim[ "loadguy" ][ "setup_straight" ] = %mortar_loadguy_setup;
|
||
|
level.scr_anim[ "loadguy" ][ "setup_left" ] = %mortar_loadguy_setup_left;
|
||
|
level.scr_anim[ "loadguy" ][ "setup_right" ] = %mortar_loadguy_setup_right;
|
||
|
|
||
|
// addNotetrack_attach("loadguy", "attach shell = right", "prop_mortar_ammunition", "TAG_WEAPON_RIGHT");
|
||
|
// addNotetrack_detach("loadguy", "detach shell = right", "prop_mortar_ammunition", "TAG_WEAPON_RIGHT");
|
||
|
// addNotetrack_customFunction("loadguy", "fire", ::fire);
|
||
|
addNotetrack_customFunction( "loadguy", "attach shell = right", ::attachMortar );
|
||
|
addNotetrack_customFunction( "loadguy", "detach shell = right", ::detachMortar );
|
||
|
|
||
|
level.scr_anim[ "aimguy" ][ "ready_idle" ][ 0 ] = %mortar_aimguy_readyidle;
|
||
|
level.scr_anim[ "aimguy" ][ "ready_alone_idle" ][ 0 ] = %mortar_aimguy_readyidle_alone;
|
||
|
level.scr_anim[ "aimguy" ][ "wait_idle" ][ 0 ] = %mortar_aimguy_waitidle;
|
||
|
level.scr_anim[ "aimguy" ][ "wait_idle" ][ 1 ] = %mortar_aimguy_waittwitch;
|
||
|
level.scr_anim[ "aimguy" ][ "fire" ] = %mortar_aimguy_fire;
|
||
|
level.scr_anim[ "aimguy" ][ "pickup" ] = %mortar_aimguy_pickup;
|
||
|
level.scr_anim[ "aimguy" ][ "pickup_alone" ] = %mortar_aimguy_pickup_alone;
|
||
|
level.scr_anim[ "aimguy" ][ "fire_alone" ] = %mortar_aimguy_fire_alone;
|
||
|
|
||
|
// addNotetrack_attach("aimguy", "attach shell = right", "prop_mortar_ammunition", "TAG_WEAPON_RIGHT");
|
||
|
// addNotetrack_detach("aimguy", "detach shell = right", "prop_mortar_ammunition", "TAG_WEAPON_RIGHT");
|
||
|
// addNotetrack_customFunction("aimguy", "fire", ::fire);
|
||
|
addNotetrack_customFunction( "aimguy", "attach shell = right", ::attachMortar );
|
||
|
addNotetrack_customFunction( "aimguy", "detach shell = right", ::detachMortar );
|
||
|
}
|