aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/mod/scripts/vscripts/titan/_titan_health.gnut
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.CustomServers/mod/scripts/vscripts/titan/_titan_health.gnut')
-rw-r--r--Northstar.CustomServers/mod/scripts/vscripts/titan/_titan_health.gnut1072
1 files changed, 1072 insertions, 0 deletions
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/titan/_titan_health.gnut b/Northstar.CustomServers/mod/scripts/vscripts/titan/_titan_health.gnut
new file mode 100644
index 000000000..d600cb03b
--- /dev/null
+++ b/Northstar.CustomServers/mod/scripts/vscripts/titan/_titan_health.gnut
@@ -0,0 +1,1072 @@
+global function TitanHealth_Init
+
+global function Titan_PlayerTookDamage
+global function Titan_NPCTookDamage
+
+global function GetShieldRegenTime
+global function GetShieldRegenDelay
+global function PlayerHasAutoEject
+global function SetTitanCoreTimer
+global function GetTitanCoreTimer
+
+global function AddCreditToTitanCoreBuilderForTitanDamageInflicted
+global function AddCreditToTitanCoreBuilderForTitanDamageReceived
+global function AddCreditToTitanCoreBuilderForDoomInflicted
+global function AddCreditToTitanCoreBuilderForDoomEntered
+global function AddCreditToTitanCoreBuilder
+
+global function TitanShieldRegenThink
+
+global function IsRodeoDamageFromBatteryPack
+global function IsKillshot
+
+global function DoomedHealthThink
+global function UndoomTitan
+global function RestoreTitan
+
+global const SIGNAL_TITAN_HEALTH_REGEN = "BeginTitanHealthRegen"
+global const SIGNAL_TITAN_SHIELD_REGEN = "BeginTitanShieldRegen"
+
+global const TITAN_HEALTH_REGEN_DELAY_MAX = 0.7 // 2.2
+
+#if MP
+// PROTO : Was 99, 49 is for test
+global const TITAN_REGEN_MIN_DAMAGE = 49
+global const TITAN_REGEN_MIN_DAMAGE_DELAY = 0.5
+#elseif SP
+global const TITAN_REGEN_MIN_DAMAGE = 70
+global const TITAN_REGEN_MIN_DAMAGE_DELAY = 0.5
+#endif
+
+// titan health system
+const TITAN_HEALTH_HISTORY_FALLOFF_START = 0 // how many seconds until shield begins to regen
+
+const float TITAN_HEALTH_HISTORY_FALLOFF_END = 4.0
+
+struct
+{
+ float earn_meter_titan_multiplier
+} file
+
+void function TitanHealth_Init()
+{
+ RegisterSignal( SIGNAL_TITAN_HEALTH_REGEN )
+ RegisterSignal( SIGNAL_TITAN_SHIELD_REGEN )
+ RegisterSignal( "Doomed" )
+ RegisterSignal( "TitanUnDoomed" )
+ RegisterSignal( "StopShieldRegen" )
+ RegisterSignal( "WeakTitanHealthInitialized" )
+
+ file.earn_meter_titan_multiplier = GetCurrentPlaylistVarFloat( "earn_meter_titan_multiplier", 1.0 )
+
+ if ( IsMenuLevel() )
+ return
+
+ HealthRegenInit()
+ AddSoulInitFunc( TitanShieldRegenThink ) //This runs even if playlist var titan_shield_regen is set to 0 because it also does stuff like give friendly Pilots protection with shield, etc
+ AddSoulDeathCallback( Titan_MonarchCleanup )
+}
+
+void function UndoomTitan( entity titan, int numSegments )
+{
+ entity soul = titan.GetTitanSoul()
+ string settings = GetSoulPlayerSettings( soul )
+
+ soul.DisableDoomed()
+ int maxHealth
+ int segmentHealth = GetSegmentHealthForTitan( titan )
+ if ( titan.IsNPC() )
+ {
+ maxHealth = int( GetPlayerSettingsFieldForClassName_Health( settings ) )
+ if ( titan.ai.titanSpawnLoadout.setFileMods.contains( "fd_health_upgrade" ) )
+ maxHealth += segmentHealth
+ if ( soul.soul.titanLoadout.setFileMods.contains( "core_health_upgrade" ) )
+ maxHealth += segmentHealth
+ }
+ else
+ {
+ maxHealth = int( titan.GetPlayerModHealth() )
+ }
+ titan.SetMaxHealth( maxHealth )
+ titan.SetHealth( segmentHealth * numSegments )
+ SetSoulBatteryCount( soul, numSegments )
+
+ titan.Signal( "TitanUnDoomed" )
+ UndoomTitan_Body( titan )
+ thread TitanShieldRegenThink( soul )
+}
+
+void function RestoreTitan( entity titan, float percent = 0.625 )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( soul.IsDoomed() )
+ UndoomTitan( titan, 1 )
+
+ soul.nextRegenTime = 0.0
+ soul.SetShieldHealth( soul.GetShieldHealthMax() )
+ int minHealth = int( titan.GetMaxHealth() * percent )
+ if ( titan.GetHealth() < minHealth )
+ {
+ titan.SetHealth( minHealth )
+ int segmentHealth = GetSegmentHealthForTitan( titan )
+ int segments = int( minHealth / float( segmentHealth ) )
+ SetSoulBatteryCount( soul, segments )
+ }
+}
+
+bool function IsRodeoDamage( entity soul, var damageInfo )
+{
+ entity titan = soul.GetTitan()
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !attacker.IsPlayer() )
+ {
+ entity rider = GetRodeoPilot( titan )
+ if ( rider == attacker )
+ return true
+ else
+ return false
+ }
+
+ if ( attacker.GetTitanSoulBeingRodeoed() != soul )
+ return false
+
+ return true
+}
+
+bool function IsCoopRodeoDamage( entity soul, var damageInfo )
+{
+ entity titan = soul.GetTitan()
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ entity rider = GetRodeoPilot( titan )
+ if ( rider == attacker )
+ return true
+ else
+ return false
+
+ unreachable
+}
+
+
+void function CheckRodeoRiderHitsTitan( entity soul, var damageInfo )
+{
+ if ( IsRodeoDamage( soul, damageInfo ) )
+ {
+ //Set Last Attack Time so warning is triggered
+ soul.SetLastRodeoHitTime( Time() )
+
+ DamageInfo_AddCustomDamageType( damageInfo, DF_RODEO )
+ }
+}
+
+bool function ShouldMultiplyRodeoDamage( var damageInfo )
+{
+ switch ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ {
+ case eDamageSourceId.mp_weapon_smr:
+ case eDamageSourceId.mp_titanability_smoke:
+ return false
+
+ case eDamageSourceId.mp_weapon_defender :
+ return true
+ }
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_EXPLOSION )
+ return false
+
+ return true
+}
+
+bool function IsRodeoDamageFromBatteryPack( entity soul, var damageInfo )
+{
+ if ( !IsRodeoDamage( soul, damageInfo ) )
+ return false
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) != damageTypes.rodeoBatteryRemoval )
+ return false
+
+ return true
+}
+
+
+int function ShieldHealthUpdate( entity titan, var damageInfo, bool critHit )
+{
+ entity soul = titan.GetTitanSoul()
+ if ( DamageInfo_GetForceKill( damageInfo ) )
+ {
+ soul.SetShieldHealth( 0 )
+ return 0
+ }
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_BYPASS_SHIELD )
+ return 0
+
+ float damage = DamageInfo_GetDamage( damageInfo )
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+
+ Assert( soul == titan.GetTitanSoul() )
+ int shieldHealth = soul.GetShieldHealth()
+
+ if ( soul.e.forcedRegenTime <= Time() )
+ soul.nextRegenTime = CalculateNextRegenTime( damage, damageType, critHit, expect float( soul.nextRegenTime ), GetShieldRegenDelay( soul ) )
+
+ int result = 0
+ if ( shieldHealth )
+ {
+ DamageInfo_AddCustomDamageType( damageInfo, DF_SHIELD_DAMAGE )
+ result = int( ShieldModifyDamage( titan, damageInfo ) )
+ }
+ else
+ {
+ TakeAwayFriendlyRodeoPlayerProtection( titan )
+ }
+
+ return result
+}
+
+
+void function PlayerOrNPCTitanTookDamage( entity titan, var damageInfo, bool critHit, TitanDamage titanDamage )
+{
+ entity soul = titan.GetTitanSoul()
+
+ if ( !IsValid( soul ) ) //Defensive fix for transient times in frame where Titan can have no soul but be damaged, e.g. immediately after embark
+ return
+
+ // zero out small forces
+ if ( LengthSqr( DamageInfo_GetDamageForce( damageInfo ) ) < 30000 * 30000 )
+ DamageInfo_SetDamageForce( damageInfo, < 0, 0, 0 > )
+
+ titanDamage.shieldDamage = CheckSpecialCaseShieldDamage( soul, titan, damageInfo )
+ if ( titanDamage.shieldDamage < 0 )
+ {
+ CheckRodeoRiderHitsTitan( soul, damageInfo )
+ titanDamage.shieldDamage = ShieldHealthUpdate( titan, damageInfo, critHit )
+ }
+
+ HandleKillshot( titan, damageInfo, titanDamage )
+
+ // health regen based on how much damage dealt to titan
+ float damage = DamageInfo_GetDamage( damageInfo )
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+ bool rodeoDamage = ( ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_RODEO ) > 0 )
+
+ if ( soul.e.forcedRegenTime <= Time() )
+ soul.nextHealthRegenTime = CalculateNextRegenTime( damage, damageType, critHit || rodeoDamage, expect float( soul.nextHealthRegenTime ), GetHealthRegenDelay( soul ) )
+}
+
+int function CheckSpecialCaseShieldDamage( entity soul, entity titan, var damageInfo )
+{
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == damagedef_suicide )
+ return 0
+
+ // no protection from doomed health loss
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return 0
+
+ if ( IsTitanWithinBubbleShield( titan ) || TitanHasBubbleShieldWeapon( titan ) )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return 0
+ }
+
+ return -1
+}
+
+void function Titan_NPCTookDamage( entity titan, var damageInfo, TitanDamage titanDamage )
+{
+ Assert( titan.IsTitan() )
+ Assert( DamageInfo_GetDamage( damageInfo ) > 0 )
+
+ // dead entities can take damage
+ if ( !IsAlive( titan ) )
+ return
+
+ entity soul = titan.GetTitanSoul()
+
+ if ( !IsValid( soul ) ) //Defensive fix for transient times in frame where Titan can have no soul but be damaged, e.g. immediately after embark
+ return
+
+ bool critHit = false
+ if ( CritWeaponInDamageInfo( damageInfo ) )
+ critHit = IsCriticalHit( DamageInfo_GetAttacker( damageInfo ), titan, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageType( damageInfo ) )
+
+ if ( critHit )
+ DamageInfo_AddCustomDamageType( damageInfo, DF_CRITICAL )
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( HeavyArmorCriticalHitRequired( damageInfo ) && CritWeaponInDamageInfo( damageInfo ) && !critHit && IsValid( attacker ) && !attacker.IsTitan())
+ {
+ float shieldHealth = float( titan.GetTitanSoul().GetShieldHealth() )
+ float damage = DamageInfo_GetDamage( damageInfo )
+ if ( shieldHealth - damage <= 0 )
+ {
+ if ( shieldHealth > 0 )
+ DamageInfo_SetDamage( damageInfo, shieldHealth )
+ else
+ DamageInfo_SetDamage( damageInfo, 0 )
+ }
+ }
+
+ PlayerOrNPCTitanTookDamage( titan, damageInfo, critHit, titanDamage )
+
+ RecordDamageToNPCTitanSoul( soul, damageInfo )
+
+ entity owner = GetPetTitanOwner( titan )
+ if ( IsValid( owner ) )
+ AutoTitan_TryMultipleTitanCallout( titan, damageInfo )
+
+ if ( GetDoomedState( titan ) )
+ titanDamage.shieldDamage = 0
+}
+
+void function Titan_PlayerTookDamage( entity player, var damageInfo, entity attacker, bool critHit, TitanDamage titanDamage )
+{
+ Assert( player.IsTitan() )
+
+ float damage = DamageInfo_GetDamage( damageInfo )
+
+ if ( !IsAlive( player ) )
+ return
+
+ entity soul = player.GetTitanSoul()
+ if ( !IsValid( soul ) ) //Defensive fix for transient times in frame where Titan can have no soul but be damaged, e.g. immediately after embark
+ return
+
+ if ( damage > 0 )
+ AdjustVelocityFromHit( player, damageInfo, attacker, damage, critHit )
+
+ if ( IsDemigod( player ) )
+ EntityDemigod_TryAdjustDamageInfo( player, damageInfo )
+
+ bool critHit = false
+ if ( CritWeaponInDamageInfo( damageInfo ) )
+ critHit = IsCriticalHit( attacker, player, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageType( damageInfo ) )
+
+ if ( critHit )
+ DamageInfo_AddCustomDamageType( damageInfo, DF_CRITICAL )
+
+ #if MP
+ if ( HeavyArmorCriticalHitRequired( damageInfo ) && CritWeaponInDamageInfo( damageInfo ) && !critHit && IsValid( attacker ) && !attacker.IsTitan())
+ {
+ float shieldHealth = float( player.GetTitanSoul().GetShieldHealth() )
+ if ( shieldHealth - damage <= 0 )
+ {
+ if ( shieldHealth > 0 )
+ DamageInfo_SetDamage( damageInfo, shieldHealth )
+ else
+ DamageInfo_SetDamage( damageInfo, 0 )
+ }
+ }
+ #endif
+
+ PlayerOrNPCTitanTookDamage( player, damageInfo, critHit, titanDamage )
+}
+
+bool function IsKillshot( entity ent, var damageInfo, entity titanSoul )
+{
+ float damage = DamageInfo_GetDamage( damageInfo )
+ int health = ent.GetHealth()
+
+ if ( health - damage > DOOMED_MIN_HEALTH )
+ return false
+
+ return true
+}
+
+bool function ShouldDoomTitan( entity ent, var damageInfo )
+{
+ if ( DoomStateDisabled() )
+ return false
+
+ if ( GetDoomedState( ent ) )
+ return false
+
+ if ( DamageInfo_GetForceKill( damageInfo ) )
+ return false
+
+ float doomedHealth = GetTitanSoulDoomedHealth( ent.GetTitanSoul() )
+ if ( doomedHealth <= 0 )
+ return false
+
+ entity soul = ent.GetTitanSoul()
+ if ( soul.soul.skipDoomState )
+ return false
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_SKIP_DAMAGE_PROT )
+ return doomedHealth > ( DamageInfo_GetDamage( damageInfo ) - ent.GetHealth() )
+
+ bool skipDoom = ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_SKIPS_DOOMED_STATE ) > 0
+ return !skipDoom
+}
+
+bool function HandleKillshot( entity ent, var damageInfo, TitanDamage titanDamage )
+{
+ #if NPC_TITAN_PILOT_PROTOTYPE
+ if ( TitanHasNpcPilot( ent ) ) //an npc titan that was dropped by an npc human
+ {
+ float damage = DamageInfo_GetDamage( damageInfo )
+ int health = ent.GetHealth()
+
+ if ( health - damage <= 0 )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ thread TitanEjectPlayer( ent )
+ }
+
+ return
+ }
+ #endif
+
+ if ( ent.IsPlayer() && ent.IsBuddhaMode() )
+ return false
+
+ entity titanSoul = ent.GetTitanSoul()
+
+ if ( IsKillshot( ent, damageInfo, titanSoul ) )
+ {
+ entity boss = titanSoul.GetBossPlayer()
+ Soul_SetLastAttackInfo( titanSoul, damageInfo )
+
+ if ( ShouldDoomTitan( ent, damageInfo ) )
+ {
+ // Added via AddCallback_OnTitanDoomed
+ foreach ( callbackFunc in svGlobal.onTitanDoomedCallbacks )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+
+ if ( IsMultiplayer() )
+ {
+ entity attacker = expect entity( expect table( titanSoul.lastAttackInfo ).attacker )
+ if ( IsValid( attacker ) )
+ {
+ entity bossPlayer = attacker.GetBossPlayer()
+ if ( attacker.IsNPC() && IsValid( bossPlayer ) )
+ attacker = bossPlayer
+
+ if ( attacker.IsPlayer() )
+ ScoreEvent_TitanDoomed( ent, attacker, damageInfo )
+ }
+ }
+
+ thread DoomedHealthThink( titanSoul, damageInfo )
+
+ titanDamage.doomedNow = true
+ titanDamage.doomedDamage = int( DamageInfo_GetDamage( damageInfo ) )
+
+ int health = ent.GetHealth()
+ DamageInfo_SetDamage( damageInfo, health - 1 )
+ return true
+ }
+ else
+ {
+ // handle auto eject here
+ if ( ent.IsPlayer() && PlayerHasAutoEject( ent ) )
+ {
+ int health = ent.GetHealth()
+ DamageInfo_SetDamage( damageInfo, health - 1 )
+ thread HandleAutoEject( ent, titanSoul )
+ return false
+ }
+ }
+ }
+
+ // Handle doom state damage
+ if ( GetDoomedState( ent ) )
+ {
+ // as long as we're dying but not yet ejecting, the last player to damage us gets credit
+ if ( titanSoul.IsEjecting() )
+ {
+ Soul_SetLastAttackInfo( titanSoul, damageInfo )
+ }
+ else if ( ent.IsPlayer() && PlayerHasAutoEject( ent ) ) //Handle auto eject for when the frame in which Titan became doomed was not valid for ejecting, e.g. melee
+ {
+ int health = ent.GetHealth()
+ DamageInfo_SetDamage( damageInfo, health - 1 )
+ thread HandleAutoEject( ent, titanSoul )
+ return false
+ }
+
+ // protect players who eject early
+ // if ( ent.IsPlayer() && IsEjectProtected( ent, damageInfo ) )
+ // DamageInfo_SetDamage( damageInfo, 0 )
+
+ // slight protection to prevent multiple rapid damage events from eating through doomed state health
+ if ( Time() - titanSoul.soul.doomedStartTime < TITAN_DOOMED_INVUL_TIME && !DamageInfo_GetForceKill( damageInfo ) )
+ DamageInfo_SetDamage( damageInfo, 0 )
+ }
+ else
+ {
+ Soul_SetLastAttackInfo( titanSoul, damageInfo )
+ }
+
+ return false
+}
+
+bool function PlayerHasAutoEject( entity player )
+{
+ if ( player.IsBot() )
+ return false
+
+ if ( !PlayerHasPassive( player, ePassives.PAS_AUTO_EJECT ) )
+ return false
+
+ return true
+}
+
+
+void function AdjustVelocityFromHit( entity player, var damageInfo, entity attacker, float damage, bool critHit )
+{
+/*
+ if ( DamageInfo_GetDamageCriticalHitScale( damageInfo ) > 1.0 )
+ {
+ // if you can crit, you have to crit!
+ if ( !critHit )
+ return
+ }
+*/
+
+ //printt( " " )
+ //printt( "damage: " + damage )
+
+ vector damageForward = DamageInfo_GetDamageForce( damageInfo )
+ damageForward.z = 0
+ //printt( "damageForward " + damageForward )
+
+ damageForward.Norm()
+
+ //vector org = DamageInfo_GetDamagePosition( damageInfo )
+ //DebugDrawLine( org, org + damageForward * 250, 255, 0, 0, true, 5.0 )
+
+ vector velocity = player.GetVelocity()
+ vector velForward = player.GetVelocity()
+ velForward.z = 0
+ velForward.Norm()
+
+ //DebugDrawLine( org, org + velForward * 250, 0, 255, 0, true, 5.0 )
+
+ float dot = DotProduct( velForward, damageForward )
+
+ // only stop from the ~front cone
+ if ( dot >= -0.5 )
+ return
+
+ float speedPercent
+
+ switch ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ {
+ //case eDamageSourceId.mp_titanweapon_40mm:
+ // speedPercent = GraphCapped( damage, 0, 750, 1, 0 )
+ // break
+
+ case eDamageSourceId.mp_titanweapon_xo16:
+ speedPercent = 0.075
+ break
+
+ default:
+ speedPercent = GraphCapped( damage, 0, 2500, 0, 1.0 )
+ }
+
+ //float dif = GraphCapped( dot, -1, -0.5, 1, 0 )
+ //speedPercent = speedPercent * dif + ( 1.0 - dif )
+
+ speedPercent *= GraphCapped( dot, -1.0, -0.5, 1, 0 )
+
+ //printt( " " )
+ //printt( "Damage: " + damage )
+ //printt( "dot: " + dot )
+ //printt( "speedPercent: " + speedPercent )
+ speedPercent = 1.0 - speedPercent
+ // make the dot into a tighter range
+ //dot += 0.5
+ //dot *= -2.0
+
+ //printt( "modifier: " + ( speedPercent ) )
+ velocity *= ( speedPercent )
+ player.SetVelocity( velocity )
+}
+
+
+
+void function DoomedHealthThink( entity titanSoul, var damageInfo )
+{
+ Assert( expect table( titanSoul.lastAttackInfo ).attacker, "Player entered reserve health with no attacker" )
+
+ entity soulOwner = titanSoul.GetTitan()
+ Assert( IsValid( soulOwner ), "Invalid owner " + soulOwner )
+
+ titanSoul.soul.doomedStartTime = Time()
+
+ // kill any existing health regen thread
+ titanSoul.Signal( SIGNAL_TITAN_HEALTH_REGEN )
+ titanSoul.Signal( SIGNAL_TITAN_SHIELD_REGEN )
+
+ titanSoul.EndSignal( "OnDestroy" )
+ titanSoul.EndSignal( "OnTitanDeath" )
+
+ float tickRate = 0.15
+ float maxDoomedHealth = GetTitanSoulDoomedHealth( titanSoul )
+ float doomedHealth = maxDoomedHealth
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_SKIP_DAMAGE_PROT )
+ doomedHealth = min( doomedHealth + soulOwner.GetHealth() - DamageInfo_GetDamage( damageInfo ), doomedHealth )
+
+ float DPS = (doomedHealth / TITAN_DOOMED_MAX_DURATION )
+
+ titanSoul.EnableDoomed()
+ titanSoul.doomedTime = Time()
+ soulOwner.SetDoomed()
+ DoomTitan( soulOwner )
+ soulOwner.Signal( "Doomed" )
+ titanSoul.Signal( "Doomed" )
+
+ // allow the damage to go through before resetting the health, so that we get proper damage indicators, etc...
+ // this process should also be in code
+ WaitEndFrame()
+
+ // grab the soul owner again since there was a wait
+ soulOwner = titanSoul.GetTitan()
+ if ( !IsValid( soulOwner ) )
+ return
+
+ if ( PROTO_AlternateDoomedState() )
+ {
+ //printt( soulOwner.GetHealth() )
+ soulOwner.SetHealth( doomedHealth )
+ soulOwner.SetMaxHealth( maxDoomedHealth )
+ //soulOwner.SetHealthPerSegment( 0 )
+
+ soulOwner.ClearDoomed()
+
+ if ( soulOwner.IsPlayer() && PlayerHasAutoEject( soulOwner ) )
+ {
+ HandleAutoEject( soulOwner, titanSoul )
+ }
+ else
+ {
+ //If it's an auto-titan with auto-eject, this just instantly kills it.
+ var attacker = ( "attacker" in titanSoul.lastAttackInfo ) ? expect table( titanSoul.lastAttackInfo ).attacker : null
+ expect entity( attacker )
+ var inflictor = ( "inflictor" in titanSoul.lastAttackInfo ) ? expect table( titanSoul.lastAttackInfo ).inflictor : null
+ expect entity( inflictor )
+ var damageSource = ( "damageSourceId" in titanSoul.lastAttackInfo ) ? expect table( titanSoul.lastAttackInfo ).damageSourceId : -1
+ int damageFlags = expect int( expect table( titanSoul.lastAttackInfo ).scriptType )
+ if ( SoulHasPassive( titanSoul, ePassives.PAS_AUTO_EJECT ) )
+ {
+ int scriptDamageType = damageTypes.titanEjectExplosion | damageFlags
+ soulOwner.Die( attacker, inflictor, { scriptType = scriptDamageType, damageSourceId = damageSource } )
+ }
+ }
+ return
+ }
+ soulOwner.SetHealth( doomedHealth )
+ soulOwner.SetMaxHealth( maxDoomedHealth )
+ //soulOwner.SetHealthPerSegment( 0 )
+
+ string settings = GetSoulPlayerSettings( titanSoul )
+ float damageMod = 1.0
+ while ( true )
+ {
+ table lastAttackInfo = expect table( titanSoul.lastAttackInfo )
+
+ table extraDeathInfo = {}
+ extraDeathInfo.scriptType <- (DF_NO_INDICATOR | DF_DOOMED_HEALTH_LOSS)
+ if ( expect int( lastAttackInfo.scriptType ) & DF_BURN_CARD_WEAPON )
+ extraDeathInfo.scriptType = expect int( extraDeathInfo.scriptType ) | DF_BURN_CARD_WEAPON
+ if ( expect int( lastAttackInfo.scriptType ) & DF_VORTEX_REFIRE )
+ extraDeathInfo.scriptType = expect int( extraDeathInfo.scriptType ) | DF_VORTEX_REFIRE
+
+ extraDeathInfo.damageSourceId <- lastAttackInfo.damageSourceId
+
+ entity soulOwner = titanSoul.GetTitan()
+ if ( !IsValid( soulOwner ) )
+ return
+ if ( soulOwner.IsPlayer() )
+ {
+ //if ( PlayerHasPassive( soulOwner, ePassives.PAS_DOOMED_TIME ) )
+ // damageMod = 0.4
+ //else
+ // damageMod = 1.0
+
+ if ( PlayerHasAutoEject( soulOwner ) )
+ {
+ //printt( "About to Auto Eject" )
+ // do it in the loop cause player could somehow get in a titan in doomed state
+ HandleAutoEject( soulOwner, titanSoul )
+ }
+ }
+
+ float dmgAmount = DPS * tickRate * damageMod
+
+ soulOwner.TakeDamage( dmgAmount, expect entity( lastAttackInfo.attacker ), expect entity( lastAttackInfo.inflictor ), extraDeathInfo )
+
+ wait tickRate
+ }
+}
+
+void function HandleAutoEject( entity rider, entity soul )
+{
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "OnTitanDeath" )
+
+ thread TitanEjectPlayer( rider )
+ if ( soul.IsEjecting() )
+ {
+ // so we don't cloak the titan during the ejection animation
+ if ( GetNuclearPayload( rider ) > 0 )
+ wait 2.0
+ else
+ wait 1.0
+
+ EnableCloak( rider, 7.0 )
+ return
+ }
+}
+
+void function TitanShieldRegenThink( entity soul )
+{
+ thread TitanShieldRegenThink_Internal( soul )
+}
+
+// HACK: this technically doesn't work properly because server framerate and all that jazz. Should really be in code.
+void function TitanShieldRegenThink_Internal( entity soul )
+{
+ soul.EndSignal( "OnDestroy" )
+ soul.EndSignal( "Doomed" )
+ soul.EndSignal( "StopShieldRegen" )
+
+ //Shield starts at 0 health for now
+ string settings = GetSoulPlayerSettings( soul )
+ bool hasShield = Dev_GetPlayerSettingByKeyField_Global( settings, "start_with_shields" ) == 1
+
+ if ( !hasShield )
+ soul.SetShieldHealth( 0 )
+
+ int lastShieldHealth = soul.GetShieldHealth()
+ bool shieldHealthSound = false
+ int maxShield = soul.GetShieldHealthMax()
+ float lastTime = Time()
+
+ while ( true )
+ {
+ entity titan = soul.GetTitan()
+ if ( !IsValid( titan ) )
+ return
+
+ int shieldHealth = soul.GetShieldHealth()
+ Assert( titan )
+
+ if ( lastShieldHealth <= 0 && shieldHealth && titan.IsPlayer() )
+ {
+ EmitSoundOnEntityOnlyToPlayer( titan, titan, "titan_energyshield_up_1P" )
+ shieldHealthSound = true
+ if ( titan.IsTitan() )
+ {
+ GiveFriendlyRodeoPlayerProtection( titan )
+ }
+ else
+ {
+ if ( titan.IsPlayer() )
+ {
+ printt( "Player was " + titan.GetPlayerSettings() )
+ }
+
+ printt( "ERROR! Expected Titan, but got " + titan )
+ }
+ }
+ else if ( shieldHealthSound && shieldHealth == soul.GetShieldHealthMax() )
+ {
+ shieldHealthSound = false
+ }
+ else if ( lastShieldHealth > shieldHealth && shieldHealthSound )
+ {
+ StopSoundOnEntity( titan, "titan_energyshield_up_1P" )
+ shieldHealthSound = false
+ }
+
+ if ( Time() >= soul.nextRegenTime && TitanHasRegenningShield( soul ) )
+ {
+ float shieldRegenRate = maxShield / ( GetShieldRegenTime( soul ) / SHIELD_REGEN_TICK_TIME )
+
+ if ( SoulHasPassive( soul, ePassives.PAS_SHIELD_BOOST ) )
+ shieldRegenRate = SHIELD_BEACON_REGEN_RATE
+
+ float frameTime = max( 0.0, Time() - lastTime )
+ shieldRegenRate = shieldRegenRate * frameTime / SHIELD_REGEN_TICK_TIME
+ // Faster shield recharge if we have Fusion Core active ability ( Stryder Signature )
+ //if ( titan.IsPlayer() && PlayerHasPassive( titan, ePassives.PAS_FUSION_CORE ) )
+ // shieldRegenRate *= 1.25
+
+ soul.SetShieldHealth( minint( soul.GetShieldHealthMax(), int( shieldHealth + shieldRegenRate ) ) )
+ }
+
+ lastShieldHealth = shieldHealth
+ lastTime = Time()
+ WaitFrame()
+ }
+}
+
+float function GetShieldRegenTime( entity soul )
+{
+ float time
+ if ( SoulHasPassive( soul, ePassives.PAS_SHIELD_REGEN ) )
+ time = TITAN_SHIELD_REGEN_TIME * 0.5
+ else
+ time = TITAN_SHIELD_REGEN_TIME
+
+ return time
+}
+
+float function GetHealthRegenDelay( entity soul )
+{
+ if ( GetDoomedState( soul.GetTitan() ) )
+ return TITAN_DOOMED_REGEN_DELAY
+
+ return GetShieldRegenDelay( soul )
+}
+
+float function GetShieldRegenDelay( entity soul )
+{
+ float regenDelay = TITAN_SHIELD_REGEN_DELAY
+
+ string settings = GetSoulPlayerSettings( soul )
+ regenDelay = expect float( Dev_GetPlayerSettingByKeyField_Global( settings, "titan_regen_delay" ) )
+
+ float delay
+ if ( SoulHasPassive( soul, ePassives.PAS_SHIELD_REGEN ) )
+ delay = regenDelay - 1.0
+ else
+ delay = regenDelay
+
+ if ( SoulHasPassive( soul, ePassives.PAS_SHIELD_BOOST ) )
+ delay = 2.0
+
+ return delay
+}
+
+void function RecordDamageToNPCTitanSoul( entity soul, var damageInfo )
+{
+ float damage = DamageInfo_GetDamage( damageInfo )
+
+ vector inflictOrigin = <0.0,0.0,0.0>
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+ if ( IsValid( inflictor ) )
+ inflictOrigin = inflictor.GetOrigin()
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+
+ entity weapon = DamageInfo_GetWeapon( damageInfo )
+ array<string> weaponMods
+ if ( IsValid( weapon ) )
+ weaponMods = weapon.GetMods()
+
+ StoreDamageHistoryAndUpdate( soul, TITAN_HEALTH_HISTORY_FALLOFF_END, damage, inflictOrigin, DamageInfo_GetCustomDamageType( damageInfo ), DamageInfo_GetDamageSourceIdentifier( damageInfo ), attacker, weaponMods )
+}
+
+void function AutoTitan_TryMultipleTitanCallout( entity titan, var damageInfo )
+{
+ array<entity> titans = GetTitansHitMeInTime( titan.GetTitanSoul(), 5 )
+ entity enemy = titan.GetEnemy()
+ if ( IsAlive( enemy ) && enemy.IsTitan() && !titans.contains( enemy ) )
+ titans.append( enemy )
+
+ int totalEngagedTitans = titans.len()
+
+ if ( totalEngagedTitans == 1 )
+ PlayAutoTitanConversation( titan, "autoEngageTitan" )
+ else if ( totalEngagedTitans > 1 )
+ PlayAutoTitanConversation( titan, "autoEngageTitans" )
+}
+
+float function CalculateNextRegenTime( float damage, int damageType, bool critHit, float oldNextRegenTime, float maxRegenDelay )
+{
+ if ( damage >= TITAN_REGEN_MIN_DAMAGE || critHit || damageType & DF_STOPS_TITAN_REGEN )
+ {
+ if ( PROTO_VariableRegenDelay() )
+ {
+ // regen delay based on damage dealt
+ float minRegenDelay = 1.0
+ float regenDelay = GraphCapped( damage, 100, 1000, minRegenDelay, maxRegenDelay )
+
+ float nextRegenTime = oldNextRegenTime
+ float delayBasedOnCurrentTime = Time() + regenDelay
+ float delayBasedOnPreviousDelay = nextRegenTime + regenDelay
+ maxRegenDelay = Time() + maxRegenDelay
+
+ delayBasedOnCurrentTime = min( delayBasedOnCurrentTime, maxRegenDelay )
+ delayBasedOnPreviousDelay = min( delayBasedOnPreviousDelay, maxRegenDelay )
+ nextRegenTime = max( delayBasedOnCurrentTime, delayBasedOnPreviousDelay )
+
+ return nextRegenTime
+ }
+ else
+ {
+ // old style
+ return Time() + maxRegenDelay
+ }
+ }
+ else
+ {
+ float addTime = TITAN_REGEN_MIN_DAMAGE_DELAY
+
+ if ( oldNextRegenTime <= Time() + addTime )
+ return Time() + addTime
+ }
+
+ return oldNextRegenTime
+}
+
+void function AddCreditToTitanCoreBuilderForTitanDamageInflicted( entity titanAttacker, float damage )
+{
+ Assert( TitanDamageRewardsTitanCoreTime() )
+
+ float rateRaw = CORE_BUILD_PERCENT_FROM_TITAN_DAMAGE_INFLICTED
+ float rate = (rateRaw * 0.01)
+ float credit = (rate * damage)
+ if ( credit > 0.0 )
+ AddCreditToTitanCoreBuilder( titanAttacker, credit )
+}
+
+void function AddCreditToTitanCoreBuilderForTitanDamageReceived( entity titanVictim, float damage )
+{
+ Assert( TitanDamageRewardsTitanCoreTime() )
+
+ float rateRaw = CORE_BUILD_PERCENT_FROM_TITAN_DAMAGE_RECEIVED
+ float rate = (rateRaw * 0.01)
+ float credit = (rate * damage)
+ if ( credit > 0.0 )
+ AddCreditToTitanCoreBuilder( titanVictim, credit )
+}
+
+void function AddCreditToTitanCoreBuilderForDoomInflicted( entity titanAttacker )
+{
+ Assert( TitanDamageRewardsTitanCoreTime() )
+
+ float valueRaw = CORE_BUILD_PERCENT_FROM_DOOM_INFLICTED
+ float credit = (valueRaw * 0.01)
+ if ( credit > 0.0 )
+ AddCreditToTitanCoreBuilder( titanAttacker, credit )
+}
+
+void function AddCreditToTitanCoreBuilderForDoomEntered( entity titanVictim )
+{
+ Assert( TitanDamageRewardsTitanCoreTime() )
+
+ float valueRaw = CORE_BUILD_PERCENT_FROM_DOOM_ENTERED
+ float credit = (valueRaw * 0.01)
+ if ( credit > 0.0 )
+ AddCreditToTitanCoreBuilder( titanVictim, credit )
+}
+
+void function AddCreditToTitanCoreBuilder( entity titan, float credit )
+{
+ Assert( TitanDamageRewardsTitanCoreTime() )
+
+ entity soul = titan.GetTitanSoul()
+ if ( !IsValid( soul ) )
+ return
+
+ entity bossPlayer = soul.GetBossPlayer()
+
+ if ( titan.IsPlayer() )
+ {
+ if ( !IsValid( bossPlayer ) )
+ return
+
+ if ( bossPlayer.IsTitan() && TitanCoreInUse( bossPlayer ) )
+ return
+ }
+ else
+ {
+ Assert( titan.IsNPC() )
+ if ( TitanCoreInUse( titan ) )
+ return
+ }
+
+ if ( !IsAlive( titan ) )
+ return
+
+ if ( SoulHasPassive( soul, ePassives.PAS_VANGUARD_COREMETER ) )
+ credit *= 1.10
+
+ credit *= file.earn_meter_titan_multiplier
+ #if MP
+ if ( titan.IsPlayer() )
+ {
+ float coreModifier = titan.GetPlayerNetFloat( "coreMeterModifier" )
+ if ( coreModifier >= 0.5 )
+ credit *= FD_HOT_STREAK_MULTIPLIER
+ }
+ #endif
+
+ bool coreWasAvailable = false
+
+ if ( IsValid( bossPlayer ) )
+ coreWasAvailable = IsCoreChargeAvailable( bossPlayer, soul )
+
+ float oldTotalCredit = SoulTitanCore_GetNextAvailableTime( soul )
+ float newTotalCredit = (credit + oldTotalCredit)
+ if ( newTotalCredit >= 0.998 ) //JFS - the rui has a +0.001 for showing the meter as full. This fixes the case where the core meter displays 100 but can't be fired.
+ newTotalCredit = 1.0
+ SoulTitanCore_SetNextAvailableTime( soul, newTotalCredit )
+
+ if ( IsValid( bossPlayer ) && !coreWasAvailable && IsCoreChargeAvailable( bossPlayer, soul ) )
+ {
+ AddPlayerScore( bossPlayer, "TitanCoreEarned" )
+ #if MP
+ UpdateTitanCoreEarnedStat( bossPlayer, titan )
+ PIN_PlayerAbilityReady( bossPlayer, "core" )
+ #endif
+ }
+
+ #if MP
+ if ( IsValid( bossPlayer ) )
+ JFS_PlayerEarnMeter_CoreRewardUpdate( titan, oldTotalCredit, newTotalCredit )
+ #endif
+
+ #if HAS_TITAN_TELEMETRY
+ if ( titan.IsPlayer() )
+ {
+ if ( IsCoreChargeAvailable( titan, soul ) )
+ {
+ TitanHints_TryShowHint( titan, [OFFHAND_EQUIPMENT] )
+ }
+ }
+ #endif
+}
+
+float function GetTitanCoreTimer( entity titan )
+{
+ Assert( titan.IsTitan() )
+ entity soul = titan.GetTitanSoul()
+ Assert( soul )
+
+ return SoulTitanCore_GetNextAvailableTime( soul ) - Time()
+}
+
+
+
+void function SetTitanCoreTimer( entity titan, float timeDiff )
+{
+ Assert( !TitanDamageRewardsTitanCoreTime() )
+
+ Assert( titan.IsTitan() )
+ entity soul = titan.GetTitanSoul()
+ Assert( soul )
+
+ float newTime = Time() + timeDiff
+ SoulTitanCore_SetNextAvailableTime( soul, max( Time() - 1, newTime ) )
+}
+
+
+void function Titan_MonarchCleanup( entity soul, var damageInfo )
+{
+ entity titan = soul.GetTitan()
+
+ if ( !IsValid( titan ) )
+ return
+
+ int statesIndex = titan.FindBodyGroup( "states" )
+ if ( statesIndex <= -1 )
+ return
+
+ titan.SetBodygroup( statesIndex, 2 )
+} \ No newline at end of file