aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_boss_titan.gnut
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_boss_titan.gnut')
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_boss_titan.gnut794
1 files changed, 794 insertions, 0 deletions
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_boss_titan.gnut b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_boss_titan.gnut
new file mode 100644
index 000000000..da3058d78
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_boss_titan.gnut
@@ -0,0 +1,794 @@
+global function PlayerParentTest
+
+global function AIBossTitan_Init
+global function OnBossTitanPrimaryFire
+global function IsVDUTitan
+global function IsBossTitan
+global function GetBossTitanCharacterModel
+
+global function BossTitanRetreat
+global function BossTitanAdvance
+global function IsMercTitan
+global function GetMercCharacterID
+global function BossTitanIntro
+global function BossTitanVDUEnabled
+global function BossTitanPlayerView
+
+global function MakeMidHealthTitan
+
+global const float SLAMZOOM_TIME = 1.0
+global const float BOSS_TITAN_CORE_DAMAGE_SCALER_LOW = 0.6
+global const float BOSS_TITAN_CORE_DAMAGE_SCALER = 0.5
+
+void function AIBossTitan_Init()
+{
+ if ( IsMultiplayer() )
+ return
+
+ FlagInit( "BossTitanViewFollow" )
+
+ AddSpawnCallback( "npc_titan", NPCTitanSpawned )
+ AddDeathCallback( "npc_titan", OnBossTitanDeath )
+ AddCallback_OnTitanDoomed( OnBossTitanDoomed )
+ AddCallback_OnTitanHealthSegmentLost( OnTitanLostSegment )
+
+ AddSyncedMeleeServerCallback( GetSyncedMeleeChooser( "titan", "titan" ), OnBossTitanExecuted )
+
+ PrecacheParticleSystem( $"P_VDU_mflash" )
+
+ RegisterSignal( "BossTitanStartAnim" )
+ RegisterSignal( "BossTitanIntroEnded" )
+}
+
+void function OnBossTitanExecuted( SyncedMeleeChooser actions, SyncedMelee action, entity attacker, entity victim )
+{
+ if ( victim.IsNPC() && IsVDUTitan( victim ) && BossTitanVDUEnabled( victim ) )
+ {
+ string name = victim.ai.bossCharacterName == "" ? "Generic1" : victim.ai.bossCharacterName
+ int bossID = GetBossTitanID( name )
+ foreach ( player in GetPlayerArray() )
+ {
+ if ( player == attacker || IsMercTitan( victim ) )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanDeath", victim.GetEncodedEHandle(), bossID )
+ }
+ }
+ }
+}
+
+void function OnBossTitanDeath( entity titan, var damageInfo )
+{
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ if ( damageSourceId == eDamageSourceId.titan_execution )
+ return
+
+ entity soul = titan.GetTitanSoul()
+ if ( soul.IsEjecting() )
+ return
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( IsVDUTitan( titan ) && BossTitanVDUEnabled( titan ) )
+ {
+ foreach ( player in GetPlayerArray() )
+ {
+ if ( player == attacker || IsMercTitan( titan ) )
+ {
+ string name = titan.ai.bossCharacterName == "" ? "Generic1" : titan.ai.bossCharacterName
+ Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanDeath", titan.GetEncodedEHandle(), GetBossTitanID( name ) )
+ }
+ }
+ }
+}
+
+void function OnBossTitanDoomed( entity titan, var damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ if ( IsVDUTitan( titan ) && BossTitanVDUEnabled( titan ) )
+ {
+ foreach ( player in GetPlayerArray() )
+ {
+ if ( player == attacker || IsMercTitan( titan ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanDoomed", titan.GetEncodedEHandle() )
+ }
+ }
+}
+
+void function OnBossTitanCoreMitigation( entity titan, var damageInfo )
+{
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ switch ( damageSourceID )
+ {
+ case eDamageSourceId.mp_titancore_salvo_core:
+ DamageInfo_ScaleDamage( damageInfo, BOSS_TITAN_CORE_DAMAGE_SCALER_LOW )
+ return
+
+ // case eDamageSourceId.mp_titancore_laser_cannon: laser core handles this in mp_titanweapon_lasercannon.nut
+ case eDamageSourceId.mp_titancore_flame_wave:
+ case eDamageSourceId.mp_titancore_flame_wave_secondary:
+ case eDamageSourceId.mp_titancore_shift_core:
+ case eDamageSourceId.mp_titanweapon_flightcore_rockets:
+ case eDamageSourceId.mp_titancore_amp_core:
+ case damagedef_nuclear_core:
+ DamageInfo_ScaleDamage( damageInfo, BOSS_TITAN_CORE_DAMAGE_SCALER )
+ return
+ }
+
+ // SMART CORE
+ array<string> weaponMods = GetWeaponModsFromDamageInfo( damageInfo )
+ if ( weaponMods.contains( "Smart_Core" ) )
+ {
+ DamageInfo_ScaleDamage( damageInfo, BOSS_TITAN_CORE_DAMAGE_SCALER )
+ // DamageInfo_ScaleDamage( damageInfo, BOSS_TITAN_CORE_DAMAGE_SCALER_LOW )
+ return
+ }
+}
+
+void function NPCTitanSpawned( entity titan )
+{
+ Assert( !IsMultiplayer() )
+
+ if ( titan.GetTeam() == TEAM_IMC )
+ {
+ switch ( titan.ai.bossTitanType )
+ {
+ case TITAN_WEAK:
+ case TITAN_HENCH:
+ MakeMidHealthTitan( titan )
+
+ case TITAN_BOSS:
+ RegisterBossTitan( titan )
+ ApplyTitanDamageState( titan )
+
+ if ( titan.ai.bossTitanType == TITAN_BOSS )
+ AddEntityCallback_OnDamaged( titan, OnBossTitanCoreMitigation )
+
+ if ( titan.HasKey( "skip_boss_intro" ) && titan.GetValueForKey( "skip_boss_intro" ) == "1" )
+ return
+ thread BossTitanNoIntro( titan )
+ break;
+
+
+ case TITAN_MERC:
+ // TODO: This SetSkin() call should move to RegisterBossTitan() when the above TITAN_BOSS stuff is cleaned up/removed.
+ titan.SetSkin( 1 ) // all titan models have a boss titan version of the skin at index 1
+ RegisterBossTitan( titan )
+ ApplyTitanDamageState( titan )
+
+ AddEntityCallback_OnDamaged( titan, OnBossTitanCoreMitigation )
+
+ if ( titan.HasKey( "skip_boss_intro" ) && titan.GetValueForKey( "skip_boss_intro" ) == "1" )
+ return
+
+ if ( !titan.ai.bossTitanPlayIntro )
+ return
+
+ foreach ( player in GetPlayerArray() )
+ {
+ thread BossTitanIntro( player, titan )
+ }
+ break
+
+ // case TITAN_WEAK:
+ // MakeLowHealthTitan( titan )
+ // break
+
+ case TITAN_AUTO:
+ if ( !IsMultiplayer() && GetMapName() == "sp_hub_timeshift" || GetMapName() == "sp_timeshift_spoke02" )
+ MakeLowHealthTitan( titan )
+ break
+ default:
+ return
+ }
+ }
+}
+
+void function BossTitanNoIntro( entity titan )
+{
+ FlagWait( "PlayerDidSpawn" )
+
+ entity player = GetPlayerArray()[0]
+
+ player.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDeath" )
+
+ // Wait until player sees the boss titan
+ waitthread WaitForHotdropToEnd( titan )
+
+ while ( 1 )
+ {
+ waitthread WaitTillLookingAt( player, titan, true, 60, 5100 )
+ if ( titan.GetEnemy() == null )
+ titan.WaitSignal( "OnSeeEnemy" )
+ else
+ break
+ }
+
+ if ( BossTitanVDUEnabled( titan ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanNoIntro", titan.GetEncodedEHandle() )
+ AddEntityCallback_OnDamaged( titan, OnBossTitanDamaged )
+ AddTitanCallback_OnHealthSegmentLost( titan, OnBossTitanLostSegment )
+}
+
+void function BossTitanIntro( entity player, entity titan, BossTitanIntroData ornull introdata = null )
+{
+ Assert( titan.IsNPC() )
+ Assert( titan.ai.bossCharacterName != "" )
+
+ if ( introdata == null )
+ {
+ BossTitanIntroData defaultData = GetBossTitanIntroData( titan.ai.bossCharacterName )
+ introdata = defaultData
+ }
+
+ expect BossTitanIntroData( introdata )
+
+ player.EndSignal( "OnDeath" )
+ titan.EndSignal( "OnDeath" )
+
+ HideCrit( titan )
+ titan.SetValidHealthBarTarget( false )
+ titan.SetInvulnerable()
+
+ // Wait until player sees the boss titan
+
+ while ( titan.e.isHotDropping )
+ {
+ WaitFrame()
+ }
+
+ HideName( titan )
+ titan.kv.allowshoot = 0
+
+ if ( introdata.waitToStartFlag != "" )
+ FlagWait( introdata.waitToStartFlag )
+
+ if ( introdata.waitForLookat )
+ waitthread WaitTillLookingAt( player, titan, introdata.lookatDoTrace, introdata.lookatDegrees, introdata.lookatMinDist )
+
+ while ( IsPlayerDisembarking( player ) || IsPlayerEmbarking( player ) )
+ {
+ WaitFrame()
+ }
+
+ BossTitanData bossTitanData = GetBossTitanData( titan.ai.bossCharacterName )
+
+ // Create a ref node to animate on
+ vector refPos
+ vector refAngles
+
+ if ( bossTitanData.introAnimTitanRef != "" )
+ {
+ entity titanAnimRef = GetEntByScriptName( bossTitanData.introAnimTitanRef )
+ refPos = titanAnimRef.GetOrigin()
+ refAngles = titanAnimRef.GetAngles()
+ }
+ else
+ {
+ refPos = titan.GetOrigin()
+
+ vector vecToPlayer = Normalize( player.GetOrigin() - titan.GetOrigin() )
+ refAngles = VectorToAngles( vecToPlayer )
+ refAngles = FlattenAngles( refAngles )
+ }
+
+ entity ref
+ if ( introdata.parentRef != null )
+ {
+ ref = introdata.parentRef
+ }
+ else
+ ref = CreateScriptRef( refPos, refAngles )
+
+ entity soul = titan.GetTitanSoul()
+ if ( IsValid( soul.soul.bubbleShield ) )
+ {
+ soul.soul.bubbleShield.Destroy()
+ }
+
+ // Freeze player and clear up the screen
+ StartBossIntro( player, titan, introdata )
+ player.Hide()
+ player.SetVelocity( <0,0,0> )
+ player.FreezeControlsOnServer()
+ player.SetNoTarget( true )
+ player.SetInvulnerable()
+
+ // Do special player view movement
+ FlagSet( "BossTitanViewFollow" )
+
+ // Animate the boss titan
+ entity pilot = CreatePropDynamic( GetBossTitanCharacterModel( titan ) )
+ if ( introdata.parentRef != null )
+ {
+ if ( introdata.parentAttach != "" )
+ {
+ pilot.SetParent( introdata.parentRef, introdata.parentAttach )
+ }
+ else
+ {
+ pilot.SetParent( introdata.parentRef )
+ }
+ }
+ SetTeam( pilot, TEAM_IMC )
+
+ string pilotAnimName = bossTitanData.introAnimPilot
+ string titanAnimName = bossTitanData.introAnimTitan
+
+ float introDuration = 6.0
+
+ Assert( titan.Anim_HasSequence( titanAnimName ), "Your boss titan does not have an intro animation set, or it is missing." )
+
+ introDuration = titan.GetSequenceDuration( titanAnimName )
+
+ svGlobal.levelEnt.Signal( "BossTitanStartAnim" )
+
+ if ( introdata.parentAttach != "" )
+ {
+ thread PlayAnim( pilot, pilotAnimName, ref, introdata.parentAttach, 0.0 )
+ thread PlayAnim( titan, titanAnimName, ref, introdata.parentAttach, 0.0 )
+ }
+ else
+ {
+ thread PlayAnim( pilot, pilotAnimName, ref, 0.0 )
+ thread PlayAnim( titan, titanAnimName, ref, 0.0 )
+ }
+
+ Objective_Hide( player )
+
+ thread BossTitanPlayerView( player, titan, ref, bossTitanData.titanCameraAttachment )
+
+ wait introDuration - SLAMZOOM_TIME
+
+ // Player view returns to normal
+ FlagClear( "BossTitanViewFollow" )
+ EndBossIntro( player, titan )
+
+ wait SLAMZOOM_TIME
+
+ // Return the player screen and movement back to normal
+ player.UnfreezeControlsOnServer()
+ player.SetNoTarget( false )
+ player.ClearInvulnerable()
+ player.Show()
+ pilot.Destroy()
+
+ if ( IsValid( titan ) )
+ {
+ titan.ClearInvulnerable()
+ titan.Solid()
+ AddEntityCallback_OnDamaged( titan, OnBossTitanDamaged )
+ AddTitanCallback_OnHealthSegmentLost( titan, OnBossTitanLostSegment )
+ ShowName( titan )
+ titan.SetValidHealthBarTarget( true )
+ ShowCrit( titan )
+ Signal( titan, "BossTitanIntroEnded" )
+ }
+
+ wait 0.5
+
+ if ( Flag( "AutomaticCheckpointsEnabled" ) )
+ {
+ if ( introdata.checkpointOnlyIfPlayerTitan )
+ {
+ if ( player.IsTitan() )
+ CheckPoint_Forced()
+ }
+ else
+ CheckPoint_Forced()
+ }
+
+ wait 1.0
+
+ titan.kv.allowshoot = 1
+ Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanPostIntro", titan.GetEncodedEHandle(), BossTitanVDUEnabled( titan ) )
+}
+
+void function PlayerParentTest()
+{
+ entity player = GetPlayerArray()[0]
+
+ vector moverStartPos = player.EyePosition()
+ vector moverStartAng = FlattenAngles( player.GetAngles() )
+ entity mover = CreateScriptMover( moverStartPos, moverStartAng )
+
+ player.SnapEyeAngles( moverStartAng )
+ player.SetParent( mover, "", true )
+}
+
+void function BossTitanPlayerView( entity player, entity titan, entity ref, string titanCameraAttachment )
+{
+ bool hasTitanCameraAttachment = titanCameraAttachment != ""
+
+ EndSignal( player, "OnDeath" )
+ EndSignal( titan, "OnDeath" )
+
+ vector moverStartPos = player.CameraPosition()
+
+ vector camFeetDiff = < 0,0,-185 >//player.GetOrigin() - player.CameraPosition()
+
+ vector moverStartAng = player.CameraAngles()
+ entity mover = CreateScriptMover( moverStartPos, moverStartAng )
+
+ // player.SnapEyeAngles( moverStartAng )
+ // player.SetParent( mover, "", true )
+ // ViewConeZero( player )
+
+ entity camera = CreateEntity( "point_viewcontrol" )
+ camera.kv.spawnflags = 56 // infinite hold time, snap to goal angles, make player non-solid
+
+ camera.SetOrigin( player.CameraPosition() )
+ camera.SetAngles( player.CameraAngles() )
+ DispatchSpawn( camera )
+
+ camera.SetParent( mover, "", false )
+
+ OnThreadEnd(
+ function() : ( player, titan, mover, camera )
+ {
+ if ( IsValid( camera ) )
+ {
+ camera.Destroy()
+ }
+
+ mover.Destroy()
+
+ if ( IsValid( player ) )
+ {
+ player.ClearParent()
+ player.ClearViewEntity()
+ RemoveCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD )
+ RemoveCinematicFlag( player, CE_FLAG_TITAN_3P_CAM )
+ }
+
+ if ( IsAlive( titan ) && titan.IsNPC() )
+ {
+ titan.SetNoTarget( false )
+ titan.DisableNPCFlag( NPC_IGNORE_ALL )
+ }
+ }
+ )
+
+ // Slam Zoom In
+ float slamZoomTime = SLAMZOOM_TIME
+ float slamZoomTimeAccel = 0.3
+ float slamZoomTimeDecel = 0.3
+ vector viewOffset = < 200, 100, 160 >
+
+ vector viewPos = ref.GetOrigin() + ( AnglesToForward( ref.GetAngles() ) * viewOffset.x ) + ( AnglesToRight( ref.GetAngles() ) * viewOffset.y ) + ( AnglesToUp( ref.GetAngles() ) * viewOffset.z )
+ vector viewAngles = ref.GetAngles() + <0,180,0>
+ if ( hasTitanCameraAttachment )
+ {
+ WaitFrame()
+ int titanCameraAttachmentID = titan.LookupAttachment( titanCameraAttachment )
+ viewPos = titan.GetAttachmentOrigin( titanCameraAttachmentID )
+ viewAngles = titan.GetAttachmentAngles( titanCameraAttachmentID )
+ }
+
+ float blendTime = 0.5
+ float waittime = 0.3
+ float moveTime = slamZoomTime - blendTime - waittime
+
+ float startTime = Time()
+
+ player.SetVelocity( < 0,0,0 > )
+ player.MakeInvisible()
+ HolsterAndDisableWeapons( player )
+
+ wait waittime // wait for the AI to blend into the anim
+
+ if ( titan.IsNPC() )
+ {
+ titan.SetNoTarget( true )
+ titan.EnableNPCFlag( NPC_IGNORE_ALL )
+ }
+
+ AddCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD )
+ AddCinematicFlag( player, CE_FLAG_TITAN_3P_CAM )
+
+ mover.SetOrigin( player.CameraPosition() )
+ mover.SetAngles( player.CameraAngles() )
+ player.SetViewEntity( camera, true )
+
+ player.SetPredictionEnabled( false )
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ player.SetPredictionEnabled( true )
+ }
+ )
+
+ while ( Time() - startTime < moveTime )
+ {
+ if ( hasTitanCameraAttachment )
+ {
+ int titanCameraAttachmentID = titan.LookupAttachment( titanCameraAttachment )
+ viewPos = titan.GetAttachmentOrigin( titanCameraAttachmentID )
+ viewAngles = titan.GetAttachmentAngles( titanCameraAttachmentID )
+ }
+ mover.NonPhysicsMoveTo( viewPos, moveTime - (Time() - startTime), 0, 0 )
+ mover.NonPhysicsRotateTo( viewAngles, moveTime - (Time() - startTime), 0, 0 )
+ wait 0.1
+ }
+
+ if ( hasTitanCameraAttachment )
+ {
+ mover.SetParent( titan, titanCameraAttachment, false, blendTime )
+ }
+
+ wait 0.5
+
+ int tagID = titan.LookupAttachment( "CHESTFOCUS" )
+ while ( Flag( "BossTitanViewFollow" ) )
+ {
+ vector lookVec = Normalize( titan.GetAttachmentOrigin( tagID ) - mover.GetOrigin() )
+ vector angles = VectorToAngles( lookVec )
+ if ( !hasTitanCameraAttachment )
+ mover.NonPhysicsRotateTo( angles, 0.2, 0.0, 0.0 )
+ WaitFrame()
+ }
+
+ // Slam Zoom Out
+
+ mover.ClearParent()
+
+ startTime = Time()
+ while ( Time() - startTime < slamZoomTime )
+ {
+ moverStartPos = player.GetOrigin() - camFeetDiff
+ moverStartAng = FlattenAngles( player.GetAngles() )
+ mover.NonPhysicsMoveTo( moverStartPos, slamZoomTime - (Time() - startTime), 0, 0 )
+ mover.NonPhysicsRotateTo( moverStartAng, slamZoomTime - (Time() - startTime), 0, 0 )
+ wait 0.1
+ }
+
+ // mover.NonPhysicsMoveTo( moverStartPos, slamZoomTime, slamZoomTimeDecel, slamZoomTimeAccel )
+ // mover.NonPhysicsRotateTo( moverStartAng, slamZoomTime, slamZoomTimeDecel, slamZoomTimeAccel )
+ // wait slamZoomTime
+
+ ClearPlayerAnimViewEntity( player )
+ player.SnapEyeAngles( moverStartAng )
+ DeployAndEnableWeapons( player )
+ player.MakeVisible()
+
+ EmitSoundOnEntity( player, "UI_Lobby_RankChip_Disable" )
+}
+
+void function OnBossTitanDamaged( entity titan, var damageInfo )
+{
+}
+
+void function OnBossTitanLostSegment( entity titan, entity attacker )
+{
+ if ( !titan.IsNPC() || !BossTitanVDUEnabled( titan ) )
+ return
+
+ foreach ( player in GetPlayerArray() )
+ {
+ if ( player == attacker || IsMercTitan( titan ) )
+ Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanLostSegment", titan.GetEncodedEHandle(), GetTitanCurrentRegenTab( titan ) )
+ }
+}
+
+void function OnBossTitanPrimaryFire( entity titan )
+{
+}
+
+bool function IsVDUTitan( entity titan )
+{
+ Assert( IsSingleplayer() )
+
+ if ( titan.GetTeam() != TEAM_IMC )
+ return false
+
+ switch ( titan.ai.bossTitanType )
+ {
+ case TITAN_AUTO:
+ case TITAN_WEAK:
+ return false
+
+ case TITAN_HENCH:
+ case TITAN_MERC:
+ case TITAN_BOSS:
+ return true
+ }
+
+ Assert( 0, "Unknown boss titan type " + titan.ai.bossTitanType )
+ unreachable
+}
+
+bool function IsBossTitan( entity titan )
+{
+ Assert( IsSingleplayer() )
+
+ if ( titan.GetTeam() != TEAM_IMC )
+ return false
+
+ switch ( titan.ai.bossTitanType )
+ {
+ case TITAN_MERC:
+ case TITAN_BOSS:
+ return true
+ }
+
+ return false
+}
+
+int function GetMercCharacterID( entity titan )
+{
+ return titan.ai.mercCharacterID
+}
+
+asset function GetBossTitanCharacterModel( entity titan )
+{
+ int mercCharacterID = GetMercCharacterID( titan )
+ return GetMercCharacterModel( mercCharacterID )
+}
+
+void function OnTitanLostSegment( entity titan, entity attacker )
+{
+ entity player
+
+ if ( !titan.IsPlayer() )
+ player = titan.GetBossPlayer()
+ else
+ player = titan
+
+ if ( !IsValid( player ) )
+ return
+
+ if ( !IsValid( attacker ) )
+ return
+
+ if ( !attacker.IsNPC() || !IsVDUTitan( attacker ) || !BossTitanVDUEnabled( attacker ) )
+ return
+
+ Remote_CallFunction_NonReplay( player, "BossTitanPlayerLostHealthSegment", GetSegmentHealthForTitan( titan ) )
+}
+
+void function BossTitanRetreat( entity titan )
+{
+ if ( !IsVDUTitan( titan ) || !BossTitanVDUEnabled( titan ) )
+ return
+
+ foreach ( player in GetPlayerArray() )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanRetreat", titan.GetEncodedEHandle() )
+ }
+}
+
+void function BossTitanAdvance( entity titan )
+{
+ if ( !IsVDUTitan( titan ) || !BossTitanVDUEnabled( titan ) )
+ return
+
+ foreach ( player in GetPlayerArray() )
+ {
+ Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanAdvance", titan.GetEncodedEHandle() )
+ }
+}
+
+/*
+------------------------------------------------------------
+Low Health Titans
+------------------------------------------------------------
+*/
+
+void function MakeLowHealthTitan( entity ent )
+{
+ entity soul = ent.GetTitanSoul()
+ soul.soul.regensHealth = false
+ thread SetHealthValuesForLowHealth( soul )
+ //ent.SetValidHealthBarTarget( false )
+
+ ent.TakeOffhandWeapon( OFFHAND_ORDNANCE )
+ ent.TakeOffhandWeapon( OFFHAND_ANTIRODEO )
+ ent.TakeOffhandWeapon( OFFHAND_EQUIPMENT )
+ ent.TakeOffhandWeapon( OFFHAND_SPECIAL )
+}
+
+void function MakeMidHealthTitan( entity ent )
+{
+ entity soul = ent.GetTitanSoul()
+ soul.soul.regensHealth = false
+ thread SetHealthValuesForMidHealth( soul )
+}
+
+void function SetHealthValuesForMidHealth( entity soul )
+{
+ soul.EndSignal( "OnDestroy" )
+ WaitEndFrame() // wait for a bunch of variables to start up
+ soul.Signal( SIGNAL_TITAN_HEALTH_REGEN )
+ soul.Signal( "StopShieldRegen" )
+ soul.SetShieldHealth( 0 )
+
+ entity titan = soul.GetTitan()
+ int numSegments = ( titan.GetMaxHealth() / GetSegmentHealthForTitan( titan ) ) - 2
+ Assert( numSegments > 0 )
+ SetSoulBatteryCount( soul, numSegments )
+ if ( IsAlive( titan ) )
+ {
+ soul.soul.skipDoomState = true
+ int segmentHealth = GetSegmentHealthForTitan( titan ) * numSegments
+ titan.SetMaxHealth( segmentHealth )
+ titan.SetHealth( segmentHealth )
+ titan.kv.healthEvalMultiplier = 2
+ }
+
+ titan.Signal( "WeakTitanHealthInitialized" )
+
+ ApplyTitanDamageState( titan )
+}
+
+void function SetHealthValuesForLowHealth( entity soul )
+{
+ soul.EndSignal( "OnDestroy" )
+ WaitEndFrame() // wait for a bunch of variables to start up
+ soul.Signal( SIGNAL_TITAN_HEALTH_REGEN )
+ soul.Signal( "StopShieldRegen" )
+ soul.SetShieldHealth( 0 )
+
+ int numSegments = 2
+
+ SetSoulBatteryCount( soul, numSegments )
+ entity titan = soul.GetTitan()
+ if ( IsAlive( titan ) )
+ {
+ soul.soul.skipDoomState = true
+ int segmentHealth = GetSegmentHealthForTitan( titan ) * numSegments
+ titan.SetMaxHealth( segmentHealth )
+ titan.SetHealth( segmentHealth )
+ titan.kv.healthEvalMultiplier = 2
+ }
+
+ titan.Signal( "WeakTitanHealthInitialized" )
+
+ ApplyTitanDamageState( titan )
+}
+
+void function ApplyTitanDamageState( entity titan )
+{
+ array<float> healthScale = [
+ 1.0,
+ 0.6,
+ 0.3,
+ 0.1
+ ]
+
+ int state = 0
+
+ if ( titan.HasKey( "DamageState" ) )
+ {
+ state = int( titan.GetValueForKey( "DamageState" ) )
+ }
+
+ titan.SetHealth( titan.GetMaxHealth() * healthScale[state] )
+
+ if ( state >= 1 )
+ {
+ string part = [
+ "left_arm",
+ "right_arm"
+ ].getrandom()
+ GibBodyPart( titan, part )
+ }
+
+ if ( state >= 2 )
+ GibBodyPart( titan, "torso" )
+}
+
+bool function IsMercTitan( entity titan )
+{
+ if ( IsMultiplayer() )
+ return false
+ if ( titan.GetTeam() != TEAM_IMC )
+ return false
+ return titan.ai.bossTitanType == TITAN_MERC
+}
+
+bool function BossTitanVDUEnabled( entity titan )
+{
+ return titan.ai.bossTitanVDUEnabled
+} \ No newline at end of file