#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 ); }