og-northstar-mods/Northstar.CustomServers/mod/scripts/vscripts/mp/_codecallbacks.gnut
Allusive 672a3f5fc9
Adds OnWeaponAttackCallbacks (#983)
* remove thread that breaks boosts

* add OnWeaponAttack callbacks

* add to file struct

---------

Co-authored-by: cat_or_not <41955154+catornot@users.noreply.github.com>
2026-03-21 23:15:03 -04:00

1083 lines
No EOL
33 KiB
Text

untyped
global function CodeCallback_Init
global function CodeCallback_DamagePlayerOrNPC
global function GameModeRulesShouldGiveTimerCredit
global function SetGameModeRulesShouldGiveTimerCredit
global function SetGameModeRulesEarnMeterOnDamage
global function GameModeRulesRegisterTimerCreditExceptions
global function GameModeRulesRegisterTimerCreditException
global function GameModeRulesGetTimerCreditExceptions
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
global function CodeCallback_WeaponDropped
global function AddCallback_OnWeaponDropped
#endif
global function CodeCallback_OnWeaponAttack
global function AddCallback_OnWeaponAttack
global function RemoveCallback_OnWeaponAttack
struct AccumulatedDamageData
{
float accumulatedDamage
float lastDamageTime
}
struct
{
float titanMeterGainScale = 0.0001
bool functionref( entity, entity, var ) ShouldGiveTimerCreditGameModeRules
void functionref( entity, entity, TitanDamage, float ) earnMeterOnDamageGameModeRulesCallback
array<int> gameModeRulesTimerCreditExceptions = [
eDamageSourceId.mp_titancore_flame_wave,
eDamageSourceId.mp_titancore_flame_wave_secondary,
eDamageSourceId.mp_titancore_salvo_core,
damagedef_titan_fall,
damagedef_nuclear_core
]
table<entity, AccumulatedDamageData> playerAccumulatedDamageData
array< void functionref( entity ) > weaponDroppedCallbacks
array< void functionref( entity, entity, string, int ) > weaponAttackCallbacks
} 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
// Added via AddEntityCallback_OnFinalDamaged
foreach ( callbackFunc in ent.e.entFinalDamageCallbacks )
{
callbackFunc( ent, damageInfo )
}
#if VERBOSE_DAMAGE_PRINTOUTS
printt( " afterAddEntityCallback_OnFinalDamaged 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 )
if ( file.gameModeRulesTimerCreditExceptions.contains( damageSourceID ) )
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
}
void function GameModeRulesRegisterTimerCreditExceptions( array<int> ids )
{
foreach( id in ids )
GameModeRulesRegisterTimerCreditException( id )
}
void function GameModeRulesRegisterTimerCreditException( int id )
{
if ( !file.gameModeRulesTimerCreditExceptions.contains( id ) )
file.gameModeRulesTimerCreditExceptions.append( id )
}
array<int> function GameModeRulesGetTimerCreditExceptions()
{
return file.gameModeRulesTimerCreditExceptions
}
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 )
}
void function AddCallback_OnWeaponDropped( void functionref( entity ) callback )
{
file.weaponDroppedCallbacks.append( callback )
}
void function CodeCallback_WeaponDropped( entity weapon )
{
// shamelessly taken form SP
if ( !IsValid( weapon ) )
return
// Might look a bit hacky to put it here, but thats how SP does it
if ( !Flag( "WeaponDropsAllowed" ) )
{
weapon.Destroy()
return
}
foreach( callback in file.weaponDroppedCallbacks )
callback( weapon )
}
#endif // #if MP
void function AddCallback_OnWeaponAttack( void functionref( entity player, entity weapon, string weaponName, int attackIndex ) callbackFunc )
{
Assert( !file.weaponAttackCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnWeaponAttackCallback" )
file.weaponAttackCallbacks.append( callbackFunc )
}
void function RemoveCallback_OnWeaponAttack( void functionref( entity player, entity weapon, string weaponName, int attackIndex ) callbackFunc )
{
Assert( file.weaponAttackCallbacks.contains( callbackFunc ), "Tried to remove " + string( callbackFunc ) + " with RemoveCallback_OnWeaponAttackCallback but it was not found" )
file.weaponAttackCallbacks.fastremovebyvalue( callbackFunc )
}
void function CodeCallback_OnWeaponAttack(entity player, entity weapon, string weaponName,int shotsFired)
{
foreach ( callbackFunc in file.weaponAttackCallbacks )
{
callbackFunc( player, weapon, weaponName, shotsFired )
}
}