From 207facbc402f5639cbcd31f079214351ef605cf2 Mon Sep 17 00:00:00 2001 From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> Date: Tue, 22 Jun 2021 14:30:49 +0100 Subject: initial commit after moving to new repo --- .../scripts/vscripts/titan/_titan_health.gnut | 1072 ++++++++++++++++++++ 1 file changed, 1072 insertions(+) create mode 100644 Northstar.CustomServers/scripts/vscripts/titan/_titan_health.gnut (limited to 'Northstar.CustomServers/scripts/vscripts/titan/_titan_health.gnut') diff --git a/Northstar.CustomServers/scripts/vscripts/titan/_titan_health.gnut b/Northstar.CustomServers/scripts/vscripts/titan/_titan_health.gnut new file mode 100644 index 000000000..d600cb03b --- /dev/null +++ b/Northstar.CustomServers/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 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 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 -- cgit v1.2.3