aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/scripts/vscripts/mp/_codecallbacks.gnut
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.CustomServers/scripts/vscripts/mp/_codecallbacks.gnut')
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_codecallbacks.gnut999
1 files changed, 999 insertions, 0 deletions
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_codecallbacks.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_codecallbacks.gnut
new file mode 100644
index 000000000..2e5651422
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_codecallbacks.gnut
@@ -0,0 +1,999 @@
+untyped
+
+
+global function CodeCallback_Init
+global function CodeCallback_DamagePlayerOrNPC
+global function GameModeRulesShouldGiveTimerCredit
+global function SetGameModeRulesShouldGiveTimerCredit
+global function SetGameModeRulesEarnMeterOnDamage
+global function GetDamageOrigin
+global function CodeCallBack_ShouldTriggerSniperCam
+global function CodeCallback_ForceAIMissPlayer
+global function CodeCallback_OnTouchHealthKit
+global function CodeCallback_OnPlayerGrappled
+global function CodeCallback_OnProjectileGrappled
+global function DamageInfo_ScaleDamage
+global function CodeCallback_CheckPassThroughAddsMods
+global function SetTitanMeterGainScale
+
+#if MP
+global function CodeCallback_OnServerAnimEvent
+#endif
+
+struct AccumulatedDamageData
+{
+ float accumulatedDamage
+ float lastDamageTime
+}
+
+struct
+{
+ float titanMeterGainScale = 0.0001
+ bool functionref( entity, entity, var ) ShouldGiveTimerCreditGameModeRules
+ void functionref( entity, entity, TitanDamage, float ) earnMeterOnDamageGameModeRulesCallback
+
+ table<entity, AccumulatedDamageData> playerAccumulatedDamageData
+} file
+
+void function CodeCallback_Init()
+{
+ file.ShouldGiveTimerCreditGameModeRules = ShouldGiveTimerCredit_Default
+ file.earnMeterOnDamageGameModeRulesCallback = GameModeRulesEarnMeterOnDamage_Default
+ RegisterSignal( "DamagedPlayerOrNPC" )
+ RegisterSignal( "UpdateAccumulatedDamageAfterDelay" )
+
+ AddCallback_OnClientConnected( OnClientConnected )
+}
+
+void function OnClientConnected( entity player )
+{
+ AccumulatedDamageData damageData
+ file.playerAccumulatedDamageData[player] <- damageData
+}
+
+// TODO: Get an equivalent callback happening on the client, so we can stop using ServerCallback_PlayerTookDamage which is always out of date to some degree.
+void function CodeCallback_DamagePlayerOrNPC( entity ent, var damageInfo )
+{
+ bool entIsPlayer = ent.IsPlayer()
+ bool entIsTitan = ent.IsTitan()
+ bool entIsNPC = ent.IsNPC()
+
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+
+ bool attackerIsPlayer = false
+ bool attackerIsTitan = false
+ bool attackerIsNPC = false
+
+ if ( IsValid( attacker ) )
+ {
+ attackerIsPlayer = attacker.IsPlayer()
+ attackerIsTitan = attacker.IsTitan()
+ attackerIsNPC = attacker.IsNPC()
+ }
+
+ // Set damage source correctly when npc grunts or titans try to melee us
+ if ( attackerIsNPC && DamageInfo_GetCustomDamageType( damageInfo ) & DF_MELEE )
+ {
+ if ( IsValid( attacker ) )
+ {
+ if ( attackerIsTitan )
+ {
+ DamageInfo_SetDamageSourceIdentifier( damageInfo, eDamageSourceId.auto_titan_melee )
+ }
+ else if ( IsSpectre( attacker ) )
+ {
+ DamageInfo_SetDamageSourceIdentifier( damageInfo, eDamageSourceId.spectre_melee )
+ }
+ else if ( IsProwler( attacker ) )
+ {
+ DamageInfo_SetDamageSourceIdentifier( damageInfo, eDamageSourceId.prowler_melee )
+ }
+ else if ( IsSuperSpectre( attacker ) )
+ {
+ DamageInfo_SetDamageSourceIdentifier( damageInfo, eDamageSourceId.super_spectre_melee )
+ }
+ else
+ {
+ DamageInfo_SetDamageSourceIdentifier( damageInfo, eDamageSourceId.grunt_melee )
+ }
+ }
+ }
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( "CodeCallback_DamagePlayerOrNPC ent:", ent )
+ printt( " Attacker:", DamageInfo_GetAttacker( damageInfo ) )
+ printt( " Inflictor:", DamageInfo_GetInflictor( damageInfo ) )
+ printt( " Distance:", DamageInfo_GetDistFromAttackOrigin( damageInfo ) )
+ printt( " Original damage:", DamageInfo_GetDamage( damageInfo ) )
+ printt( " Hitbox:", DamageInfo_GetHitBox( damageInfo ) )
+ int sourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ printt( " SourceID:", sourceID )
+ if ( sourceID == -1 )
+ printt( " SourceID: From Code (npc melee, etc)" )
+ else
+ printt( " SourceID:", GetObitFromDamageSourceID( sourceID ) )
+
+ PrintDamageFlags( DamageInfo_GetCustomDamageType( damageInfo ) )
+ #endif
+
+ if ( !ScriptCallback_ShouldEntTakeDamage( ent, damageInfo ) )
+ {
+ // EMP triggers on damage, but in some cases players are invlunerable (embark, disembark, etc...)
+ if ( entIsPlayer && DamageInfo_GetDamageSourceIdentifier( damageInfo ) in level._empForcedCallbacks )
+ {
+ if ( ShouldPlayEMPEffectEvenWhenDamageIsZero( ent, attacker ) )
+ EMP_DamagedPlayerOrNPC( ent, damageInfo )
+ }
+
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+
+ if ( ( IsAirDrone( ent ) ) && ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) in level._empForcedCallbacks ) )
+ {
+ EMP_DamagedPlayerOrNPC( ent, damageInfo )
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == damagedef_titan_step )
+ HandleFootstepDamage( ent, damageInfo )
+
+ // HACK helps trap/grenade weapons do damage to the correct entities (player who deployed it as well as the team opposite his)
+ if ( IsValid( inflictor ) && "originalOwner" in inflictor.s )
+ {
+ local ogOwner = inflictor.s.originalOwner
+ if ( IsValid( ogOwner ) )
+ {
+ // if the victim is the guy who damaged the trap, and he is not the ogOwner...
+ if ( ent == attacker && ent != ogOwner )
+ {
+ // HACK to do this legit we need DamageInfo_SetAttacker( damageInfo )
+ // victim should take damage from the original owner instead of the satchel attacker so he gets a kill credit
+ ent.TakeDamage( DamageInfo_GetDamage( damageInfo ), ogOwner, inflictor, { weapon = DamageInfo_GetWeapon( damageInfo ), origin = DamageInfo_GetDamagePosition( damageInfo ), force = DamageInfo_GetDamageForce( damageInfo ), scriptType = DamageInfo_GetCustomDamageType( damageInfo ), damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo ) } )
+
+ // now zero out the normal damage and return
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+ }
+ }
+
+ if ( IsValid( inflictor ) )
+ {
+ if ( inflictor.IsProjectile() && entIsPlayer )
+ {
+ if ( inflictor.proj.damageScale != 1.0 )
+ {
+ DamageInfo_ScaleDamage( damageInfo, inflictor.proj.damageScale )
+ }
+
+ // Don't take damage from projectiles created before you where spawned.
+ if ( inflictor.GetProjectileCreationTime() < ent.s.respawnTime && ( Time() - ent.s.respawnTime ) < 2.0 )
+ {
+ DamageInfo_SetDamage( damageInfo, 0 )
+ return
+ }
+ }
+
+ if ( inflictor.e.onlyDamageEntitiesOnce == true || inflictor.e.onlyDamageEntitiesOncePerTick == true )
+ {
+ Assert( !inflictor.e.damagedEntities.contains(ent) )
+ inflictor.e.damagedEntities.append( ent )
+ }
+ }
+
+ // Round damage to nearest full value
+ DamageInfo_SetDamage( damageInfo, floor( DamageInfo_GetDamage( damageInfo ) + 0.5 ) )
+ if ( DamageInfo_GetDamage( damageInfo ) <= 0 )
+ return
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " rounded damage amount:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ HandleLocationBasedDamage( ent, damageInfo )
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after location based damage:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ //PROTO Defensive AI Chip. Ideally less invisible gameplay, but something that can combo with other chips.
+ if ( ent.IsTitan() && entIsNPC )
+ {
+ entity soul = ent.GetTitanSoul()
+ if ( IsValid( soul ) && SoulHasPassive( soul, ePassives.PAS_GUARDIAN_CHIP ) )
+ {
+ DamageInfo_SetDamage( damageInfo, DamageInfo_GetDamage( damageInfo ) * 0.8 )
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( "After guardian chip :", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+ }
+ }
+
+ RunClassDamageCallbacks( ent, damageInfo )
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after class damage callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+
+ // use AddDamageByCallback( "classname", function ) to registed functions
+ if ( IsValid( attacker ) )
+ {
+ if ( attackerIsTitan )
+ {
+ entity soul = attacker.GetTitanSoul()
+ if ( IsValid( soul ) )
+ {
+ float damageAmpScale = 1.0 + StatusEffect_Get( soul, eStatusEffect.titan_damage_amp )
+ if ( damageAmpScale != 1.0 )
+ DamageInfo_ScaleDamage( damageInfo, damageAmpScale )
+ }
+ }
+
+ string attackerClassName = attacker.GetClassName()
+ if ( attackerClassName in svGlobal.damageByCallbacks )
+ {
+ foreach ( callbackFunc in svGlobal.damageByCallbacks[attackerClassName] )
+ {
+ callbackFunc( ent, damageInfo )
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+ }
+ }
+ }
+
+ float damageMultiplier = 1.0 + StatusEffect_Get( ent, eStatusEffect.damage_received_multiplier )
+ if ( damageMultiplier != 1.0 )
+ DamageInfo_ScaleDamage( damageInfo, damageMultiplier )
+
+ // Added via AddEntityCallback_OnDamaged
+ foreach ( callbackFunc in ent.e.entDamageCallbacks )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after AddEntityCallback_OnDamaged callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ // use AddDamageCallbackSourceID( "classname", function ) to registed functions
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ if ( damageSourceId in shGlobal.damageSourceIdCallbacks )
+ {
+ foreach ( callbackFunc in shGlobal.damageSourceIdCallbacks[ damageSourceId ] )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+ }
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after damageSourceID callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+
+ RunClassDamageFinalCallbacks( ent, damageInfo )
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " after class damage final callbacks:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 )
+ return
+
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ DamageInfo_AddDamageFlags( damageInfo, DAMAGEFLAG_NOPAIN )
+
+ float savedDamage = DamageInfo_GetDamage( damageInfo )
+
+ TitanDamage titanDamage
+ if ( entIsPlayer )
+ {
+ PlayerTookDamage( ent, damageInfo, attacker, inflictor, damageSourceId, titanDamage )
+ if ( DamageInfo_GetDamage( damageInfo ) == 0 && entIsTitan )
+ {
+ EarnMeterDamageConversion( damageInfo, attacker, ent, 0, titanDamage )
+ return
+ }
+
+ if ( attackerIsPlayer )
+ PlayerDamageFeedback( ent, damageInfo )
+ savedDamage = DamageInfo_GetDamage( damageInfo )
+
+ if ( !entIsTitan )
+ ent.SetCloakFlicker( 0.5, 0.65 )
+ }
+ else
+ {
+ Assert( entIsNPC )
+ bool clearedDamage
+ if ( ent.ai.buddhaMode )
+ {
+ float currentDamage = DamageInfo_GetDamage( damageInfo )
+ int remainingHealth = ent.GetHealth()
+
+ if ( currentDamage >= remainingHealth - ( DOOMED_MIN_HEALTH + 1 ) )
+ {
+ currentDamage = max( remainingHealth - ( DOOMED_MIN_HEALTH + 1 ), 0 )
+ DamageInfo_SetDamage( damageInfo, currentDamage )
+ clearedDamage = currentDamage == 0
+ }
+ }
+
+ if ( !clearedDamage )
+ {
+ if ( entIsTitan )
+ {
+ Titan_NPCTookDamage( ent, damageInfo, titanDamage )
+ savedDamage = DamageInfo_GetDamage( damageInfo )
+ }
+ else
+ {
+ Generic_NPCTookDamage( ent, damageInfo, titanDamage )
+ }
+ }
+
+ if ( attackerIsPlayer )
+ PlayerDamageFeedback( ent, damageInfo )
+ }
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " After player damage mod:", DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ if ( titanDamage.shieldDamage > 0 )
+ printt( " Shield Damage:", titanDamage.shieldDamage )
+ #endif
+
+ // Added via AddEntityCallback_OnPostDamaged
+ foreach ( callbackFunc in ent.e.entPostDamageCallbacks )
+ {
+ callbackFunc( ent, damageInfo )
+ }
+
+ UpdateLastDamageTime( ent )
+
+ //pain sounds _base_gametype.nut, death sounds in _death_package.nut
+ UpdateDamageState( ent, damageInfo )
+ HandlePainSounds( ent, damageInfo )
+
+ UpdateAttackerInfo( ent, attacker, savedDamage )
+
+ if ( !(DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS) )
+ {
+ if ( attackerIsPlayer )
+ {
+ if ( entIsTitan )
+ {
+ PlayerDealtTitanDamage( attacker, ent, savedDamage, damageInfo )
+
+ entity entSoul = ent.GetTitanSoul()
+ if ( attacker.p.currentTargetPlayerOrSoul_Ent != entSoul )
+ {
+ attacker.p.currentTargetPlayerOrSoul_Ent = ent.GetTitanSoul()
+
+ TitanVO_TellPlayersThatAreAlsoFightingThisTarget( attacker, entSoul )
+ }
+ attacker.p.currentTargetPlayerOrSoul_LastHitTime = Time()
+ }
+ else if ( entIsPlayer )
+ {
+ attacker.p.currentTargetPlayerOrSoul_Ent = ent
+ attacker.p.currentTargetPlayerOrSoul_LastHitTime = Time()
+ }
+ }
+ }
+
+ EarnMeterDamageConversion( damageInfo, attacker, ent, savedDamage, titanDamage )
+
+ if ( entIsTitan )
+ {
+ TitanDamageFlinch( ent, damageInfo )
+
+ if ( TitanDamageRewardsTitanCoreTime() && entIsPlayer && attacker.GetTeam() != ent.GetTeam() )
+ AddCreditToTitanCoreBuilderForTitanDamageReceived( ent, savedDamage )
+ }
+
+ if ( entIsPlayer && !entIsTitan )
+ PilotDamageFlinch( ent, damageInfo )
+
+ #if VERBOSE_DAMAGE_PRINTOUTS
+ printt( " final damage done:", DamageInfo_GetDamage( damageInfo ) )
+ printt( " health: " + ent.GetHealth() )
+ #endif
+
+ RunClassPostDamageCallbacks( ent, damageInfo )
+
+ #if SERVER && MP
+ Stats_OnPlayerDidDamage( ent, damageInfo )
+ PIN_DamageDone( attacker, ent, DamageInfo_GetDamage( damageInfo ) )
+ #endif
+
+ attacker.Signal( "DamagedPlayerOrNPC" )
+}
+
+void function EarnMeterDamageConversion( var damageInfo, entity attacker, entity ent, float savedDamage, TitanDamage titanDamage )
+{
+ if ( !(DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS) )
+ {
+ bool shouldGiveTimerCredit = file.ShouldGiveTimerCreditGameModeRules( attacker, ent, damageInfo )
+ if ( attacker.IsPlayer() )
+ {
+ float titanSpawnDelay = GetTitanBuildTime( attacker )
+ float timerCredit = 0.0
+
+ if ( shouldGiveTimerCredit )
+ {
+ file.earnMeterOnDamageGameModeRulesCallback( attacker, ent, titanDamage, savedDamage )
+
+ // Timer Credit seems unused. Need to investigate if all DecrementBuildTimer functions are worthless.
+ if ( titanSpawnDelay && IsAlive( ent ) && GetCurrentPlaylistVarInt( "titan_build_credit_enabled", 1 ) == 1 )
+ {
+ if ( ent.IsTitan() )
+ {
+ timerCredit = GetCurrentPlaylistVarFloat( "titan_kill_credit", 0.5 )
+ if ( PlayerHasServerFlag( attacker, SFLAG_HUNTER_TITAN ) )
+ timerCredit *= 2.0
+ }
+ else
+ {
+ if ( ent.IsPlayer() )
+ {
+ timerCredit = GetCurrentPlaylistVarFloat( "player_kill_credit", 0.5 )
+ if ( PlayerHasServerFlag( attacker, SFLAG_HUNTER_PILOT ) )
+ timerCredit *= 2.5
+ }
+ else
+ {
+ if ( IsGrunt( ent ) )
+ {
+ timerCredit = GetCurrentPlaylistVarFloat( "ai_kill_credit", 0.5 )
+ if ( PlayerHasServerFlag( attacker, SFLAG_HUNTER_GRUNT ) )
+ timerCredit *= 2.5
+ }
+ else
+ if ( IsSpectre( ent ) )
+ {
+ timerCredit = GetCurrentPlaylistVarFloat( "spectre_kill_credit", 0.5 )
+ if ( PlayerHasServerFlag( attacker, SFLAG_HUNTER_SPECTRE ) )
+ timerCredit *= 2.5
+ }
+ else
+ if ( IsTurret( ent ) )
+ {
+
+ timerCredit = GetCurrentPlaylistVarFloat( "megaturret_kill_credit", 0.5 )
+ //No 2x burn card for shooting mega turret
+ }
+ #if HAS_EVAC
+ else
+ if ( IsEvacDropship( ent ) )
+ {
+ timerCredit = GetCurrentPlaylistVarFloat( "evac_dropship_kill_credit", 0.5 )
+ }
+ #endif
+ }
+ }
+
+ float dealtDamage = min( ent.GetHealth(), (savedDamage + titanDamage.shieldDamage) )
+ timerCredit = timerCredit * (dealtDamage / ent.GetMaxHealth().tofloat())
+ }
+
+ if ( IsPilot( attacker ) && PlayerHasPassive( attacker, ePassives.PAS_AT_HUNTER ) )
+ timerCredit *= 1.1
+
+ if ( timerCredit && (!TitanDamageRewardsTitanCoreTime() || !attacker.IsTitan() ) )
+ DecrementBuildTimer( attacker, timerCredit )
+ }
+ }
+
+ if ( shouldGiveTimerCredit //Primary Check
+ && TitanDamageRewardsTitanCoreTime() //Playlist var check
+ && ent.IsTitan()
+ && attacker.IsTitan()
+ && attacker.GetTeam() != ent.GetTeam()
+ && !attacker.ContextAction_IsMeleeExecution() // Some melee executions deal A LOT of damage
+ )
+ AddCreditToTitanCoreBuilderForTitanDamageInflicted( attacker, savedDamage + titanDamage.shieldDamage )
+ }
+}
+
+
+bool function ShouldUseNonTitanHeavyArmorDamageScale( entity victim )
+{
+ if ( (victim.GetArmorType() != ARMOR_TYPE_HEAVY) )
+ return false
+
+ if ( victim.IsTitan() )
+ return false
+
+ if ( IsDropship( victim ) )
+ return false
+
+ return true
+}
+
+void function GameModeRulesEarnMeterOnDamage_Default( entity attacker, entity victim, TitanDamage titanDamage, float savedDamage )
+{
+ #if MP
+ if ( victim.IsTitan() && !attacker.IsTitan() && !IsValid( attacker.GetPetTitan() ) )
+ {
+ float damage = min( victim.GetHealth(), (savedDamage + titanDamage.shieldDamage) )
+ float meterAmount = damage * file.titanMeterGainScale
+ if ( PlayerHasPassive( attacker, ePassives.PAS_AT_HUNTER ) )
+ meterAmount *= 1.1
+ PlayerEarnMeter_AddOwnedFrac( attacker, meterAmount )
+
+ AccumulatedDamageData damageData = file.playerAccumulatedDamageData[attacker]
+ damageData.lastDamageTime = Time()
+ damageData.accumulatedDamage += meterAmount
+
+ if ( damageData.accumulatedDamage >= 0.01 )
+ {
+ attacker.Signal( "UpdateAccumulatedDamageAfterDelay" )
+ AddPlayerScore( attacker, "DamageTitan", null, "", int( damageData.accumulatedDamage * 100 ) )
+ damageData.accumulatedDamage = 0
+ }
+ else
+ {
+ thread UpdateAccumulatedDamageAfterDelay( attacker )
+ }
+ }
+ #endif
+}
+
+void function SetTitanMeterGainScale( float scalar )
+{
+ file.titanMeterGainScale = scalar
+}
+
+#if MP
+void function UpdateAccumulatedDamageAfterDelay( entity attacker )
+{
+ attacker.EndSignal( "OnDeath" )
+ attacker.Signal( "UpdateAccumulatedDamageAfterDelay" )
+ attacker.EndSignal( "UpdateAccumulatedDamageAfterDelay" )
+
+ wait 0.25
+
+ AccumulatedDamageData damageData = file.playerAccumulatedDamageData[attacker]
+
+ if ( damageData.accumulatedDamage == 0 )
+ return
+
+ AddPlayerScore( attacker, "DamageTitan", null, "", int( max( damageData.accumulatedDamage * 100, 1 ) ) )
+ damageData.accumulatedDamage = 0
+}
+#endif
+
+void function SetGameModeRulesEarnMeterOnDamage( void functionref( entity, entity, TitanDamage, float ) rules )
+{
+ file.earnMeterOnDamageGameModeRulesCallback = rules
+}
+
+bool function ShouldGiveTimerCredit_Default( entity player, entity victim, var damageInfo )
+{
+ if ( player == victim )
+ return false
+
+ if ( player.IsTitan() && !IsCoreAvailable( player ) )
+ return false
+
+ if ( GAMETYPE == FREE_AGENCY && !player.IsTitan() )
+ return false
+
+ int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+ switch ( damageSourceID )
+ {
+ case eDamageSourceId.mp_titancore_flame_wave:
+ case eDamageSourceId.mp_titancore_flame_wave_secondary:
+ case eDamageSourceId.mp_titancore_salvo_core:
+ case damagedef_titan_fall:
+ case damagedef_nuclear_core:
+ return false
+ }
+
+ return true
+}
+
+bool function GameModeRulesShouldGiveTimerCredit( entity player, entity victim, var damageInfo )
+{
+ return file.ShouldGiveTimerCreditGameModeRules( player, victim, damageInfo )
+}
+
+void function SetGameModeRulesShouldGiveTimerCredit( bool functionref( entity, entity, var ) rules )
+{
+ file.ShouldGiveTimerCreditGameModeRules = rules
+}
+
+function TitanDamageFlinch( entity ent, damageInfo )
+{
+ if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ return
+
+ if ( TitanStagger( ent, damageInfo ) )
+ return
+
+ if ( DamageInfo_GetDamage( damageInfo ) >= TITAN_ADDITIVE_FLINCH_DAMAGE_THRESHOLD )
+ AddFlinch( ent, damageInfo )
+}
+
+function PilotDamageFlinch( entity ent, damageInfo )
+{
+ //if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS )
+ // return
+
+ float damage = DamageInfo_GetDamage( damageInfo )
+ if ( damage >= 5 )
+ AddFlinch( ent, damageInfo )
+}
+
+vector function GetDamageOrigin( damageInfo, entity victim = null )
+{
+ int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo )
+
+ entity inflictor = DamageInfo_GetInflictor( damageInfo )
+
+ if ( inflictor == svGlobal.worldspawn )
+ return DamageInfo_GetDamagePosition( damageInfo )
+
+ vector damageOrigin = IsValid( inflictor ) ? inflictor.GetOrigin() : DamageInfo_GetDamagePosition( damageInfo )
+
+ switch ( damageSourceId )
+ {
+ case eDamageSourceId.mp_weapon_satchel:
+ case eDamageSourceId.mp_weapon_proximity_mine:
+ case eDamageSourceId.mp_titanweapon_arc_pylon:
+ break
+
+ case damagedef_nuclear_core:
+ case eDamageSourceId.mp_titanability_smoke:
+ //if ( IsValid( victim ) && victim.IsPlayer() && IsValid( victim.GetTitanSoulBeingRodeoed() ) )
+ {
+ damageOrigin += (RandomVecInDome( Vector( 0, 0, -1 ) ) * 300.0)
+ damageOrigin += Vector( 0, 0, 128 )
+ }
+ break
+
+ case eDamageSourceId.switchback_trap:
+ if ( IsValid( victim ) && victim.IsPlayer() )
+ damageOrigin = victim.EyePosition() + (RandomVecInDome( Vector( 0, 0, -1 ) ) * 300.0)
+ break
+
+ default:
+ if ( DamageInfo_GetAttacker( damageInfo ) )
+ {
+ inflictor = DamageInfo_GetAttacker( damageInfo )
+ damageOrigin = inflictor.GetWorldSpaceCenter()
+ }
+ break
+ }
+
+ return damageOrigin
+}
+
+/*
+function TrackDPS( ent )
+{
+ ent.s.dpsTracking <- {}
+ ent.s.dpsTracking.damage <- 0
+
+ local startTime = Time()
+
+ ent.WaitSignal( "Doomed" )
+
+ local duration = Time() - startTime
+
+ printt( "DPS:", ent.s.dpsTracking.damage / duration, duration )
+
+ delete ent.s.dpsTracking
+}
+
+function UpdateDPS( ent, damageInfo )
+{
+ if ( GetDoomedState( ent ) )
+ return
+
+ if ( !( "dpsTracking" in ent.s ) )
+ thread TrackDPS( ent )
+
+ ent.s.dpsTracking.damage += DamageInfo_GetDamage( damageInfo )
+}
+*/
+
+
+void function PlayerTookDamage( entity player, var damageInfo, entity attacker, entity inflictor, int damageSourceId, TitanDamage titanDamage )
+{
+ int hitBox = DamageInfo_GetHitBox( damageInfo )
+
+ bool critHit = false
+
+ if ( CritWeaponInDamageInfo( damageInfo ) )
+ critHit = IsCriticalHit( attacker, player, hitBox, DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageType( damageInfo ) )
+
+ if ( critHit )
+ DamageInfo_AddCustomDamageType( damageInfo, DF_CRITICAL )
+
+ array<string> weaponMods = GetWeaponModsFromDamageInfo( damageInfo )
+
+ local eModSourceID = null
+ foreach ( mod in weaponMods )
+ {
+ local modSourceID = GetModSourceID( mod )
+ if ( modSourceID != null && modSourceID in modNameStrings )
+ eModSourceID = modSourceID
+ }
+
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == eDamageSourceId.fall )
+ DamageInfo_SetForceKill( damageInfo, true )
+
+ bool isTitan = player.IsTitan()
+
+ if ( isTitan )
+ Titan_PlayerTookDamage( player, damageInfo, attacker, critHit, titanDamage )
+ else
+ Wallrun_PlayerTookDamage( player, damageInfo, attacker )
+
+ float damageAmount = DamageInfo_GetDamage( damageInfo )
+ bool isKillShot = (damageAmount >= player.GetHealth())
+ int damageType = DamageInfo_GetCustomDamageType( damageInfo )
+ if ( isKillShot )
+ damageType = (damageType | DF_KILLSHOT)
+
+ if ( isTitan && (DamageInfo_GetDamage( damageInfo ) == 0) )
+ {
+ if ( titanDamage.doomedNow ) // to make kill card come up for you even if you have auto-eject. In Titan_PlayerTookDamage we set damage to 0 if you have Auto-Eject and are doomed
+ TellClientPlayerTookDamage( player, damageInfo, attacker, eModSourceID, damageType, damageSourceId, titanDamage )
+ }
+
+ vector attackerOrigin = Vector( 0, 0, 0 )
+ if ( IsValid( attacker ) )
+ attackerOrigin = attacker.GetOrigin()
+
+ if ( IsAlive( player ) )
+ {
+ float storeTime = MAX_DAMAGE_HISTORY_TIME
+ entity storeEnt
+ if ( isTitan )
+ {
+ storeEnt = player.GetTitanSoul()
+ }
+ else
+ {
+ storeEnt = player
+ if ( IsSingleplayer() )
+ storeTime = 30.0
+ }
+
+ StoreDamageHistoryAndUpdate( storeEnt, storeTime, DamageInfo_GetDamage( damageInfo ), attackerOrigin, damageType, damageSourceId, attacker, weaponMods )
+ }
+
+ if ( !(DamageInfo_GetCustomDamageType( damageInfo ) & DF_DOOMED_HEALTH_LOSS) )
+ TellClientPlayerTookDamage( player, damageInfo, attacker, eModSourceID, damageType, damageSourceId, titanDamage )
+}
+
+function TellClientPlayerTookDamage( entity player, damageInfo, entity attacker, eModSourceID, int damageType, int damageSourceId, TitanDamage titanDamage )
+{
+ if ( !player.hasConnected )
+ return
+
+ local attackerEHandle = IsValid( attacker ) ? attacker.GetEncodedEHandle() : null
+ local weaponEHandle = IsValid( DamageInfo_GetWeapon( damageInfo ) ) ? DamageInfo_GetWeapon( damageInfo ).GetEncodedEHandle() : null
+ local damageOrigin = GetDamageOrigin( damageInfo, player )
+
+ if ( player.IsTitan() )
+ Remote_CallFunction_Replay( player, "ServerCallback_TitanTookDamage", DamageInfo_GetDamage( damageInfo ), damageOrigin.x, damageOrigin.y, damageOrigin.z, damageType, damageSourceId, attackerEHandle, eModSourceID, titanDamage.doomedNow, titanDamage.doomedDamage )
+ else
+ Remote_CallFunction_Replay( player, "ServerCallback_PilotTookDamage", DamageInfo_GetDamage( damageInfo ), damageOrigin.x, damageOrigin.y, damageOrigin.z, damageType, damageSourceId, attackerEHandle, eModSourceID )
+}
+
+// This only handles damage events. Whizbys can still cause snipercam to trigger without passing through this check.
+function CodeCallBack_ShouldTriggerSniperCam( damageInfo )
+{
+ switch ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) )
+ {
+ case damagedef_titan_step:
+ case eDamageSourceId.super_electric_smoke_screen:
+ return false
+ }
+
+ return true
+}
+
+bool function CodeCallback_ForceAIMissPlayer( entity npc, entity player )
+{
+ return SPMP_Callback_ForceAIMissPlayer( npc, player )
+}
+
+bool function CodeCallback_OnTouchHealthKit( entity player, entity ent )
+{
+ string entityClassName = ent.GetClassName()
+
+ Assert( entityClassName in svGlobal.onTouchHealthKitCallbacks )
+
+ array<bool functionref( entity player, entity healthpack )> callbackFuncs = svGlobal.onTouchHealthKitCallbacks[ entityClassName ]
+ foreach ( callbackFunc in callbackFuncs )
+ {
+ bool result = callbackFunc( player, ent )
+ if ( result )
+ return result
+ }
+
+ return false
+}
+
+bool function ShouldPlayEMPEffectEvenWhenDamageIsZero( entity ent, entity attacker )
+{
+ if ( ent.IsTitan() && IsTitanWithinBubbleShield( ent ) )
+ return false
+
+ if ( !IsValid( attacker ) )
+ return true
+
+ if ( attacker.GetTeam() != ent.GetTeam() )
+ return true
+
+ return false
+}
+
+void function CodeCallback_OnPlayerGrappled( entity player, entity victim )
+{
+ if ( victim.GetTeam() != player.GetTeam() )
+ {
+ if ( victim.p.lastGrappledTime + TITAN_GRAPPLE_DEBOUNCE_TIME < Time() )
+ {
+ if ( player.IsTitan() )
+ {
+ victim.TakeDamage( TITAN_GRAPPLE_DAMAGE, player, player, { origin = victim.EyePosition(), scriptType = DF_GIB, damageSourceId = eDamageSourceId.titan_grapple } )
+
+ if ( victim.IsTitan() )
+ {
+ entity soul = victim.GetTitanSoul()
+ if ( soul == null )
+ soul = victim
+
+ float fadeTime = 0.5
+ StatusEffect_AddTimed( soul, eStatusEffect.dodge_speed_slow, 0.75, 0.9 + fadeTime, fadeTime )
+ StatusEffect_AddTimed( soul, eStatusEffect.move_slow, 0.75, 0.9 + fadeTime, fadeTime )
+ }
+ }
+
+ if ( victim.IsPlayer() )
+ {
+ if ( player.IsTitan() )
+ MessageToPlayer( victim, eEventNotifications.Grapple_WasGrappled_ByTitan )
+ else
+ MessageToPlayer( victim, eEventNotifications.Grapple_WasGrappled_ByPilot )
+ }
+ }
+
+ victim.p.lastGrappledTime = Time()
+ }
+}
+
+void function CodeCallback_OnProjectileGrappled( entity player, entity projectile )
+{
+
+}
+
+void function DamageInfo_ScaleDamage( var damageInfo, float scalar )
+{
+ DamageInfo_SetDamage( damageInfo, DamageInfo_GetDamage( damageInfo ) * scalar )
+}
+
+string function CodeCallback_CheckPassThroughAddsMods( entity player, entity hitEnt, string currWeaponName )
+{
+ if ( !IsValid( player ) )
+ return ""
+
+ if ( StatusEffect_Get( hitEnt, eStatusEffect.pass_through_amps_weapon ) > 0 )
+ {
+ array<string> mods = GetWeaponBurnMods( currWeaponName )
+ if ( mods.len() > 0 )
+ return mods[0]
+ }
+ return ""
+}
+
+void function Generic_NPCTookDamage( entity npc, damageInfo, TitanDamage titanDamage )
+{
+ Assert( !npc.IsTitan() )
+ Assert( DamageInfo_GetDamage( damageInfo ) > 0 )
+ Assert( IsAlive( npc ) )
+
+ bool critHit = false
+ if ( CritWeaponInDamageInfo( damageInfo ) )
+ critHit = IsCriticalHit( DamageInfo_GetAttacker( damageInfo ), npc, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageType( damageInfo ) )
+
+ if ( critHit )
+ DamageInfo_AddCustomDamageType( damageInfo, DF_CRITICAL )
+
+ titanDamage.shieldDamage = NPCShieldHealthUpdate( npc, damageInfo, critHit )
+}
+
+
+int function NPCShieldHealthUpdate( entity npc, damageInfo, bool critHit )
+{
+ if ( npc.GetShieldHealth() <= 0 )
+ return 0
+
+ if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == damagedef_suicide )
+ return 0
+
+ if ( DamageInfo_GetForceKill( damageInfo ) )
+ {
+ npc.SetShieldHealth( 0 )
+ return 0
+ }
+ else if ( DamageInfo_GetCustomDamageType( damageInfo ) & DF_BYPASS_SHIELD )
+ {
+ return 0
+ }
+
+ DamageInfo_AddCustomDamageType( damageInfo, DF_SHIELD_DAMAGE )
+ return int( ShieldModifyDamage( npc, damageInfo ) )
+}
+
+#if MP
+// taken from sp/sh_sp_dialogue.gnut, with some sp-exclusive stuff removed
+
+// called by code when an animation does { event AE_SV_VSCRIPT_CALLBACK FrameNumber "some string" }
+// and by a script function OnFootstep, apparently.
+void function CodeCallback_OnServerAnimEvent( entity ent, string eventName )
+{
+ PerfStart( PerfIndexServer.CB_OnServerAnimEvent )
+ if ( HasAnimEvent( ent, eventName ) )
+ thread RunAnimEventCallbacks( ent, eventName )
+
+ if ( eventName in svGlobal.globalAnimEventCallbacks )
+ {
+ thread svGlobal.globalAnimEventCallbacks[ eventName ]( ent )
+ PerfEnd( PerfIndexServer.CB_OnServerAnimEvent )
+ return
+ }
+
+
+ // couldn't find this eventName on the ent or the global anim events,
+ // so try breaking it down. If we didn't find it, it means
+ // script needs to handle the event, even if it is just to
+ // do nothing with it
+
+ array<string> tokens = split( eventName, ":" )
+ string tokenName = tokens[0]
+
+ switch ( tokenName )
+ {
+ case "worldsound":
+ GlobalAnimEventWithStringParameter_WorldSound( ent, tokens[1] )
+ break
+
+ case "signal":
+ SendSignalFromTokens( ent, tokens )
+ break
+
+ case "flagset":
+ GlobalAnimEventWithStringParameter_FlagSet( ent, tokens[1] )
+ break
+
+ //case "dialogue":
+ // // Make sure that animation triggered dialogue uses the correct priority and skips the queue
+ // string name = tokens[1]
+ // Assert( file.registeredDialogIDs.find( name ) >= 0, "Dialogue line " + name + " is not registered" )
+ // int aliasID = file.registeredDialogIDs.find( name )
+ // DialogueData data = file.registeredDialog[ aliasID ]
+ // Assert( data.priority == PRIORITY_NO_QUEUE, "Dialogue " + name + " triggered via qc must use PRIORITY_NO_QUEUE" )
+ // thread PlayDialogue( name, ent )
+ // break
+
+ case "fireViperSalvo":
+ int value = tokens[1].tointeger()
+ ent.Signal( "fireSalvo", { num = value } )
+ break
+
+ //case "conversation":
+ // thread PlayerConversation( tokens[1], GetPlayerArray()[0], ent )
+ // break
+ }
+
+ PerfEnd( PerfIndexServer.CB_OnServerAnimEvent )
+}
+#endif // #if MP \ No newline at end of file