2024-12-11 11:28:08 +01:00

1377 lines
44 KiB
Plaintext

#include maps\mp\_utility;
#include common_scripts\utility;
init()
{
level.killstreakFuncs[ "odin_support" ] = ::tryUseOdin;
level.killstreakFuncs[ "odin_assault" ] = ::tryUseOdin;
level._effect[ "odin_clouds" ] = LoadFX( "vfx/gameplay/mp/killstreaks/odin/odin_parallax_clouds" );
level._effect[ "odin_fisheye" ] = LoadFX( "vfx/gameplay/screen_effects/vfx_scrnfx_odin_fisheye" );
level._effect[ "odin_targeting" ] = LoadFX( "vfx/gameplay/mp/killstreaks/odin/vfx_marker_odin_cyan" );
level.odinSettings = [];
level.odinSettings[ "odin_support" ] = SpawnStruct();
level.odinSettings[ "odin_support" ].timeOut = 60.0;
level.odinSettings[ "odin_support" ].streakName = "odin_support";
level.odinSettings[ "odin_support" ].vehicleInfo = "odin_mp";
level.odinSettings[ "odin_support" ].modelBase = "vehicle_odin_mp";
level.odinSettings[ "odin_support" ].teamSplash = "used_odin_support";
level.odinSettings[ "odin_support" ].voTimedOut = "odin_gone";
level.odinSettings[ "odin_support" ].voKillSingle = "odin_target_killed";
level.odinSettings[ "odin_support" ].voKillMulti = "odin_targets_killed";
level.odinSettings[ "odin_support" ].ui_num = 1; // let lua know what to show
level.odinSettings[ "odin_support" ].unavailable_string = &"KILLSTREAKS_ODIN_UNAVAILABLE";
// the weapon can be mutliple types
level.odinSettings[ "odin_support" ].weapon[ "airdrop" ] = SpawnStruct();
level.odinSettings[ "odin_support" ].weapon[ "airdrop" ].projectile = "odin_projectile_airdrop_mp";
level.odinSettings[ "odin_support" ].weapon[ "airdrop" ].rumble = "smg_fire";
level.odinSettings[ "odin_support" ].weapon[ "airdrop" ].ammoOmnvar = "ui_odin_airdrop_ammo";
level.odinSettings[ "odin_support" ].weapon[ "airdrop" ].airdropType = "airdrop_support";
level.odinSettings[ "odin_support" ].weapon[ "airdrop" ].reloadTimer = 20;
level.odinSettings[ "odin_support" ].weapon[ "airdrop" ].ui_num_fired = -1; // tell ui that we fired
level.odinSettings[ "odin_support" ].weapon[ "airdrop" ].voAirdrop = "odin_carepackage";
level.odinSettings[ "odin_support" ].weapon[ "airdrop" ].plr_ready_sound= "odin_carepack_ready";
level.odinSettings[ "odin_support" ].weapon[ "airdrop" ].plr_fire_sound = "odin_carepack_launch";
level.odinSettings[ "odin_support" ].weapon[ "marking" ] = SpawnStruct();
level.odinSettings[ "odin_support" ].weapon[ "marking" ].projectile = "odin_projectile_marking_mp";
level.odinSettings[ "odin_support" ].weapon[ "marking" ].rumble = "heavygun_fire";
level.odinSettings[ "odin_support" ].weapon[ "marking" ].ammoOmnvar = "ui_odin_marking_ammo";
level.odinSettings[ "odin_support" ].weapon[ "marking" ].reloadTimer = 4;
level.odinSettings[ "odin_support" ].weapon[ "marking" ].ui_num_fired = -1; // tell ui that we fired
level.odinSettings[ "odin_support" ].weapon[ "marking" ].voMarking = "odin_marking";
level.odinSettings[ "odin_support" ].weapon[ "marking" ].voMarkedSingle = "odin_marked";
level.odinSettings[ "odin_support" ].weapon[ "marking" ].voMarkedMulti = "odin_m_marked";
level.odinSettings[ "odin_support" ].weapon[ "marking" ].plr_ready_sound= "odin_flash_ready";
level.odinSettings[ "odin_support" ].weapon[ "marking" ].plr_fire_sound = "odin_flash_launch";
level.odinSettings[ "odin_support" ].weapon[ "smoke" ] = SpawnStruct();
level.odinSettings[ "odin_support" ].weapon[ "smoke" ].projectile = "odin_projectile_smoke_mp";
level.odinSettings[ "odin_support" ].weapon[ "smoke" ].rumble = "smg_fire";
level.odinSettings[ "odin_support" ].weapon[ "smoke" ].ammoOmnvar = "ui_odin_smoke_ammo";
level.odinSettings[ "odin_support" ].weapon[ "smoke" ].reloadTimer = 7;
level.odinSettings[ "odin_support" ].weapon[ "smoke" ].ui_num_fired = -1; // tell ui that we fired
level.odinSettings[ "odin_support" ].weapon[ "smoke" ].voSmoke = "odin_smoke";
level.odinSettings[ "odin_support" ].weapon[ "smoke" ].plr_ready_sound = "odin_smoke_ready";
level.odinSettings[ "odin_support" ].weapon[ "smoke" ].plr_fire_sound = "odin_smoke_launch";
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ] = SpawnStruct();
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ].projectile = "odin_projectile_smoke_mp";
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ].rumble = "heavygun_fire";
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ].ammoOmnvar = "ui_odin_juggernaut_ammo";
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ].juggType = "juggernaut_recon";
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ].reloadTimer = level.odinSettings[ "odin_support" ].timeOut; // make sure they can only call 1 in
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ].ui_num_fired = -1; // tell ui that we fired
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ].ui_num_move = -2; // tell ui to show jugg move text
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ].ui_num_dead = -3; // tell ui to show jugg dead text
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ].voJugg = "odin_moving";
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ].plr_ready_sound = "null";
level.odinSettings[ "odin_support" ].weapon[ "juggernaut" ].plr_fire_sound = "odin_jugg_launch";
level.odinSettings[ "odin_assault" ] = SpawnStruct();
level.odinSettings[ "odin_assault" ].timeOut = 60.0;
level.odinSettings[ "odin_assault" ].streakName = "odin_assault";
level.odinSettings[ "odin_assault" ].vehicleInfo = "odin_mp";
level.odinSettings[ "odin_assault" ].modelBase = "vehicle_odin_mp";
level.odinSettings[ "odin_assault" ].teamSplash = "used_odin_assault";
level.odinSettings[ "odin_assault" ].voTimedOut = "loki_gone";
level.odinSettings[ "odin_assault" ].voKillSingle = "odin_target_killed";
level.odinSettings[ "odin_assault" ].voKillMulti = "odin_targets_killed";
level.odinSettings[ "odin_assault" ].ui_num = 2; // let lua know what to show
level.odinSettings[ "odin_assault" ].unavailable_string = &"KILLSTREAKS_LOKI_UNAVAILABLE";
// the weapon can be mutliple types
level.odinSettings[ "odin_assault" ].weapon[ "airdrop" ] = SpawnStruct();
level.odinSettings[ "odin_assault" ].weapon[ "airdrop" ].projectile = "odin_projectile_airdrop_mp";
level.odinSettings[ "odin_assault" ].weapon[ "airdrop" ].rumble = "smg_fire";
level.odinSettings[ "odin_assault" ].weapon[ "airdrop" ].ammoOmnvar = "ui_odin_airdrop_ammo";
level.odinSettings[ "odin_assault" ].weapon[ "airdrop" ].airdropType = "airdrop_assault";
level.odinSettings[ "odin_assault" ].weapon[ "airdrop" ].reloadTimer = 20;
level.odinSettings[ "odin_assault" ].weapon[ "airdrop" ].ui_num_fired = -1; // tell ui that we fired
level.odinSettings[ "odin_assault" ].weapon[ "airdrop" ].voAirdrop = "odin_carepackage";
level.odinSettings[ "odin_assault" ].weapon[ "airdrop" ].plr_ready_sound= "odin_carepack_ready";
level.odinSettings[ "odin_assault" ].weapon[ "airdrop" ].plr_fire_sound = "odin_carepack_launch";
level.odinSettings[ "odin_assault" ].weapon[ "large_rod" ] = SpawnStruct();
level.odinSettings[ "odin_assault" ].weapon[ "large_rod" ].projectile = "odin_projectile_large_rod_mp";
level.odinSettings[ "odin_assault" ].weapon[ "large_rod" ].rumble = "heavygun_fire";
level.odinSettings[ "odin_assault" ].weapon[ "large_rod" ].ammoOmnvar = "ui_odin_marking_ammo";
level.odinSettings[ "odin_assault" ].weapon[ "large_rod" ].reloadTimer = 4;
level.odinSettings[ "odin_assault" ].weapon[ "large_rod" ].ui_num_fired = -2; // tell ui that we fired
level.odinSettings[ "odin_assault" ].weapon[ "large_rod" ].plr_ready_sound = "null";
level.odinSettings[ "odin_assault" ].weapon[ "large_rod" ].plr_fire_sound = "ac130_105mm_fire";
level.odinSettings[ "odin_assault" ].weapon[ "large_rod" ].npc_fire_sound = "ac130_105mm_fire_npc";
level.odinSettings[ "odin_assault" ].weapon[ "small_rod" ] = SpawnStruct();
level.odinSettings[ "odin_assault" ].weapon[ "small_rod" ].projectile = "odin_projectile_small_rod_mp";
level.odinSettings[ "odin_assault" ].weapon[ "small_rod" ].rumble = "smg_fire";
level.odinSettings[ "odin_assault" ].weapon[ "small_rod" ].ammoOmnvar = "ui_odin_smoke_ammo";
level.odinSettings[ "odin_assault" ].weapon[ "small_rod" ].reloadTimer = 2;
level.odinSettings[ "odin_assault" ].weapon[ "small_rod" ].ui_num_fired = -2; // tell ui that we fired
level.odinSettings[ "odin_assault" ].weapon[ "small_rod" ].plr_ready_sound = "null";
level.odinSettings[ "odin_assault" ].weapon[ "small_rod" ].plr_fire_sound = "ac130_40mm_fire";
level.odinSettings[ "odin_assault" ].weapon[ "small_rod" ].npc_fire_sound = "ac130_40mm_fire_npc";
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ] = SpawnStruct();
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ].projectile = "odin_projectile_smoke_mp";
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ].rumble = "heavygun_fire";
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ].ammoOmnvar = "ui_odin_juggernaut_ammo";
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ].juggType = "juggernaut";
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ].reloadTimer = level.odinSettings[ "odin_assault" ].timeOut; // make sure they can only call 1 in
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ].ui_num_fired = -1; // tell ui that we fired
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ].ui_num_move = -2; // tell ui to show jugg move text
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ].ui_num_dead = -3; // tell ui to show jugg dead text
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ].voJugg = "odin_moving";
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ].plr_ready_sound = "null";
level.odinSettings[ "odin_assault" ].weapon[ "juggernaut" ].plr_fire_sound = "odin_jugg_launch";
// check to see if the mesh already exists, b/c heli_pilot sets this up in the init
if( !IsDefined( level.heli_pilot_mesh ) )
{
// throw the mesh way up into the air, the gdt entry for the vehicle must match
level.heli_pilot_mesh = GetEnt( "heli_pilot_mesh", "targetname" );
if( !IsDefined( level.heli_pilot_mesh ) )
PrintLn( "heli_pilot_mesh doesn't exist in this level: " + level.script );
else
level.heli_pilot_mesh.origin += getHeliPilotMeshOffset();
}
maps\mp\agents\_agents::wait_till_agent_funcs_defined();
level.agent_funcs["odin_juggernaut"] = level.agent_funcs["player"];
level.agent_funcs["odin_juggernaut"]["think"] = ::empty_init_func;
level.odin_marking_flash_radius_max = 800; // straight from the gdt entry
level.odin_marking_flash_radius_min = 200; // straight from the gdt entry
level.active_odin = [];
/#
SetDevDvarIfUninitialized( "scr_odin_support_timeout", 60.0 );
SetDevDvarIfUninitialized( "scr_odin_assault_timeout", 60.0 );
#/
}
tryUseOdin( lifeId, streakName )
{
if ( IsDefined( self.underWater ) && self.underWater )
{
return false;
}
odinType = streakName;
numIncomingVehicles = 1;
if( currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + numIncomingVehicles >= maxVehiclesAllowed() )
{
self IPrintLnBold( &"KILLSTREAKS_TOO_MANY_VEHICLES" );
return false;
}
// only allowing one of each odin
if( IsDefined( level.active_odin[ odinType ] ) )
{
self IPrintLnBold( level.odinSettings[ odinType ].unavailable_string );
return false;
}
// increment the faux vehicle count before we spawn the vehicle so no other vehicles try to spawn
incrementFauxVehicleCount();
odin = createOdin( odinType );
if( !IsDefined( odin ) )
{
// decrement the faux vehicle count since this failed to spawn
decrementFauxVehicleCount();
return false;
}
result = self startOdin( odin );
if( !IsDefined( result ) )
result = false;
return result;
}
watchHostMigrationFinishedInit( player )
{
player endon( "disconnect" );
player endon( "joined_team" );
player endon( "joined_spectators" );
player endon( "killstreak_disowned" );
level endon( "game_ended" );
self endon( "death" );
for (;;)
{
level waittill( "host_migration_end" );
player SetClientOmnvar( "ui_odin", level.odinSettings[ self.odinType ].ui_num );
player ThermalVisionFOFOverlayOn();
PlayFXOnTag( level._effect[ "odin_targeting" ], self.targeting_marker, "tag_origin" );
self.targeting_marker ShowToPlayer( player );
}
}
createOdin( odinType ) // self == player
{
startPos = ( self.origin * ( 1, 1, 0 ) ) + ( ( level.heli_pilot_mesh.origin - getHeliPilotMeshOffset() ) * ( 0, 0, 1 ) );
startAng = ( 0, 0, 0 );
odin = SpawnHelicopter( self, startPos, startAng, level.odinSettings[ odinType ].vehicleInfo, level.odinSettings[ odinType ].modelBase );
if( !IsDefined( odin ) )
return;
odin.speed = 40;
odin.owner = self;
odin.team = self.team;
odin.odinType = odinType;
level.active_odin[ odinType ] = true;
self.odin = odin;
odin thread odin_watchDeath();
odin thread odin_watchTimeout();
odin thread odin_watchOwnerLoss();
odin thread odin_watchRoundEnd();
odin thread odin_watchTargeting();
odin thread odin_watchObjectiveCamera();
odin thread odin_watchOutlines();
odin thread odin_watchPlayerKilled();
odin thread odin_dialog_killed_player();
odin thread odin_onPlayerConnect();
odin.owner maps\mp\_matchdata::logKillstreakEvent( level.odinSettings[ odinType ].streakName, startPos );
return odin;
}
startOdin( odin ) // self == player
{
level endon( "game_ended" );
odin endon( "death" );
self.restoreAngles = VectorToAngles( AnglesToForward( self.angles ) );
self odin_set_using( odin );
if( GetDvarInt( "camera_thirdPerson" ) )
self setThirdPersonDOF( false );
self thread watchIntroCleared( odin );
self freezeControlsWrapper( true );
// for the zoom we want to go higher than where the odin will be and then come back
self odin_zoom_up( odin );
self thread maps\mp\killstreaks\_juggernaut::disableJuggernaut();
result = self maps\mp\killstreaks\_killstreaks::initRideKillstreak( odin.odinType );
if( result != "success" )
{
if( IsDefined( self.disabledWeapon ) && self.disabledWeapon )
self _enableWeapon();
odin notify( "death" );
return false;
}
self freezeControlsWrapper( false );
// override the client audio trigger and set a new one
// TODO: waiting for code to move the audio functions to MP
//self SetClientTriggerAudioZone( "odin_loki_satellite", 0.25 );
// link the heli into the mesh and give them control
self RemoteControlVehicle( odin );
odin thread watchHostMigrationFinishedInit( self );
odin.odin_overlay_ent = SpawnFXForClient( level._effect[ "odin_fisheye" ], self GetEye(), self );
TriggerFX( odin.odin_overlay_ent );
odin.odin_overlay_ent SetFXKillDefOnDelete();
level thread teamPlayerCardSplash( level.odinSettings[ odin.odinType ].teamSplash, self );
self ThermalVisionFOFOverlayOn();
self thread waitAndOutlineOwner( odin );
return true;
}
waitAndOutlineOwner( odin ) // self == player
{
self endon( "disconnect" );
odin endon( "death" );
// when you first enter the odin you'll sometimes see your outline before you're fully in it, so this waits before turning on the outline
wait( 1.0 );
id = outlineEnableForPlayer( self, "cyan", self, false, "killstreak" );
odin thread removeOutline( id, self );
}
odin_zoom_up( odin )
{
ent = Spawn( "script_model", odin.origin + ( 0, 0, 3000 ) );
ent.angles = VectorToAngles( ( 0, 0, 1 ) );
ent SetModel( "tag_origin" );
ent thread waitAndDelete( 5 );
// need to check for collision above the player so we know which cinematic camera to use
zoom_file = "odin_zoom_up";
lead = ent GetEntityNumber();
support = self GetEntityNumber();
bullet_trace = BulletTrace( self.origin, odin.origin, false, self );
if( bullet_trace[ "surfacetype" ] != "none" )
{
zoom_file = "odin_zoom_down";
lead = odin GetEntityNumber();
support = ent GetEntityNumber();
}
players_to_zoom = array_add( self get_players_watching(), self );
foreach( player in players_to_zoom )
{
player SetClientOmnvar( "cam_scene_name", zoom_file );
player SetClientOmnvar( "cam_scene_lead", lead );
player SetClientOmnvar( "cam_scene_support", support );
player thread clouds();
}
}
waitAndDelete( time )
{
self endon( "death" );
level endon( "game_ended" );
wait( time );
self delete();
}
clouds() // self == player
{
level endon( "game_ended" );
ent = Spawn( "script_model", self.origin + ( 0, 0, 250 ) );
ent.angles = VectorToAngles( ( 0, 0, 1 ) );
ent SetModel( "tag_origin" );
ent thread waitAndDelete( 2 );
wait( 0.1 );
PlayFXOnTagForClients( level._effect[ "odin_clouds" ], ent, "tag_origin", self );
}
odin_set_using( odin ) // self == player
{
self setUsingRemote( odin.odinType );
self.odin = odin;
}
odin_clear_using( odin ) // self == player
{
odin.odin_juggernautUseTime = undefined;
odin.odin_markingUseTime = undefined;
odin.odin_smokeUseTime = undefined;
odin.odin_airdropUseTime = undefined;
odin.odin_largeRodUseTime = undefined;
odin.odin_smallRodUseTime = undefined;
if ( IsDefined(self) )
{
self clearUsingRemote();
self.odin = undefined;
}
}
watchIntroCleared( odin ) // self == player
{
self endon( "disconnect" );
self endon( "joined_team" );
self endon( "joined_spectators" );
level endon( "game_ended" );
odin endon( "death" );
self waittill( "intro_cleared" );
// self SetClientOmnvar( "ui_odin", 0 );
// wait( 0.75 );
self SetClientOmnvar( "ui_odin", level.odinSettings[ odin.odinType ].ui_num );
// do this here to make sure we are in the killstreak before letting them leave, it was causing a bug where the player could get stuck on a black screen
self watchEarlyExit( odin );
}
//
// state trackers
//
odin_waitForDoneFiring( time_out ) // self == odin
{
while( IsDefined( self.is_firing ) && time_out > 0 )
{
wait( 0.05 );
time_out -= 0.05;
}
}
odin_watchDeath() // self == odin
{
level endon( "game_ended" );
self endon( "gone" );
self waittill( "death" );
if( IsDefined( self.owner ) )
self.owner odin_EndRide( self );
cleanup_ents();
self odin_waitForDoneFiring( 3.0 );
// decrement the faux vehicle count right before it is deleted this way we know for sure it is gone
decrementFauxVehicleCount();
// reset the level variable so we know we can call another one out
level.active_odin[ self.odinType ] = undefined;
self delete();
}
odin_watchTimeout() // self == odin
{
level endon ( "game_ended" );
self endon( "death" );
self.owner endon( "disconnect" );
self.owner endon( "joined_team" );
self.owner endon( "joined_spectators" );
config = level.odinSettings[ self.odinType ];
timeout = config.timeOut;
/#
timeout = GetDvarFloat( "scr_" + self.odinType + "_timeout" );
#/
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( timeout );
self thread odin_leave();
}
odin_watchOwnerLoss() // self == odin
{
level endon ( "game_ended" );
self endon( "death" );
self endon( "leaving" );
self.owner waittill_any( "disconnect", "joined_team", "joined_spectators" );
// leave
self thread odin_leave();
}
odin_watchObjectiveCamera() // self == odin
{
level endon ( "game_ended" );
self endon( "death" );
self endon( "leaving" );
self.owner endon( "disconnect" );
self.owner endon( "joined_team" );
self.owner endon( "joined_spectators" );
level waittill ( "objective_cam" );
// leave
self thread odin_leave();
}
odin_watchRoundEnd() // self == odin
{
self endon( "death" );
self endon( "leaving" );
self.owner endon( "disconnect" );
self.owner endon( "joined_team" );
self.owner endon( "joined_spectators" );
level waittill_any( "round_end_finished", "game_ended" );
// leave
self thread odin_leave();
}
odin_leave() // self == odin
{
self endon( "death" );
self notify( "leaving" );
config = level.odinSettings[ self.odinType ];
leaderDialog( config.voTimedOut );
if( IsDefined( self.owner ) )
self.owner odin_EndRide( self );
// remove
self notify( "gone" );
cleanup_ents();
self odin_waitForDoneFiring( 3.0 );
// decrement the faux vehicle count right before it is deleted this way we know for sure it is gone
decrementFauxVehicleCount();
// reset the level variable so we know we can call another one out
level.active_odin[ self.odinType ] = undefined;
self delete();
}
odin_EndRide( odin ) // self == player
{
if( IsDefined( odin ) )
{
self SetClientOmnvar( "ui_odin", -1 );
odin notify( "end_remote" );
self notify( "odin_ride_ended" );
self odin_clear_using( odin );
if( GetDvarInt( "camera_thirdPerson" ) )
self setThirdPersonDOF( true );
self ThermalVisionFOFOverlayOff();
self RemoteControlVehicleOff( odin );
self SetPlayerAngles( self.restoreAngles );
self thread odin_FreezeBuffer();
// make sure all local sounds are stopped, so we don't hear them while on the ground putting away the laptop
self StopLocalSound( "odin_negative_action" );
self StopLocalSound( "odin_positive_action" );
foreach( odin_weapon in level.odinSettings[ odin.odinType ].weapon )
{
if( IsDefined( odin_weapon.plr_ready_sound ) )
self StopLocalSound( odin_weapon.plr_ready_sound );
if( IsDefined( odin_weapon.plr_fire_sound ) )
self StopLocalSound( odin_weapon.plr_fire_sound );
}
if( IsDefined( odin.juggernaut ) )
odin.juggernaut maps\mp\bots\_bots_strategy::bot_guard_player( self, 350 );
}
}
odin_FreezeBuffer()
{
self endon( "disconnect" );
self endon( "death" );
level endon( "game_ended" );
self freezeControlsWrapper( true );
wait( 0.5 );
self freezeControlsWrapper( false );
}
odin_watchTargeting() // self == odin
{
self endon( "death" );
level endon( "game_ended" );
owner = self.owner;
owner endon( "disconnect" );
// show a marker in the ground
startTrace = owner GetViewOrigin();
endTrace = startTrace + ( AnglesToForward( self GetTagAngles( "tag_player" ) ) * 10000 );
markerPos = BulletTrace( startTrace, endTrace, false, self );
marker = Spawn( "script_model", markerPos[ "position" ] );
marker.angles = VectorToAngles( ( 0, 0, 1 ) );
marker SetModel( "tag_origin" );
self.targeting_marker = marker;
marker endon("death");
// keep it on the ground
trace = BulletTrace( marker.origin + ( 0, 0, 50 ), marker.origin + ( 0, 0, -100 ), false, marker );
marker.origin = trace[ "position" ] + ( 0, 0, 50 );
// only the owner can see the targeting
marker Hide();
marker ShowToPlayer( owner );
marker childthread monitorMarkerVisibility(owner);
self thread showFX();
self thread watchAirdropUse();
self thread watchJuggernautUse();
switch( self.odinType )
{
case "odin_support":
self thread watchSmokeUse();
self thread watchMarkingUse();
break;
case "odin_assault":
self thread watchLargeRodUse();
self thread watchSmallRodUse();
break;
}
self SetOtherEnt( marker );
/*
while( true )
{
startTrace = owner GetViewOrigin();
endTrace = startTrace + ( AnglesToForward( self GetTagAngles( "tag_player" ) ) * 10000 );
markerPos = BulletTrace( startTrace, endTrace, false, self );
marker.origin = markerPos[ "position" ];
// keep it on the ground
trace = BulletTrace( marker.origin + ( 0, 0, 50 ), marker.origin + ( 0, 0, -100 ), false, marker );
marker.origin = trace[ "position" ] + ( 0, 0, 10 );
wait( 0.05 );
}
*/
}
monitorMarkerVisibility( owner ) // self == targeting marker
{
wait(1.5);
prev_players_watching = [];
while(1)
{
current_players_watching = owner get_players_watching();
foreach( player in prev_players_watching )
{
if ( !array_contains( current_players_watching, player ) )
{
// Player is no longer watching
prev_players_watching = array_remove(prev_players_watching, player);
self Hide();
self ShowToPlayer(owner);
}
}
foreach ( player in current_players_watching )
{
self ShowToPlayer(player);
if ( !array_contains( prev_players_watching, player ) )
{
// New player watching, restart the fx so he can see them
prev_players_watching = array_add( prev_players_watching, player );
StopFXOnTag( level._effect[ "odin_targeting" ], self, "tag_origin" );
wait(0.05);
PlayFXOnTag( level._effect[ "odin_targeting" ], self, "tag_origin" );
}
}
wait(0.25);
}
}
watchAirdropUse() // self == odin
{
self endon( "death" );
level endon( "game_ended" );
owner = self.owner;
owner endon( "disconnect" );
weaponStruct = level.odinSettings[ self.odinType ].weapon[ "airdrop" ];
self.odin_airdropUseTime = 0;
owner SetClientOmnvar( weaponStruct.ammoOmnvar, level.odinSettings[ self.odinType ].ui_num );
if ( !IsAI(owner) ) // Bots handle this internally
owner NotifyOnPlayerCommand( "airdrop_action", "+smoke" );
// watch for button presses
while( true )
{
owner waittill( "airdrop_action" );
if( IsDefined( level.hostMigrationTimer ) )
continue;
// if the owner doesn't have an odin then we need to get out
// the odin doesn't die right away once you exit it so this makes sure we don't do something we shouldn't be doing
if( !IsDefined( owner.odin ) )
return;
if( GetTime() >= self.odin_airdropUseTime )
{
if( level.teamBased )
leaderDialog( weaponStruct.voAirdrop, self.team );
else
owner leaderDialogOnPlayer( weaponStruct.voAirdrop );
self.odin_airdropUseTime = self odin_fireWeapon( "airdrop" );
weaponStruct = level.odinSettings[ self.odinType ].weapon[ "airdrop" ];
level thread maps\mp\killstreaks\_airdrop::doFlyBy( owner, self.targeting_marker.origin, randomFloat( 360 ), weaponStruct.airdropType );
}
else
owner _playLocalSound( "odin_negative_action" );
wait( 1.0 );
}
}
watchSmokeUse() // self == odin
{
self endon( "death" );
level endon( "game_ended" );
owner = self.owner;
owner endon( "disconnect" );
weaponStruct = level.odinSettings[ self.odinType ].weapon[ "smoke" ];
self.odin_smokeUseTime = 0;
owner SetClientOmnvar( weaponStruct.ammoOmnvar, level.odinSettings[ self.odinType ].ui_num );
if ( !IsAI(owner) ) // Bots handle this internally
{
owner NotifyOnPlayerCommand( "smoke_action", "+speed_throw" );
owner NotifyOnPlayerCommand( "smoke_action", "+ads_akimbo_accessible" );
if( !level.console )
{
owner NotifyOnPlayerCommand( "smoke_action", "+toggleads_throw" );
}
}
// watch for button presses
while( true )
{
owner waittill( "smoke_action" );
if( IsDefined( level.hostMigrationTimer ) )
continue;
// if the owner doesn't have an odin then we need to get out
// the odin doesn't die right away once you exit it so this makes sure we don't do something we shouldn't be doing
if( !IsDefined( owner.odin ) )
return;
if( GetTime() >= self.odin_smokeUseTime )
{
if( level.teamBased )
leaderDialog( weaponStruct.voSmoke, self.team );
else
owner leaderDialogOnPlayer( weaponStruct.voSmoke );
self.odin_smokeUseTime = self odin_fireWeapon( "smoke" );
}
else
owner _playLocalSound( "odin_negative_action" );
wait( 1.0 );
}
}
watchMarkingUse() // self == odin
{
self endon( "death" );
level endon( "game_ended" );
owner = self.owner;
owner endon( "disconnect" );
weaponStruct = level.odinSettings[ self.odinType ].weapon[ "marking" ];
self.odin_markingUseTime = 0;
owner SetClientOmnvar( weaponStruct.ammoOmnvar, level.odinSettings[ self.odinType ].ui_num );
if ( !IsAI(owner) ) // Bots handle this internally
{
owner NotifyOnPlayerCommand( "marking_action", "+attack" );
owner NotifyOnPlayerCommand( "marking_action", "+attack_akimbo_accessible" ); // support accessibility control scheme
}
// watch for button presses
while( true )
{
owner waittill( "marking_action" );
if( IsDefined( level.hostMigrationTimer ) )
continue;
// if the owner doesn't have an odin then we need to get out
// the odin doesn't die right away once you exit it so this makes sure we don't do something we shouldn't be doing
if( !IsDefined( owner.odin ) )
return;
if( GetTime() >= self.odin_markingUseTime )
{
self.odin_markingUseTime = self odin_fireWeapon( "marking" );
self thread doMarkingFlash( self.targeting_marker.origin + ( 0, 0, 10 ) );
}
else
owner _playLocalSound( "odin_negative_action" );
wait( 1.0 );
}
}
watchJuggernautUse() // self == odin
{
self endon( "death" );
level endon( "game_ended" );
owner = self.owner;
owner endon( "disconnect" );
owner endon( "juggernaut_dead" );
weaponStruct = level.odinSettings[ self.odinType ].weapon[ "juggernaut" ];
self.odin_juggernautUseTime = 0;
owner SetClientOmnvar( weaponStruct.ammoOmnvar, level.odinSettings[ self.odinType ].ui_num );
if ( !IsAI(owner) ) // Bots handle this internally
owner NotifyOnPlayerCommand( "juggernaut_action", "+frag" );
// watch for button presses
while( true )
{
owner waittill( "juggernaut_action" );
if( IsDefined( level.hostMigrationTimer ) )
continue;
// if the owner doesn't have an odin then we need to get out
// the odin doesn't die right away once you exit it so this makes sure we don't do something we shouldn't be doing
if( !IsDefined( owner.odin ) )
return;
if( GetTime() >= self.odin_juggernautUseTime )
{
node = getJuggStartingPathNode( self.targeting_marker.origin );
if( IsDefined( node ) )
{
self.odin_juggernautUseTime = self odin_fireWeapon( "juggernaut" );
self thread waitAndSpawnJugg( node );
}
else
owner _playLocalSound( "odin_negative_action" );
}
else if( IsDefined( self.juggernaut ) )
{
node = getJuggMovingPathNode( self.targeting_marker.origin );
if( IsDefined( node ) )
{
// since the juggernaut is already out, this will mark the position that he will guard
owner leaderDialogOnPlayer( weaponStruct.voJugg );
owner _playLocalSound( "odin_positive_action" );
owner PlayRumbleOnEntity( "pistol_fire" );
self.juggernaut maps\mp\bots\_bots_strategy::bot_protect_point( node.origin, 128 );
owner SetClientOmnvar( weaponStruct.ammoOmnvar, level.odinSettings[ self.odinType ].ui_num );
}
else
owner _playLocalSound( "odin_negative_action" );
}
wait( 1.1 );
// set the ammo to 3 so it'll show the move text
if( IsDefined( self.juggernaut ) )
owner SetClientOmnvar( weaponStruct.ammoOmnvar, weaponStruct.ui_num_move );
}
}
watchLargeRodUse() // self == odin
{
self endon( "death" );
level endon( "game_ended" );
owner = self.owner;
owner endon( "disconnect" );
weaponStruct = level.odinSettings[ self.odinType ].weapon[ "large_rod" ];
self.odin_LargeRodUseTime = 0;
owner SetClientOmnvar( weaponStruct.ammoOmnvar, level.odinSettings[ self.odinType ].ui_num );
if ( !IsAI(owner) ) // Bots handle this internally
{
owner NotifyOnPlayerCommand( "large_rod_action", "+attack" );
owner NotifyOnPlayerCommand( "large_rod_action", "+attack_akimbo_accessible" ); // support accessibility control scheme
}
// watch for button presses
while( true )
{
owner waittill( "large_rod_action" );
if( IsDefined( level.hostMigrationTimer ) )
continue;
// if the owner doesn't have an odin then we need to get out
// the odin doesn't die right away once you exit it so this makes sure we don't do something we shouldn't be doing
if( !IsDefined( owner.odin ) )
return;
if( GetTime() >= self.odin_LargeRodUseTime )
{
self.odin_largeRodUseTime = self odin_fireWeapon( "large_rod" );
}
else
owner _playLocalSound( "odin_negative_action" );
wait( 1.0 );
}
}
watchSmallRodUse() // self == odin
{
self endon( "death" );
level endon( "game_ended" );
owner = self.owner;
owner endon( "disconnect" );
weaponStruct = level.odinSettings[ self.odinType ].weapon[ "small_rod" ];
self.odin_smallRodUseTime = 0;
owner SetClientOmnvar( weaponStruct.ammoOmnvar, level.odinSettings[ self.odinType ].ui_num );
if ( !IsAI(owner) ) // Bots handle this internally
{
owner NotifyOnPlayerCommand( "small_rod_action", "+speed_throw" );
owner NotifyOnPlayerCommand( "small_rod_action", "+ads_akimbo_accessible" );
if( !level.console )
{
owner NotifyOnPlayerCommand( "small_rod_action", "+toggleads_throw" );
}
}
// watch for button presses
while( true )
{
owner waittill( "small_rod_action" );
if( IsDefined( level.hostMigrationTimer ) )
continue;
// if the owner doesn't have an odin then we need to get out
// the odin doesn't die right away once you exit it so this makes sure we don't do something we shouldn't be doing
if( !IsDefined( owner.odin ) )
return;
if( GetTime() >= self.odin_smallRodUseTime )
{
self.odin_smallRodUseTime = self odin_fireWeapon( "small_rod" );
}
else
owner _playLocalSound( "odin_negative_action" );
wait( 1.0 );
}
}
odin_fireWeapon( weaponType ) // self == odin
{
self.is_firing = true;
owner = self.owner;
weaponStruct = level.odinSettings[ self.odinType ].weapon[ weaponType ];
forward_dir = AnglesToForward( owner GetPlayerAngles() );
start = self.origin + (forward_dir * 100);
owner SetClientOmnvar( weaponStruct.ammoOmnvar, weaponStruct.ui_num_fired );
self thread watchReload( weaponStruct );
target_pos = self.targeting_marker.origin;
reload_time = ( GetTime() + ( weaponStruct.reloadTimer * 1000 ) );
// the large and small rods will act differently
// do the firing sound, pause, then fire to give it a feeling of distance
if( weaponType == "large_rod" )
{
wait( 0.5 ); // delay for sound
owner PlayRumbleOnEntity( weaponStruct.rumble );
Earthquake (0.3, 1.5, self.origin, 1000);
owner PlaySoundToPlayer( weaponStruct.plr_fire_sound, owner );
playSoundAtPos( self.origin, weaponStruct.npc_fire_sound );
wait( 1.5 );
}
else if( weaponType == "small_rod" )
{
wait( 0.5 ); // delay for sound
owner PlayRumbleOnEntity( weaponStruct.rumble );
Earthquake (0.2, 1, self.origin, 1000);
owner PlaySoundToPlayer( weaponStruct.plr_fire_sound, owner );
playSoundAtPos( self.origin, weaponStruct.npc_fire_sound );
wait( 0.3 );
}
else
{
if( IsDefined( weaponStruct.plr_fire_sound ) )
owner PlaySoundToPlayer( weaponStruct.plr_fire_sound, owner );
if( IsDefined( weaponStruct.npc_fire_sound ) )
playSoundAtPos( self.origin, weaponStruct.npc_fire_sound );
owner PlayRumbleOnEntity( weaponStruct.rumble );
}
projectile = MagicBullet( weaponStruct.projectile, start, target_pos, owner );
projectile.type = "odin";
projectile thread watchExplosion( weaponType );
if( weaponType == "smoke" || weaponType == "juggernaut" || weaponType == "large_rod" )
level notify( "smoke", projectile, weaponStruct.projectile );
self.is_firing = undefined;
return reload_time;
}
watchExplosion( weaponType ) // self == projectile
{
self waittill( "explode", position );
if( weaponType == "small_rod" )
{
PlayRumbleOnPosition( "grenade_rumble", position );
Earthquake( 0.7, 1.0, position, 1000 );
}
else if( weaponType == "large_rod" )
{
PlayRumbleOnPosition( "artillery_rumble", position );
Earthquake( 1.0, 1.0, position, 2000 );
}
}
getJuggStartingPathNode( pos ) // self == odin
{
// the pos can be undefined sometimes when the odin is dying
// because we do a slight wait before completely removing the odin so the projectiles still hit the ground once you've left it
if( !IsDefined( pos ) )
return;
// try to spawn the agent on a path node near the marker
nearestPathNode = GetNodesInRadiusSorted( pos, 256, 0, 128, "Path" );
if( !IsDefined( nearestPathNode[ 0 ] ) )
return;
return nearestPathNode[ 0 ];
}
getJuggMovingPathNode( pos ) // self == odin
{
// the pos can be undefined sometimes when the odin is dying
// because we do a slight wait before completely removing the odin so the projectiles still hit the ground once you've left it
if( !IsDefined( pos ) )
return;
// try to move the agent to a spot on the pathgrid near the marker
nearestPathNode = GetNodesInRadiusSorted( pos, 128, 0, 64, "Path" );
if( !IsDefined( nearestPathNode[ 0 ] ) )
return;
return nearestPathNode[ 0 ];
}
waitAndSpawnJugg( nearestPathNode ) // self == odin
{
self endon( "death" );
level endon( "game_ended" );
owner = self.owner;
owner endon( "disconnect" );
pos = self.targeting_marker.origin;
// waiting for the smoke to rise
wait( 3.0 );
agent = maps\mp\agents\_agents::add_humanoid_agent( "odin_juggernaut", owner.team, "class1", nearestPathNode.origin, VectorToAngles( pos - nearestPathNode.origin ), owner, false, false, "veteran" );
if( IsDefined( agent ) )
{
weaponStruct = level.odinSettings[ self.odinType ].weapon[ "juggernaut" ];
agent thread maps\mp\killstreaks\_juggernaut::giveJuggernaut( weaponStruct.juggType );
agent thread maps\mp\killstreaks\_agent_killstreak::sendAgentWeaponNotify();
agent maps\mp\bots\_bots_strategy::bot_protect_point( nearestPathNode.origin, 128 );
self.juggernaut = agent;
self thread watchJuggernautDeath();
owner SetClientOmnvar( weaponStruct.ammoOmnvar, weaponStruct.ui_num_move );
id = outlineEnableForPlayer( agent, "cyan", self.owner, false, "killstreak" );
self thread removeOutline( id, agent );
agent _setNameplateMaterial( "player_name_bg_green_agent", "player_name_bg_red_agent" );
}
else
{
// agent wasn't able to spawn, let the user know
owner iPrintLnBold( &"KILLSTREAKS_AGENT_MAX" );
}
}
watchJuggernautDeath() // self == odin
{
self endon( "death" );
level endon( "game_ended" );
self.juggernaut waittill( "death" );
self.owner notify( "juggernaut_dead" );
weaponStruct = level.odinSettings[ self.odinType ].weapon[ "juggernaut" ];
self.owner SetClientOmnvar( weaponStruct.ammoOmnvar, weaponStruct.ui_num_dead );
self.juggernaut = undefined;
}
showFX() // self == odin
{
self endon( "death" );
wait( 1.0 );
PlayFXOnTag( level._effect[ "odin_targeting" ], self.targeting_marker, "tag_origin" );
}
watchReload( weaponStruct ) // self == odin
{
self endon( "death" );
level endon( "game_ended" );
owner = self.owner;
owner endon( "disconnect" );
owner endon( "odin_ride_ended" );
dvar = weaponStruct.ammoOmnvar;
time = weaponStruct.reloadTimer;
plr_ready_sound = weaponStruct.plr_ready_sound;
ui_num = level.odinSettings[ self.odinType ].ui_num;
wait( time );
// if the owner doesn't have an odin then we need to get out
// the odin doesn't die right away once you exit it so this makes sure we don't do something we shouldn't be doing
if( !IsDefined( owner.odin ) )
return;
if( IsDefined( plr_ready_sound ) )
owner _playLocalSound( plr_ready_sound );
owner SetClientOmnvar( dvar, ui_num );
}
// this is copied from the doNineBang() function, it needed its own flavor
doMarkingFlash( pos ) // self == odin
{
level endon( "game_ended" );
attacker = self.owner;
radius_max_sq = level.odin_marking_flash_radius_max * level.odin_marking_flash_radius_max;
radius_min_sq = level.odin_marking_flash_radius_min * level.odin_marking_flash_radius_min;
viewHeightStanding = 60;
viewHeightCrouching = 40;
viewHeightProne = 11;
//playSoundAtPos( pos, "flashbang_explode_default" );
// get players within the radius
num_marked = 0;
foreach( player in level.participants )
{
if( !isReallyAlive( player ) || player.sessionstate != "playing" )
continue;
if( level.teamBased && player.team == self.team )
continue;
// first make sure they are within distance
dist = DistanceSquared( pos, player.origin );
if( dist > radius_max_sq )
continue;
stance = player GetStance();
viewOrigin = player.origin;
switch( stance )
{
case "stand":
viewOrigin = ( viewOrigin[0], viewOrigin[1], viewOrigin[2] + viewHeightStanding );
break;
case "crouch":
viewOrigin = ( viewOrigin[0], viewOrigin[1], viewOrigin[2] + viewHeightCrouching );
break;
case "prone":
viewOrigin = ( viewOrigin[0], viewOrigin[1], viewOrigin[2] + viewHeightProne );
break;
}
// now make sure they can be hit by it
if( !BulletTracePassed( pos, viewOrigin, false, player ) )
continue;
if ( dist <= radius_min_sq )
percent_distance = 1.0;
else
percent_distance = 1.0 - ( dist - radius_min_sq ) / ( radius_max_sq - radius_min_sq );
forward = AnglesToForward( player GetPlayerAngles() );
toBlast = pos - viewOrigin;
toBlast = VectorNormalize( toBlast );
percent_angle = 0.5 * ( 1.0 + VectorDot( forward, toBlast ) );
extra_duration = 1; // first blast is 1 sec, each after is 2 sec
player notify( "flashbang", pos, percent_distance, percent_angle, attacker, extra_duration );
num_marked++;
// show outlined to the team (unless they have blindeye equipped
if ( !enemyNotAffectedByOdinOutline( player ) )
{
if( level.teamBased )
id = outlineEnableForTeam( player, "orange", self.team, false, "killstreak" );
else
id = outlineEnableForPlayer( player, "orange", self.owner, false, "killstreak" );
self thread removeOutline( id, player, 3.0 );
}
}
weaponStruct = level.odinSettings[ self.odinType ].weapon[ "marking" ];
if( num_marked == 1 )
{
if( level.teamBased )
leaderDialog( weaponStruct.voMarkedSingle, self.team );
else
attacker leaderDialogOnPlayer( weaponStruct.voMarkedSingle );
}
else if( num_marked > 1 )
{
if( level.teamBased )
leaderDialog( weaponStruct.voMarkedMulti, self.team );
else
attacker leaderDialogOnPlayer( weaponStruct.voMarkedMulti );
}
ents = maps\mp\gametypes\_weapons::getEMPDamageEnts( pos, 512, false );
foreach ( ent in ents )
{
if ( isDefined( ent.owner ) && !maps\mp\gametypes\_weapons::friendlyFireCheck( self.owner, ent.owner ) )
continue;
ent notify( "emp_damage", self.owner, 8.0 );
}
}
applyOutline( player ) // self == odin
{
if( level.teamBased && player.team == self.team )
return;
else if( !level.teamBased && player == self.owner )
return;
if( enemyNotAffectedByOdinOutline(player) )
return;
id = outlineEnableForPlayer( player, "orange", self.owner, true, "killstreak" );
self thread removeOutline( id, player );
}
enemyNotAffectedByOdinOutline( enemy )
{
return enemy _hasPerk( "specialty_noplayertarget" );
}
removeOutline( id, ent, time_out ) // self == odin
{
if( IsDefined( ent ) )
ent endon( "disconnect" );
level endon( "game_ended" );
wait_array = [ "leave", "death" ];
if( IsDefined( time_out ) )
self waittill_any_in_array_or_timeout_no_endon_death( wait_array, time_out );
else
self waittill_any_in_array_return_no_endon_death( wait_array );
if( IsDefined( ent ) )
outlineDisable( id, ent );
}
odin_watchOutlines() // self == odin
{
self endon( "death" );
level endon( "game_ended" );
// highlight all enemies in the world that can't be seen
foreach( player in level.participants )
{
self applyOutline( player );
}
}
odin_watchPlayerKilled() // self == odin
{
self endon( "death" );
level endon( "game_ended" );
self.enemiesKilledInTimeWindow = 0;
while( true )
{
level waittill( "odin_killed_player", victim );
self.enemiesKilledInTimeWindow++;
self notify( "odin_enemy_killed" );
}
}
odin_dialog_killed_player( victim ) // self == odin
{
self endon( "death" );
level endon( "game_ended" );
config = level.odinSettings[ self.odinType ];
time_window = 1.0;
while( true )
{
self waittill( "odin_enemy_killed" );
wait( time_window );
if( self.enemiesKilledInTimeWindow > 1 )
self.owner leaderDialogOnPlayer( config.voKillMulti );
else
self.owner leaderDialogOnPlayer( config.voKillSingle );
self.enemiesKilledInTimeWindow = 0;
}
}
odin_onPlayerConnect() // self == odin
{
self endon( "death" );
level endon( "game_ended" );
while( true )
{
level waittill( "connected", player );
player thread odin_onPlayerSpawned( self );
}
}
odin_onPlayerSpawned( odin ) // self == player
{
self endon( "disconnect" );
self waittill( "spawned_player" );
odin applyOutline( self );
}
cleanup_ents() // self == odin
{
if( IsDefined( self.targeting_marker ) )
self.targeting_marker delete();
if( IsDefined( self.odin_overlay_ent ) )
self.odin_overlay_ent delete();
}
watchEarlyExit( odin ) // self == player
{
level endon( "game_ended" );
odin endon( "death" );
odin thread maps\mp\killstreaks\_killstreaks::allowRideKillstreakPlayerExit();
odin waittill("killstreakExit");
config = level.odinSettings[ odin.odinType ];
leaderDialog( config.voTimedOut );
odin notify ("death");
}