aboutsummaryrefslogtreecommitdiff
path: root/Northstar.Custom/mod/scripts/vscripts/titan
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.Custom/mod/scripts/vscripts/titan')
-rw-r--r--Northstar.Custom/mod/scripts/vscripts/titan/sh_titan.gnut1276
1 files changed, 1276 insertions, 0 deletions
diff --git a/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan.gnut b/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan.gnut
new file mode 100644
index 00000000..0793acd2
--- /dev/null
+++ b/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan.gnut
@@ -0,0 +1,1276 @@
+untyped
+
+global function TitanShared_Init
+global function CodeCallback_PlayerInTitanCockpit
+global function DebugNewTitanModels
+global function Titan_CreatePhysicsModelsFromParentedModels
+global function TemporarilyNonSolidPlayer
+
+#if SERVER
+ global function NPC_GetNuclearPayload
+ global function NPC_SetNuclearPayload
+ global function TempAirControl
+ global function TempLossOfAirControl
+ global function TitanEjectPlayer
+ global function TitanStagger
+ global function EnableTitanExit
+ global function DisableTitanExit
+ global function TitanSkipsDeathOnEject
+#endif
+
+global const TITAN_EJECT_SCREECH = "titan_eject_screech"
+global const TITAN_DECAY_LIMIT = 0.35 // should be the same as the frac that flames start.
+
+global const TITAN_NUCLEAR_CORE_FX_3P = $"P_xo_exp_nuke_3P_alt"
+global const TITAN_NUCLEAR_CORE_FX_1P = $"P_xo_exp_nuke_1P_alt"
+global const TITAN_NUCLEAR_CORE_NUKE_FX = $"P_xo_nuke_warn_flare"
+
+
+global enum eCockpitState
+{
+ Disabled = 0
+ Enabled = 1
+ NULL = 3
+ Open = 5
+ Close = 7
+ Eject = 9
+}
+
+struct
+{
+ float titanVOEjectNotifyDist = 2000 * 2000
+} file
+
+
+function TitanShared_Init()
+{
+ level._titanCrushables <- {}
+
+ RegisterSignal( "TitanBeingEntered" )
+ RegisterSignal( "TitanEntered" )
+ RegisterSignal( "TitanExit" )
+ RegisterSignal( "TitanExitComplete" )
+ RegisterSignal( "TitanDecay" )
+ RegisterSignal( "TitanEjectionStarted" )
+ RegisterSignal( "EjectLand" )
+ RegisterSignal( "EjectAttempt" )
+ RegisterSignal( "TempAirControl" )
+
+ #if SERVER
+ AddSoulSettingsChangeFunc( UpdateTitanPanel )
+ AddSoulSettingsChangeFunc( UpdateTitanArmBadge )
+ AddSoulTransferFunc( SmartAmmo_TransferMissileLockons )
+ AddSoulDeathCallback( Titan_RodeoPanelCleanup )
+ AddSoulDeathCallback( Titan_ArmBadgeCleanup )
+ #endif
+
+ #if SERVER
+ AddSoulInitFunc( AddPanelToTitan )
+ AddSoulInitFunc( AddArmBadgeToTitan )
+
+ PrecacheParticleSystem( TITAN_NUCLEAR_CORE_FX_3P )
+ PrecacheParticleSystem( TITAN_NUCLEAR_CORE_FX_1P )
+ PrecacheParticleSystem( TITAN_NUCLEAR_CORE_NUKE_FX )
+
+ PrecacheModel( $"models/industrial/bolt_tiny01.mdl" )
+ #endif
+}
+
+#if SERVER
+void function Titan_RodeoPanelCleanup( entity soul, var damageInfo )
+{
+ if ( IsValid( soul.soul.batteryContainer ) )
+ soul.soul.batteryContainer.Kill_Deprecated_UseDestroyInstead()
+}
+
+void function Titan_ArmBadgeCleanup( entity soul, var damageInfo )
+{
+ if ( IsValid( soul.soul.armBadge ) )
+ soul.soul.armBadge.Kill_Deprecated_UseDestroyInstead()
+}
+#endif
+
+//Can't just do this by default for all children on the Titan since they need to have physics properties defined
+function Titan_CreatePhysicsModelsFromParentedModels( parentedModel, entity soul )
+{
+ if ( !IsValid( parentedModel ) )
+ return
+
+ // Make it not solid so ejection doesn't get caught up on it
+ parentedModel.NotSolid()
+
+/*
+ // Stop any running animations
+ parentedModel.Anim_Stop()
+
+ // Spawn a physics version of the models
+ entity prop_physics = CreateEntity( "prop_physics" )
+ SetTargetName( prop_physics, UniqueString( "parentedModel" ) )
+ prop_physics.SetValueForModelKey( parentedModel.GetModelName() )
+ prop_physics.kv.skin = parentedModel.GetSkin()
+ prop_physics.kv.spawnflags = 4 // debris nocollide
+ prop_physics.kv.fadedist = -1
+ prop_physics.kv.physdamagescale = 0.1
+ prop_physics.kv.inertiaScale = 1.0
+ prop_physics.kv.renderamt = 255
+ prop_physics.kv.rendercolor = "255 255 255"
+ prop_physics.SetOrigin( parentedModel.GetOrigin() )
+ prop_physics.SetAngles( parentedModel.GetAngles() )
+ DispatchSpawn( prop_physics )
+ //prop_physics.SetAngularVelocity( 0,0,0 )
+ //prop_physics.SetVelocity( Vector( 0,0,0) )
+ prop_physics.Kill_Deprecated_UseDestroyInstead( 11.0 )
+*/
+
+ // Hide pod model, and delete it. We have to hide it first because it doesn't get deleted right away for some reason
+ parentedModel.Hide()
+ parentedModel.Kill_Deprecated_UseDestroyInstead()
+
+}
+
+function CodeCallback_PlayerInTitanCockpit( titan, player )
+{
+ expect entity( titan )
+ expect entity( player )
+
+ // clear the damage history when you enter a titan
+ ClearRecentDamageHistory( player )
+
+ #if SERVER
+// player.SetUsableByGroup( "enemies" ) // rodeo'able
+ //player.SetUsable()
+ //player.SetUsePrompts( "Hold [USE] to rodeo.", "Hold [USE] to rodeo." )
+ //player.SetUsePrompts( " ", " " )
+
+ TitanTaken( player, titan )
+ titan.GetTitanSoul().soul.lastOwner = player
+
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanCockpitBoot" )
+ player.CockpitStartBoot()
+
+ Signal( svGlobal.levelEnt, "TitanEntered", { player = player } )
+ Signal( player, "TitanEntered" )
+ #elseif CLIENT
+ Signal( player, "TitanEntered" )
+ #endif
+}
+
+#if SERVER
+void function AddPanelToTitan( entity soul )
+{
+ entity titan = soul.GetTitan()
+
+ string settings = GetSoulPlayerSettings( soul )
+ var model = Dev_GetPlayerSettingAssetByKeyField_Global( settings, "hatchmodel" )
+ if ( model == $"" )
+ return
+ expect asset( model )
+
+ entity rodeoPanel = CreatePropDynamic( model )
+
+ string titanType = GetSoulTitanSubClass( soul )
+
+ rodeoPanel.NotSolid()
+ rodeoPanel.SetParent( titan, "RODEO_BATTERY" )
+ rodeoPanel.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_up_idle" ) )
+ rodeoPanel.s.opened <- false
+ rodeoPanel.s.lastDamageStateThreshold <- 1.1
+ rodeoPanel.s.lastDamageStateParticleSystem <- null
+ rodeoPanel.s.damageAnimDone <- true
+ SetTeam( rodeoPanel, titan.GetTeam() )
+ rodeoPanel.MarkAsNonMovingAttachment()
+ rodeoPanel.RemoveFromSpatialPartition()
+ rodeoPanel.SetSkin( titan.GetSkin() )
+
+ soul.soul.batteryContainer = rodeoPanel
+}
+
+void function UpdateTitanPanel( entity soul )
+{
+ entity titan = soul.GetTitan()
+ if ( !IsAlive( titan ) )
+ return
+
+ string settings = GetSoulPlayerSettings( soul )
+ var model = Dev_GetPlayerSettingAssetByKeyField_Global( settings, "hatchmodel" )
+ if ( model == $"" )
+ return
+ expect asset( model )
+
+ entity batteryContainer = soul.soul.batteryContainer
+
+ if ( soul.soul.batteryContainerBeingUsed )
+ return
+
+ batteryContainer.SetModel( model )
+ batteryContainer.SetSkin( titan.GetSkin() )
+ batteryContainer.ClearParent()
+ batteryContainer.SetParent( titan, "RODEO_BATTERY" ) //Needed to fix battery being parented to wrong spot after picking a different titan loadout in grace period.
+
+ string titanType = GetSoulTitanSubClass( soul )
+ batteryContainer.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_up_idle" ) )
+
+}
+
+void function AddArmBadgeToTitan( entity soul )
+{
+ thread AddArmBadgeToTitan_Internal( soul )
+}
+
+void function AddArmBadgeToTitan_Internal( entity soul )
+{
+ soul.EndSignal( "OnDeath" )
+
+ // wait until the end of the frame to allow the soul to become owned by a boss player
+ WaitEndFrame()
+
+ entity titan = soul.GetTitan()
+
+ var model = GetTitanArmBadge( soul )
+ if ( model == $"" )
+ return
+ expect asset( model )
+
+ entity soulOwner = soul.GetBossPlayer()
+ if ( !IsValid( soulOwner ) )
+ return
+
+ entity armBadge = CreatePropDynamic( model )
+
+ //string titanType = GetSoulTitanSubClass( soul )
+
+ armBadge.NotSolid()
+ armBadge.SetParent( titan, TITAN_ARM_BADGE_ATTACHMENT )
+ //armBadge.Anim_Play( GetAnimFromAlias( titanType, "hatch_rodeo_up_idle" ) )
+ SetTeam( armBadge, titan.GetTeam() )
+ armBadge.MarkAsNonMovingAttachment()
+ armBadge.RemoveFromSpatialPartition()
+ #if MP
+ int difficulty = FD_GetHighestDifficultyForTitan( soulOwner, GetActiveTitanLoadout( soulOwner ).titanClass )
+ switch ( difficulty )
+ {
+ case eFDDifficultyLevel.HARD:
+ armBadge.SetBodygroup( 0, 1 )
+ break
+ case eFDDifficultyLevel.MASTER:
+ armBadge.SetBodygroup( 0, 2 )
+ break
+ case eFDDifficultyLevel.INSANE:
+ armBadge.SetBodygroup( 0, 3 )
+ break
+ }
+ #endif
+
+ soul.soul.armBadge = armBadge
+}
+
+void function UpdateTitanArmBadge( entity soul )
+{
+ entity titan = soul.GetTitan()
+ if ( !IsAlive( titan ) )
+ return
+
+ var model = GetTitanArmBadge( soul )//Dev_GetPlayerSettingAssetByKeyField_Global( settings, "hatchmodel" )
+ if ( model == $"" )
+ return
+ expect asset( model )
+
+ entity soulOwner = soul.GetBossPlayer()
+ if ( !IsValid( soulOwner ) )
+ return
+
+ entity armBadge = soul.soul.armBadge
+ if(armBadge == null)
+ return
+
+ //armBadge.SetModel( model )
+ //armBadge.SetSkin( titan.GetSkin() )
+ armBadge.ClearParent()
+ armBadge.SetParent( titan, TITAN_ARM_BADGE_ATTACHMENT )
+}
+
+int function NPC_GetNuclearPayload( entity npc )
+{
+ return npc.ai.nukeCore
+}
+
+void function NPC_SetNuclearPayload( entity npc, int doSet = 4 )
+{
+ npc.ai.nukeCore = doSet
+}
+#endif //Server only
+
+function DebugNewTitanModels()
+{
+ return GetCurrentPlaylistVarInt( "r2_titan_models", 0 )
+}
+
+/*
+open
+openIdle
+close
+closeIdle
+frontToBack
+backToFront
+backIdle
+*/
+
+#if SERVER
+const TITAN_PLAYEREJECT_DELAY = 0.4
+const TITAN_PLAYEREJECT_DURATION = 0.8 // long enough foranimation
+const MAX_EJECT_LATENCY_COMPENSATION = 0.4
+
+function TitanSkipsDeathOnEject( entity titan )
+{
+ entity soul = titan.GetTitanSoul()
+ soul.soul.diesOnEject = false
+}
+
+function TitanEjectPlayer( entity ejectTitan, bool instant = false ) //TODO: This needs a refactor badly. Way too long and unwieldy. I think it was a mistake to handle both player Titan eject and NPC titan eject in the same function
+{
+ ejectTitan.Signal( "EjectAttempt" )
+
+ Assert( ejectTitan.IsTitan() )
+ Assert( IsAlive( ejectTitan ), "Ejecting titan expected to be alive. IsPlayer? " + ejectTitan.IsPlayer() + " ent: " + ejectTitan )
+
+ if ( ejectTitan.ContextAction_IsActive() )
+ return
+
+ entity soul = ejectTitan.GetTitanSoul()
+
+ if ( soul.IsEjecting() )
+ return
+
+ if ( ejectTitan.IsPlayer() )
+ {
+ if ( IsPlayerDisembarking( ejectTitan ) )
+ return
+ }
+
+ local e = {}
+ e.titan <- ejectTitan
+ e.team <- ejectTitan.GetTeam()
+
+ e.player <- null
+ e.npcPilot <- null
+ bool ejectTitanHasNpcPilot = false
+ if ( ejectTitan.IsPlayer() )
+ e.player = ejectTitan
+
+ #if NPC_TITAN_PILOT_PROTOTYPE
+ if ( TitanHasNpcPilot( ejectTitan ) )
+ {
+ ejectTitanHasNpcPilot = true
+ }
+ #endif
+
+ e.nukeFX <- []
+ e.attacker <- ( "attacker" in soul.lastAttackInfo ) ? soul.lastAttackInfo.attacker : null
+ e.inflictor <- ( "inflictor" in soul.lastAttackInfo ) ? soul.lastAttackInfo.inflictor : null
+ e.damageSourceId <- ( "damageSourceId" in soul.lastAttackInfo ) ? soul.lastAttackInfo.damageSourceId : -1
+ e.damageTypes <- soul.lastAttackInfo.scriptType
+ e.overrideAttacker <- soul.soul.nukeAttacker
+
+ local nuclearPayload = 0
+ if ( IsValid( e.player ) )
+ nuclearPayload = GetNuclearPayload( ejectTitan )
+ else
+ nuclearPayload = NPC_GetNuclearPayload( ejectTitan )
+
+ e.nuclearPayload <- nuclearPayload
+
+ if ( e.nuclearPayload )
+ {
+ e.needToClearNukeFX <- false
+ e.nukeFXInfoTarget <- CreateEntity( "info_target" )
+ e.nukeFXInfoTarget.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ DispatchSpawn( e.nukeFXInfoTarget )
+
+ AI_CreateDangerousArea_DamageDef( damagedef_nuclear_core, e.nukeFXInfoTarget, ejectTitan.GetTeam(), true, true )
+ }
+
+ entity rodeoPilot = GetRodeoPilot( ejectTitan )
+ if ( rodeoPilot && rodeoPilot == e.attacker )
+ e.damageSourceId = eDamageSourceId.rodeo_forced_titan_eject
+
+ ejectTitan.Signal( "TitanEjectionStarted" )
+ ejectTitan.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function() : ( e, ejectTitan )
+ {
+ if ( IsAlive( ejectTitan ) )
+ {
+ thread ClearEjectInvulnerability( ejectTitan )
+ }
+ else if ( IsValid( ejectTitan ) )
+ {
+ ejectTitan.ClearInvulnerable()
+ }
+
+ if ( IsValid( e.player ) )
+ {
+ e.player.UnfreezeControlsOnServer()
+ }
+
+ entity titan = expect entity( e.titan )
+
+ if ( e.nuclearPayload )
+ {
+ if ( e.needToClearNukeFX )
+ {
+ if ( IsAlive( titan ) )
+ {
+ //Nuclear eject sequence got interrupted early, probably because Pilot died
+ Assert( titan.IsTitan() )
+ thread NuclearCoreExplosion( titan.GetOrigin(), e )
+ }
+ else
+ {
+ //Nuclear eject fired, needs to be cleaned up
+ ClearNuclearBlueSunEffect( e )
+ }
+ }
+ //Nuclear core handles cleaning up the left over titan by itself, so just return out early
+ return
+ }
+
+ if ( !IsAlive( titan ) )
+ return
+
+ entity soul = titan.GetTitanSoul()
+ if ( !soul.soul.diesOnEject )
+ return
+
+ Assert( titan.IsTitan() )
+ Assert( soul.IsEjecting() )
+ titan.Die( e.attacker, e.inflictor, { scriptType = damageTypes.titanEjectExplosion | e.damageTypes, damageSourceId = e.damageSourceId } )
+ }
+ )
+
+ soul.SetEjecting( true )
+ ejectTitan.SetInvulnerable() //Give both player and ejectTitan temporary invulnerability in the course of ejecting. Player invulnerability gets cleared in ClearEjectInvulnerability
+
+ #if SERVER
+ StatusEffect_StopAll( expect entity( e.titan ), eStatusEffect.lockon_detected_titan )
+ #endif
+
+ #if HAS_STATS
+ if ( IsValid( e.player ) )
+ {
+ UpdatePlayerStat( expect entity( e.player ), "misc_stats", "timesEjected" )
+ if ( nuclearPayload )
+ UpdatePlayerStat( expect entity( e.player ), "misc_stats", "timesEjectedNuclear" )
+ }
+ #endif
+ #if SERVER && MP
+ PIN_AddToPlayerCountStat( expect entity( e.player ), "ejects" )
+ #endif
+
+ if ( !ejectTitan.ContextAction_IsBusy() )
+ ejectTitan.ContextAction_SetBusy()
+
+ local standing = true
+ if ( IsValid( e.player ) )
+ standing = e.player.IsStanding()
+ else
+ standing = soul.GetStance() == STANCE_STAND
+
+ local titanEjectAnimPlayer, titanEjectAnimTitan
+ if ( standing )
+ {
+ if ( nuclearPayload )
+ {
+ titanEjectAnimPlayer = "at_nuclear_eject_standing"
+ titanEjectAnimTitan = "at_nuclear_eject_standing_idle"
+ }
+ else
+ {
+ titanEjectAnimPlayer = "at_MP_eject_stand_start"
+ titanEjectAnimTitan = "at_MP_eject_stand_end"
+ }
+ }
+ else
+ {
+ titanEjectAnimPlayer = "at_MP_eject_crouch_idle"
+ titanEjectAnimTitan = "at_MP_eject_crouch_start"
+ }
+
+ float ejectDuration // = TITAN_PLAYEREJECT_DURATION
+ if ( nuclearPayload )
+ ejectDuration = TITAN_PLAYEREJECT_DURATION * 2.0
+ else
+ ejectDuration = TITAN_PLAYEREJECT_DURATION
+
+// ejectDuration = ejectTitan.GetSequenceDuration( titanEjectAnimPlayer )
+
+ if ( nuclearPayload )
+ {
+ array<entity> players = GetPlayerArray()
+ local frequency = 40
+ local duration = 8.5
+ vector origin = ejectTitan.GetOrigin()
+
+ foreach ( guy in players )
+ {
+ if ( guy == e.player )
+ continue
+
+ if ( !IsAlive( guy ) )
+ continue
+
+ float dist = Distance( guy.GetOrigin(), origin )
+ float result = Graph( dist, 750, 1500, 5.0, 0.0 )
+ Remote_CallFunction_Replay( guy, "ServerCallback_ScreenShake", result, frequency, duration )
+ }
+
+ e.needToClearNukeFX = true
+ e.nukeFXInfoTarget.SetParent( ejectTitan, "CHESTFOCUS" ) //Play FX and sound on entity since we need something that lasts across the player titan -> pilot transition
+ e.nukeFX.append( PlayFXOnEntity( TITAN_NUCLEAR_CORE_NUKE_FX, expect entity( e.nukeFXInfoTarget ) ) )
+ e.nukeFX.append( e.nukeFXInfoTarget )
+ //ejectDuration += 0.5
+
+ EmitSoundOnEntity( e.nukeFXInfoTarget, "titan_nuclear_death_charge" )
+ }
+
+ entity rodeoPlayer = GetRodeoPilot( ejectTitan )
+ if ( IsValid( rodeoPlayer ) )
+ Remote_CallFunction_Replay( rodeoPlayer, "ServerCallback_RodeoerEjectWarning", ejectTitan.GetTitanSoul().GetEncodedEHandle(), TITAN_PLAYEREJECT_DELAY + ejectDuration )
+
+ if ( IsValid( e.player ) )
+ e.player.CockpitStartEject()
+
+ float blendDelay = 0.15
+ vector origin = ejectTitan.GetOrigin()
+
+ if ( !instant )
+ {
+ if ( IsValid( e.player ) )
+ {
+ Remote_CallFunction_Replay( e.player, "ServerCallback_EjectConfirmed" )
+ EmitSoundAtPositionExceptToPlayer( e.team, ejectTitan.GetOrigin(), e.player, "Titan_Eject_Servos_3P" )
+ e.player.FreezeControlsOnServer()
+ }
+ else
+ {
+ EmitSoundAtPosition( e.team, ejectTitan.GetOrigin(), "Titan_Eject_Servos_3P" )
+ }
+
+ if ( !ejectTitan.IsTitan() )
+ {
+ // must be a titan, something bad has happened
+ KillStuckPlayer( ejectTitan )
+ return
+ }
+
+ ejectTitan.Anim_Play( titanEjectAnimPlayer )
+
+ wait blendDelay // wait for ejectTitan to blend into disembark pose
+
+ Assert( ejectDuration > MAX_EJECT_LATENCY_COMPENSATION )
+ wait ejectDuration - MAX_EJECT_LATENCY_COMPENSATION
+
+ if ( IsValid( e.player ) )
+ {
+ // subtract player latency so that the client gets the eject at the same time they finish the animation
+ float latency = expect entity( e.player ).GetLatency()
+ float waitduration = MAX_EJECT_LATENCY_COMPENSATION - min( latency, MAX_EJECT_LATENCY_COMPENSATION )
+ //printt( "Eject: compensating for " + latency + " seconds of latency; wait " + waitduration )
+ wait waitduration
+ }
+ }
+
+ // Defensive fix for if player becomes a spectator between initiating eject and now
+ if ( IsValid( e.player ) && e.player.GetPlayerSettings() == "spectator" )
+ return
+
+ if ( ejectTitan.GetTitanSoul() == null )
+ return
+
+ if ( IsValid( e.player ) )
+ EmitSoundAtPositionExceptToPlayer( e.team, ejectTitan.GetOrigin(), e.player, "Titan_Eject_PilotLaunch_3P" )
+ else
+ EmitSoundAtPosition( e.team, ejectTitan.GetOrigin(), "Titan_Eject_PilotLaunch_3P" )
+
+ entity titan
+ if ( IsValid( e.player ) )
+ {
+ entity player = expect entity( e.player )
+ titan = CreateAutoTitanForPlayer_ForTitanBecomesPilot( player )
+ DispatchSpawn( titan )
+ player.p.lastEjectTime = Time()
+ HolsterAndDisableWeapons( player ) //Primarily done to not play the holster animation, then deploy animation of weapon if we happened to switch the active weapon in GiveWeaponsFromStoredArray()
+ TitanBecomesPilot( ejectTitan, titan )
+ DeployAndEnableWeapons( player )//Undo Holster
+ player.UnfreezeControlsOnServer()
+ }
+ else
+ {
+ // the titan is an AI
+ titan = ejectTitan
+ }
+
+ #if NPC_TITAN_PILOT_PROTOTYPE
+ if ( ejectTitanHasNpcPilot )
+ e.npcPilot = NpcTitanBecomesPilot( ejectTitan )
+ #endif
+
+ vector titanOrigin = titan.GetOrigin()
+
+ // HACKY, surprised there isn't a wrapper for this yet
+ if ( !( "disableAutoTitanConversation" in titan.s ) )
+ titan.s.disableAutoTitanConversation <- true // no auto titan chatter
+
+ titan.SetInvulnerable() //Titan dies at the end of eject sequence by script
+ titan.SetNPCPriorityOverride_NoThreat() // AI shouldn't consider this ejecting titan as an enemy and shoot it, etc
+
+ if ( e.nuclearPayload )
+ {
+ e.nukeFXInfoTarget.SetParent( titan, "CHESTFOCUS" )
+ }
+
+ local isInDeepWater = ( "isInDeepWater" in ejectTitan.s && ejectTitan.s.isInDeepWater )
+
+ if ( e.nuclearPayload || isInDeepWater )
+ {
+ thread TitanNonSolidTemp( titan )
+ }
+
+ ejectTitan.Anim_Stop()
+ e.titan = titan
+
+ if ( ejectTitan.ContextAction_IsBusy() )
+ ejectTitan.ContextAction_ClearBusy()
+
+ FirstPersonSequenceStruct sequence
+ sequence.thirdPersonAnim = expect string ( titanEjectAnimTitan )
+ sequence.teleport = true
+ thread FirstPersonSequence( sequence, titan )
+
+ if ( IsValid( e.player ) )
+ {
+ entity player = expect entity( e.player )
+ thread TempAirControl( player )
+
+ PutEntityInSafeSpot( player, titan, null, origin + <0,0,60>, player.GetOrigin() + <0,0,60> )
+ }
+
+ vector ejectAngles = titan.GetAngles()
+ ejectAngles.x = 270
+ //ejectAngles.x = RandomIntRange( 263, 277 ) //5 degrees back of straight up was 245
+
+ float speed = RandomFloatRange( 1500, 1700 ) //was 1000
+ if ( nuclearPayload )
+ speed += 400
+
+ if ( isInDeepWater )
+ speed += 1000
+
+ e.singleRodeoPilot <- null //HACKY. Need to store it off because after time passes we don't have a handle to the rider anymore. Terribly hacky
+
+ entity rider = GetRodeoPilot( titan )
+ if ( rider && rider.GetParent() == titan )
+ {
+ e.singleRodeoPilot = rider //Need to store it off because after time passes we don't have a handle to the rider anymore. Terribly hacky
+ if ( IsValid( e.player ) )
+ thread TemporarilyNonSolidPlayer( expect entity( e.player ) )
+
+ thread TemporarilyNonSolidPlayer( rider )
+
+ vector riderEjectAngles = AnglesCompose( ejectAngles, < 5, 0, 0 > )
+
+ float gravityScale = expect float ( rider.GetPlayerSettingsField( "gravityscale" ) )
+ vector riderVelocity = AnglesToForward( riderEjectAngles ) * (speed * gravityScale) * 0.95
+
+ ThrowRiderOff( rider, titan, riderVelocity )
+
+ wait 0.05
+ }
+
+ if ( IsAlive( expect entity( e.player ) ) )
+ {
+ if ( PlayerHasPassive( expect entity( e.player ), ePassives.PAS_PHASE_EJECT ) )
+ {
+ PhaseShift( expect entity( e.player ), 0.0, 3.0 )
+ ejectAngles.x = 315
+ speed *= 0.5
+ }
+ ejectAngles = AnglesCompose( ejectAngles, < -5, 0, 0 > )
+
+ float gravityScale = expect float ( e.player.GetPlayerSettingsField( "gravityscale" ) )
+ vector velocity = AnglesToForward( ejectAngles ) * speed * sqrt( gravityScale )
+ e.player.SetOrigin( e.player.GetOrigin() )
+ e.player.SetVelocity( velocity )
+ vector player_look_angles = titan.GetAngles()
+ player_look_angles.x = 80 //was 35
+ e.player.SetAngles( player_look_angles )
+
+ thread EjectFlightTracker( expect entity( e.player ) )
+
+ entity rider = expect entity( e.singleRodeoPilot )
+ if ( IsAlive( rider ) && e.player.GetTeam() != rider.GetTeam() )
+ thread LookAtEachOther( rider, expect entity( e.player ) )
+ }
+ else if ( ejectTitanHasNpcPilot && IsAlive( expect entity( e.npcPilot ) ) )
+ {
+ speed *= 0.6//magic number
+ vector velocity = < 0, 0, speed > //straight up
+ e.npcPilot.SetOrigin( titan.GetOrigin() /* + Vector(0,0,100)*/ )
+ e.npcPilot.SetAngles( titan.GetAngles() )
+ e.npcPilot.Anim_ScriptedPlay( "running_jump_F_float" )
+ e.npcPilot.SetVelocity( velocity )
+ }
+
+ if ( IsValid( e.player ) )
+ TitanEjectVO( expect entity( e.player ), titanOrigin )
+
+ wait 0.15
+
+ vector explosionOrigin = titanOrigin + Vector( 0, 0, 200 )
+
+ if ( nuclearPayload )
+ {
+ thread NuclearCoreExplosion( explosionOrigin, e )
+ }
+ else
+ {
+ entity explosionOwner = GetExplosionOwner( e )
+ entity inflictor
+ if ( IsValid( titan ) )
+ inflictor = titan
+ else
+ inflictor = explosionOwner
+
+ RadiusDamage(
+ explosionOrigin, // origin
+ explosionOwner, // owner
+ inflictor, // inflictor
+ 1, // normal damage
+ 1800, // heavy armor damage
+ 100, // inner radius
+ 300, // outer radius
+ SF_ENVEXPLOSION_NO_DAMAGEOWNER, // explosion flags
+ 0, // distanceFromAttacker
+ 0, // explosionForce
+ damageTypes.explosive, // damage flags
+ eDamageSourceId.titan_explosion // damage source id
+ )
+
+ entity shake = CreateEntity( "env_shake" )
+ shake.SetOrigin( titanOrigin )
+ shake.kv.amplitude = 12 //1-16
+ shake.kv.duration = 1
+ shake.kv.frequency = 100 //.001 - 255
+ shake.kv.radius = 1000
+ shake.kv.spawnflags = 4 //in air
+ DispatchSpawn( shake )
+ shake.Fire( "StartShake" )
+ shake.Kill_Deprecated_UseDestroyInstead( 1 )
+ }
+
+ if ( IsValid( titan ) )
+ {
+ if ( titan.ContextAction_IsBusy() )
+ titan.ContextAction_ClearBusy()
+ }
+}
+
+function TitanEjectVO( entity player, vector titanOrigin )
+{
+ array<entity> titans = GetTitanArray()
+ int team = player.GetTeam()
+ int voEnum
+
+ foreach ( titan in titans )
+ {
+ if ( !titan.IsPlayer() )
+ continue
+ if ( titan == player )
+ continue
+
+ if ( team == titan.GetTeam() )
+ {
+ if ( DistanceSqr( titanOrigin, titan.GetOrigin() ) > file.titanVOEjectNotifyDist )
+ return
+
+ voEnum = eTitanVO.FRIENDLY_EJECTED
+ }
+ else
+ {
+ if ( !ShouldCalloutEjection( player, titanOrigin, titan ) )
+ return
+
+ voEnum = eTitanVO.ENEMY_EJECTED
+ }
+
+ Remote_CallFunction_Replay( titan, "SCB_TitanDialogue", voEnum )
+ }
+}
+#endif // SERVER
+
+bool function ShouldCalloutEjection( entity player, vector titanOrigin, entity titan )
+{
+ if ( DistanceSqr( titanOrigin, titan.GetOrigin() ) < file.titanVOEjectNotifyDist )
+ return true
+
+ // have they hit each other recently? To catch LTS sniper war ejections
+ if ( WasRecentlyHitByEntity( player, titan, 6.0 ) )
+ return true
+
+ if ( WasRecentlyHitByEntity( titan, player, 6.0 ) )
+ return true
+
+ return false
+}
+
+
+function TemporarilyNonSolidPlayer( entity rider )
+{
+ rider.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function () : ( rider )
+ {
+ if ( IsValid( rider ) )
+ {
+ rider.Solid()
+ }
+ }
+ )
+
+ rider.NotSolid()
+ wait 1.5
+}
+
+
+#if SERVER
+function TitanNonSolidTemp( entity titan )
+{
+ if ( !EntityInSolid( titan ) )
+ return
+
+ local collisionGroup = titan.kv.CollisionGroup
+
+ // Blocks bullets, projectiles but not players and not AI
+ titan.kv.CollisionGroup = TRACE_COLLISION_GROUP_BLOCK_WEAPONS
+
+ titan.EndSignal( "OnDeath" )
+
+ while( EntityInSolid( titan ) )
+ {
+ wait 0.1
+ }
+
+ titan.kv.collisionGroup = collisionGroup
+}
+
+function NuclearCoreExplosion( vector origin, e )
+{
+ entity titan = expect entity( e.titan )
+
+ titan.EndSignal( "OnDeath" )
+
+ e.needToClearNukeFX = false //This thread and NuclearCoreExplosionChainReaction now take responsibility for clearing the FX
+
+ OnThreadEnd(
+ function() : ( e )
+ {
+ ClearNuclearBlueSunEffect( e )
+ }
+ )
+
+ wait 1.3
+ Assert( IsValid( titan ) )
+ titan.s.silentDeath <- true //Don't play normal titan_death_explode in _deathpackage since we're playing titan_nuclear_death_explode
+
+ EmitSoundAtPosition( titan.GetTeam(), origin, "titan_nuclear_death_explode" )
+
+ titan.s.noLongerCountsForLTS <- true
+
+ thread NuclearCoreExplosionChainReaction( origin, e )
+
+ if ( IsAlive( titan ) )
+ titan.Die( e.attacker, e.inflictor, { scriptType = DF_EXPLOSION, damageType = DMG_REMOVENORAGDOLL, damageSourceId = e.damageSourceId } )
+}
+
+
+void function KillStuckPlayer( entity player )
+{
+ if ( IsAlive( player ) )
+ player.Die( svGlobal.worldspawn, svGlobal.worldspawn, { scriptType = DF_DISSOLVE, damageSourceId = damagedef_crush } )
+}
+#endif // SERVER
+
+function ClearNuclearBlueSunEffect( e )
+{
+ foreach ( fx in e.nukeFX )
+ {
+ if ( IsValid( fx ) )
+ fx.Kill_Deprecated_UseDestroyInstead()
+ }
+ e.nukeFX.clear()
+ e.needToClearNukeFX = false
+}
+
+#if SERVER
+function NuclearCoreExplosionChainReaction( vector origin, e )
+{
+ int explosions
+ local innerRadius
+ float time
+ bool IsNPC
+
+ local heavyArmorDamage = 2500
+ local normalDamage = 75
+
+ switch ( e.nuclearPayload )
+ {
+ case 4:
+ // npc nuke: the idea is to be the same as the regular nuke - but with less explosion calls
+ explosions = 3
+ innerRadius = 350
+ time = 1.5 //1 is the regular nuke time - but we won't be adding an extra explosion and we want 3 explosions over 1s. This will mathematically give us that.
+ IsNPC = true
+
+ local fraction = 10.0 / explosions //10 is the regular nuke number
+ heavyArmorDamage = heavyArmorDamage * fraction
+ normalDamage = normalDamage * fraction
+ break
+
+ case 3:
+ // super nuke: PAS_NUCLEAR_CORE + PAS_BUILD_UP_NUCLEAR_CORE
+ explosions = 20
+ innerRadius = 350
+ time = 1.7
+ IsNPC = false
+ break
+
+ case 2:
+ // super nuke: PAS_NUCLEAR_CORE
+ explosions = 15
+ innerRadius = 350
+ time = 1.4
+ IsNPC = false
+ break
+
+ case 1:
+ // regular nuke: PAS_BUILD_UP_NUCLEAR_CORE
+ explosions = 10
+ innerRadius = 350
+ time = 1.0
+ IsNPC = false
+ break
+
+ default:
+ Assert( 0, "e.nuclearPayload value: " + e.nuclearPayload + " not accounted for." )
+ break
+ }
+
+ float waitPerExplosion = time / explosions
+
+ ClearNuclearBlueSunEffect( e )
+
+ if ( IsValid( e.player ) )
+ {
+ thread __CreateFxInternal( TITAN_NUCLEAR_CORE_FX_1P, null, "", origin, Vector(0,RandomInt(360),0), C_PLAYFX_SINGLE, null, 1, expect entity( e.player ) )
+ thread __CreateFxInternal( TITAN_NUCLEAR_CORE_FX_3P, null, "", origin + Vector( 0, 0, -100 ), Vector(0,RandomInt(360),0), C_PLAYFX_SINGLE, null, 6, expect entity( e.player ) )
+ }
+ else
+ {
+ PlayFX( TITAN_NUCLEAR_CORE_FX_3P, origin + Vector( 0, 0, -100 ), Vector(0,RandomInt(360),0) )
+ }
+
+ // one extra explosion that does damage to physics entities at smaller radius
+ if ( !IsNPC )
+ explosions += 1
+
+ local outerRadius
+
+ local baseNormalDamage = normalDamage
+ local baseHeavyArmorDamage = heavyArmorDamage
+ local baseInnerRadius = innerRadius
+ local baseOuterRadius = outerRadius
+
+ // all damage must have an inflictor currently
+ entity inflictor = CreateEntity( "script_ref" )
+ inflictor.SetOrigin( origin )
+ inflictor.kv.spawnflags = SF_INFOTARGET_ALWAYS_TRANSMIT_TO_CLIENT
+ DispatchSpawn( inflictor )
+
+ OnThreadEnd(
+ function() : ( inflictor )
+ {
+ if ( IsValid( inflictor ) )
+ inflictor.Destroy()
+ }
+ )
+
+ for ( int i = 0; i < explosions; i++ )
+ {
+ local normalDamage = baseNormalDamage
+ local heavyArmorDamage = baseHeavyArmorDamage
+ local innerRadius = baseInnerRadius
+ local outerRadius = baseOuterRadius
+
+ if ( i == 0 && !IsNPC )
+ {
+ normalDamage = 75
+ heavyArmorDamage = 0
+ outerRadius = 600
+ }
+ else
+ {
+ outerRadius = 750
+ }
+
+ entity explosionOwner = GetExplosionOwner( e )
+
+ if ( outerRadius < innerRadius )
+ outerRadius = innerRadius
+
+ RadiusDamage_DamageDef( damagedef_nuclear_core,
+ origin, // origin
+ explosionOwner, // owner
+ inflictor, // inflictor
+ normalDamage, // normal damage
+ heavyArmorDamage, // heavy armor damage
+ innerRadius, // inner radius
+ outerRadius, // outer radius
+ 0 ) // dist from attacker
+
+ wait waitPerExplosion
+ }
+}
+
+entity function GetExplosionOwner( e )
+{
+ if ( IsValid( expect entity( e.overrideAttacker ) ) )
+ return expect entity( e.overrideAttacker )
+
+ if ( IsValid( expect entity( e.player ) ) )
+ return expect entity( e.player )
+
+ if ( IsValid( expect entity( e.titan ) ) )
+ return expect entity( e.titan )
+
+ return GetTeamEnt( expect int( e.team ) )
+}
+#endif // SERVER
+
+function ClearEjectInvulnerability( entity player )
+{
+ if ( !IsValid( player ) )
+ return
+
+ player.EndSignal( "OnDeath" )
+
+ OnThreadEnd(
+ function () : (player)
+ {
+ if ( IsValid( player ) )
+ player.ClearInvulnerable()
+ }
+ )
+
+ wait 0.35
+}
+
+function EjectFlightTracker( entity player )
+{
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "EjectLand" )
+ player.EndSignal( "RodeoStarted" )
+
+ OnThreadEnd(
+ function () : (player)
+ {
+ player.p.pilotEjecting = false
+ player.p.pilotEjectEndTime = Time()
+ }
+ )
+
+ player.p.pilotEjecting = true
+ player.p.pilotEjectStartTime = Time()
+
+ wait 0.1
+ for ( ;; )
+ {
+ if ( player.IsOnGround() )
+ player.Signal("EjectLand")
+
+ wait 0.1
+ }
+}
+
+#if SERVER
+function TempAirControl( entity player )
+{
+ player.EndSignal( "TempAirControl" )
+ player.EndSignal( "OnDeath" )
+
+ player.kv.airSpeed = 200
+ player.kv.airAcceleration = 800
+
+ wait 1.5
+
+ player.kv.airSpeed = 100
+
+ wait 3.5
+
+ player.kv.airSpeed = player.GetPlayerSettingsField( "airSpeed" )
+ player.kv.airAcceleration = player.GetPlayerSettingsField( "airAcceleration" )
+}
+
+
+function TempLossOfAirControl( entity player, float time )
+{
+ player.EndSignal( "OnDeath" )
+
+ player.kv.airSpeed = 0
+ player.kv.airAcceleration = 0 // 500
+
+ wait time
+
+ player.kv.airSpeed = player.GetPlayerSettingsField( "airSpeed" )
+ player.kv.airAcceleration = player.GetPlayerSettingsField( "airAcceleration" )
+}
+
+
+bool function TitanStagger( entity titan, damageInfo )
+{
+ if ( !IsAlive( titan ) )
+ return false
+
+ if ( !titan.IsPlayer() )
+ return false
+
+ if ( Time() - titan.s.lastStaggerTime < 1.0 )
+ return false
+
+ if ( titan.GetTitanSoul().GetShieldHealth() )
+ return false
+
+ if ( DamageInfo_GetDamage( damageInfo ) < 1000 )
+ return false
+
+ switch ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ {
+ // R1
+ // case eDamageSourceId.mp_titanweapon_arc_cannon:
+ // case eDamageSourceId.titanEmpField:
+ // case eDamageSourceId.mp_titanweapon_40mm:
+ // case eDamageSourceId.mp_weapon_rocket_launcher:
+ // case eDamageSourceId.mp_titanweapon_sniper:
+ // case eDamageSourceId.mp_titanweapon_shoulder_rockets:
+ // case eDamageSourceId.mp_titanweapon_homing_rockets:
+ // case eDamageSourceId.mp_titanweapon_dumbfire_rockets:
+ // case eDamageSourceId.mp_titanweapon_salvo_rockets:
+
+ // R2 ORDNANCE
+ case eDamageSourceId.mp_titanweapon_shoulder_rockets:
+ case eDamageSourceId.mp_titanweapon_tracker_rockets:
+ // case eDamageSourceId.mp_titanweapon_flame_wall:
+ case eDamageSourceId.mp_titanweapon_shoulder_rockets:
+ case eDamageSourceId.mp_titanweapon_laser_lite:
+ case eDamageSourceId.mp_titanweapon_stun_laser:
+ case eDamageSourceId.mp_titanweapon_arc_wave:
+ case eDamageSourceId.mp_titanweapon_dumbfire_rockets:
+ case eDamageSourceId.mp_titanability_power_shot:
+
+ // R2 PRIMARY WEAPONS
+ // case eDamageSourceId.mp_titanweapon_xo16_shorty:
+ case eDamageSourceId.mp_titanweapon_sticky_40mm:
+ case eDamageSourceId.mp_titanweapon_meteor:
+ case eDamageSourceId.mp_titanweapon_rocketeer_rocketstream:
+ case eDamageSourceId.mp_titanweapon_particle_accelerator:
+ case eDamageSourceId.mp_titanweapon_leadwall:
+ case eDamageSourceId.mp_titanweapon_sniper:
+ // case eDamageSourceId.mp_titanweapon_predator_cannon
+
+ // R2 CORES
+ case eDamageSourceId.mp_titancore_amp_core:
+ case eDamageSourceId.mp_titancore_salvo_core:
+ case eDamageSourceId.mp_titancore_flame_wave:
+ case eDamageSourceId.mp_titanweapon_flightcore_rockets:
+ case eDamageSourceId.mp_titancore_laser_cannon:
+ case eDamageSourceId.mp_titancore_shift_core:
+ case eDamageSourceId.mp_titancore_siege_mode:
+
+ titan.SetStaggering()
+ titan.s.lastStaggerTime = Time()
+ return true
+
+ default:
+ return false
+ }
+
+ unreachable
+}
+#endif // SERVER
+
+function LookAtEachOther( entity rider, entity player )
+{
+ rider.EndSignal( "OnDeath" )
+ player.EndSignal( "OnDeath" )
+
+ float endTime = Time() + 0.45
+
+ for ( ;; )
+ {
+ vector org1 = rider.GetOrigin()
+ vector org2 = player.GetOrigin()
+ vector vec1 = org2 - org1
+ vector angles1 = VectorToAngles( vec1 )
+ vector vec2 = org1 - org2
+ vector angles2 = VectorToAngles( vec2 )
+
+ angles1.x = 0
+ angles2.x = 0
+ if ( rider.GetParent() == null )
+ rider.SetAngles( angles1 )
+ if ( player.GetParent() == null )
+ player.SetAngles( angles2 )
+
+ if ( Time() >= endTime )
+ return
+
+ WaitFrame()
+ }
+}
+
+#if SERVER
+function EnableTitanExit( entity player )
+{
+ if ( CanDisembark( player ) == true )
+ return
+
+ Disembark_Allow( player )
+
+ printt( player, "Titan exit enabled" )
+}
+
+function DisableTitanExit( entity player )
+{
+ if ( !CanDisembark( player ) )
+ return
+
+ Disembark_Disallow( player )
+
+ printt( player, "Titan exit disabled" )
+}
+
+asset function GetTitanArmBadge( entity soul )
+{
+ #if MP
+ entity soulOwner = soul.GetBossPlayer()
+ if ( !IsValid( soulOwner ) )
+ return $""
+
+ TitanLoadoutDef loadout = GetActiveTitanLoadout( soulOwner )
+ return GetTitanArmBadgeFromLoadoutAndPrimeStatus( loadout )
+ #else
+ return $""
+ #endif
+}
+
+#endif \ No newline at end of file