untyped
global function ClDamageIndicator_Init
global function Create_DamageIndicatorHUD
global function DamageIndicators
global function GrenadeArrowThink
global function RumbleForTitanDamage
global function ServerCallback_TitanTookDamage
global function ServerCallback_PilotTookDamage
//global function ClientCodeCallback_OnMissileCreation
global function ClientCodeCallback_CreateGrenadeIndicator
global function DamageIndicatorRui
global function ShowGrenadeArrow
global function SCB_AddGrenadeIndicatorForEntity
const DAMAGEARROW_FADEANIM = "damage_fade"
const DAMAGEARROW_DURATION = 2.5
const DAMAGEARROW_SMALL = 0
const DAMAGEARROW_MEDIUM = 1
const DAMAGEARROW_LARGE = 2
const float DAMAGEHUD_GRENADE_DEBOUNCE_TIME = 0.4
const float DAMAGEHUD_GRENADE_DEBOUNCE_TIME_LOWSPEED = 0.15
const float DAMAGEHUD_GRENADE_DEBOUNCE_TIME_LOWSPEED_VELOCITYCUTOFF = 500.0
struct {
array
damageArrows
int currentDamageArrow = 0
int numDamageArrows = 16
float damageArrowFadeDuration = 1.0
float damageArrowTime = 0.0
vector damageArrowAngles = < 0.0, 0.0, 0.0 >
vector damageArrowPointCenter = < 0.0, 0.0, 0.0 >
table whizByFX = {
small = null,
large = null,
titan = null,
}
array arrowIncomingAnims = [
{ anim = "damage_incoming_small", duration = 1.5 },
{ anim = "damage_incoming", duration = 1.75 },
{ anim = "damage_incoming_large", duration = 2.00 },
]
int damageIndicatorCount = 0
} file
function ClDamageIndicator_Init()
{
RegisterSignal( "CriticalHitReceived" )
AddCreateCallback( "titan_cockpit", DamageArrow_CockpitInit )
PrecacheParticleSystem( $"P_wpn_grenade_frag_icon" )
PrecacheParticleSystem( $"P_wpn_grenade_frag_blue_icon" )
PrecacheParticleSystem( $"P_wpn_grenade_smoke_icon" )
if ( !IsLobby() )
AddCallback_EntitiesDidLoad( InitDamageArrows )
}
function ServerCallback_TitanTookDamage( damage, x, y, z, damageType, damageSourceId, attackerEHandle, eModId, doomedNow, doomedDamage )
{
expect float( damage )
expect int( damageType )
expect int( damageSourceId )
expect bool( doomedNow )
expect int( doomedDamage )
if ( IsWatchingThirdPersonKillReplay() )
return
if ( DebugVictimClientDamageFeedbackIsEnabled() && (damage > 0.0) )
{
entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null
entity localViewPlayer = GetLocalViewPlayer()
bool isHeadShot = (damageType & DF_HEADSHOT) ? true : false
bool isKillShot = (damageType & DF_KILLSHOT) ? true : false
bool isCritical = (damageType & DF_CRITICAL) ? true : false
bool isDoomProtected = (damageType & DF_DOOM_PROTECTED) ? true : false
bool isDoomFatality = (damageType & DF_DOOM_FATALITY) ? true : false
local weaponMods = []
if ( eModId != null && eModId in modNameStrings )
weaponMods.append( modNameStrings[eModId] )
string modDesc = ((eModId != null && eModId in modNameStrings) ? (expect string( modNameStrings[eModId] )) : "")
DebugTookDamagePrint( localViewPlayer, attacker, damage, damageSourceId, modDesc, isHeadShot, isKillShot, isCritical, doomedNow, doomedDamage, isDoomProtected, isDoomFatality )
}
// It appears to be faster here to create a new thread so other functions called can wait until the frame ends before running.
thread TitanTookDamageThread( damage, x, y, z, damageType, damageSourceId, attackerEHandle, eModId, doomedNow, doomedDamage )
vector damageOrigin = < x, y, z >
entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null
if ( damageSourceId in clGlobal.onLocalPlayerTookDamageCallback )
{
foreach ( callback in clGlobal.onLocalPlayerTookDamageCallback[ damageSourceId ] )
callback( damage, damageOrigin, damageType, damageSourceId, attacker )
}
}
function TitanTookDamageThread( float damage, x, y, z, int damageType, int damageSourceId, attackerEHandle, eModId, bool doomedNow, int doomedDamage )
{
WaitEndFrame()
entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null
entity localViewPlayer = GetLocalViewPlayer()
entity cockpit = localViewPlayer.GetCockpit()
if ( cockpit && IsTitanCockpitModelName( cockpit.GetModelName() ) )
TitanCockpit_DamageFeedback( localViewPlayer, cockpit, damage, damageType, < x, y, z >, damageSourceId, doomedNow, doomedDamage )
if ( damage >= DAMAGE_BREAK_MELEE_ASSIST )
localViewPlayer.Lunge_ClearTarget()
if ( damageSourceId != eDamageSourceId.bubble_shield ) //Don't play Betty OS dialogue if we took damage by bubble shield. We don't have appropriate dialogue for it.
Tracker_PlayerAttackedByTarget( localViewPlayer, attacker )
array weaponMods
if ( eModId != null && eModId in modNameStrings )
weaponMods.append( expect string( modNameStrings[ eModId ] ) )
if ( (damage > 0.0) || doomedDamage )
{
vector damageOrigin = < x, y, z >
DamageHistoryStruct damageHistory = StoreDamageHistoryAndUpdate( localViewPlayer, MAX_DAMAGE_HISTORY_TIME, damage, damageOrigin, damageType, damageSourceId, attacker, weaponMods )
DamageIndicators( damageHistory, true )
}
entity soul = localViewPlayer.GetTitanSoul()
if ( PlayerHasPassive( localViewPlayer, ePassives.PAS_AUTO_EJECT ) ) //TODO: Handle nuclear eject if we ever allow nuclear + auto eject combo again
{
if ( ShouldPlayAutoEjectAnim( localViewPlayer, soul, doomedNow ) )
thread PlayerEjects( localViewPlayer, cockpit )
}
if ( damageType & DF_CRITICAL )
{
localViewPlayer.Signal( "CriticalHitReceived" )
EmitSoundOnEntity( localViewPlayer, "titan_damage_crit_3p_vs_1p" )
}
}
bool function ShouldPlayAutoEjectAnim( entity player, entity titanSoul, bool doomedNow )
{
if ( !titanSoul.IsDoomed() )
return false
if ( player.ContextAction_IsActive() && !player.ContextAction_IsBusy() ) //Some other context action, e.g. melee instead of eject. Then again
return false
return true
}
string function DevBuildAttackerDesc( entity localViewPlayer, entity ent )
{
if ( ent == null )
return ""
if ( localViewPlayer == ent )
return ("")
if ( ent.IsPlayer() )
return ("'" + ent.GetPlayerName() + "' " + ent.GetPlayerSettings())
entity bossPlayer = ent.GetBossPlayer()
string ownerString = ((bossPlayer != null) ? (bossPlayer.GetPlayerName() + "'s ") : "")
var sigName = ent.GetSignifierName()
string debugName = (sigName != null) ? expect string( sigName ) : ent.GetClassName()
return (ownerString + debugName)
}
void function DebugTookDamagePrint( entity ornull localViewPlayer, entity attacker, float damage, int damageSourceId, string modDesc, bool isHeadShot, bool isKillShot, bool isCritical, bool isDoomShot, int doomShotDamage, bool isDoomProtected, bool isDoomFatality )
{
Assert( localViewPlayer )
string attackerDesc = DevBuildAttackerDesc( expect entity( localViewPlayer ), attacker )
string timePrint = format( "%d:%.2f", FrameCount(), PlatformTime() )
printt(
"{"+timePrint+"} TOOK DAMAGE: " + damage +
(isHeadShot ? " (headshot)" : "") +
(isCritical ? " (critical)" : "") +
(isKillShot ? " (KILLED)" : "") +
(isDoomShot ? " (DOOMED dmg:" + doomShotDamage + ")" : "") +
(isDoomProtected ? " (DOOM PROTECTION)" : "") +
(isDoomFatality ? " (DOOM FATALITY)" : "") +
" " + attackerDesc +
" w/ " + GetObitFromDamageSourceID( damageSourceId ) + modDesc
)
}
void function PlayVictimHeadshotConfirmation( bool isKillShot )
{
entity localViewPlayer = GetLocalViewPlayer()
if ( localViewPlayer == null )
return
if ( isKillShot )
EmitSoundOnEntity( localViewPlayer, "Player.Hitbeep_headshot.Kill.Human_3P_vs_1P" )
else
EmitSoundOnEntity( localViewPlayer, "Player.Hitbeep_headshot.Human_3P_vs_1P" )
}
void function RumbleForPilotDamage( float damageAmount )
{
Rumble_Play( "rumble_pilot_hurt", {} )
}
void function RumbleForTitanDamage( float damageAmount )
{
string rumbleName;
if ( damageAmount < 500 )
rumbleName = "titan_damaged_small"
else if ( damageAmount < 1000 )
rumbleName = "titan_damaged_med"
else
rumbleName = "titan_damaged_big"
Rumble_Play( rumbleName, {} )
}
function ServerCallback_PilotTookDamage( damage, x, y, z, damageType, damageSourceId, attackerEHandle, eModId )
{
expect float( damage )
expect int( damageType )
expect int( damageSourceId )
if ( IsWatchingThirdPersonKillReplay() )
return
entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null
entity localViewPlayer = GetLocalViewPlayer()
vector damageOrigin = < x, y, z >
bool isHeadShot = (damageType & DF_HEADSHOT) ? true : false
bool isKillShot = (damageType & DF_KILLSHOT) ? true : false
if ( isHeadShot )
PlayVictimHeadshotConfirmation( isKillShot );
//Jolt view if player is getting meleed
if ( damageSourceId == eDamageSourceId.human_melee )
{
vector joltDir = Normalize( localViewPlayer.CameraPosition() - damageOrigin )
//clear melee assist when you get meleed
localViewPlayer.Lunge_ClearTarget()
}
array weaponMods
if ( eModId != null && eModId in modNameStrings )
weaponMods.append( expect string( modNameStrings[ eModId ] ) )
if ( DebugVictimClientDamageFeedbackIsEnabled() && !IsWatchingReplay() )
{
string modDesc = (weaponMods.len() > 0 ? (" +" + weaponMods[0]) : "")
bool isCritical = (damageType & DF_CRITICAL) ? true : false
DebugTookDamagePrint( localViewPlayer, attacker, damage, damageSourceId, modDesc, isHeadShot, isKillShot, isCritical, false, 0, false, false )
}
RumbleForPilotDamage( damage )
DamageHistoryStruct damageTable = StoreDamageHistoryAndUpdate( localViewPlayer, MAX_DAMAGE_HISTORY_TIME, damage, damageOrigin, damageType, damageSourceId, attacker, weaponMods )
DamageIndicators( damageTable, false )
if ( damageSourceId in clGlobal.onLocalPlayerTookDamageCallback )
{
foreach ( callback in clGlobal.onLocalPlayerTookDamageCallback[ damageSourceId ] )
callback( damage, damageOrigin, damageType, damageSourceId, attacker )
}
}
/*
void function ClientCodeCallback_OnMissileCreation( entity missileEnt, string weaponName, bool firstTime )
{
}
*/
void function ClientCodeCallback_CreateGrenadeIndicator( entity missileEnt, string weaponName )
{
if ( !IsValid( missileEnt ) )
return
//Called for all projectiles, not just missiles.
TryAddGrenadeIndicator( missileEnt, weaponName )
}
void function DamageIndicators( DamageHistoryStruct damageHistory, bool playerIsTitan )
{
if ( damageHistory.damageType & DF_NO_INDICATOR )
return
if ( !level.clientScriptInitialized )
return
if ( IsWatchingThirdPersonKillReplay() )
return
entity localViewPlayer = GetLocalViewPlayer()
int arrowType = DAMAGEARROW_MEDIUM
if ( IsValid( damageHistory.attacker ) )
{
if ( damageHistory.attacker == localViewPlayer )
return
if ( damageHistory.attacker.IsTitan() )
arrowType = DAMAGEARROW_MEDIUM
else if ( damageHistory.attacker.IsPlayer() )
arrowType = DAMAGEARROW_SMALL
else
arrowType = DAMAGEARROW_SMALL
//if ( damageHistory.attacker.IsTitan() )
// arrowType = DAMAGEARROW_LARGE
//else if ( damageHistory.attacker.IsPlayer() )
// arrowType = DAMAGEARROW_MEDIUM
//else
// arrowType = DAMAGEARROW_SMALL
}
if ( playerIsTitan )
{
entity cockpit = localViewPlayer.GetCockpit()
if ( !cockpit )
return
vector dirToDamage = damageHistory.origin - localViewPlayer.GetOrigin()
dirToDamage.z = 0
dirToDamage = Normalize( dirToDamage )
vector playerViewForward = localViewPlayer.GetViewVector()
playerViewForward.z = 0.0
playerViewForward = Normalize( playerViewForward )
float damageFrontDot = DotProduct( dirToDamage, playerViewForward )
if ( damageFrontDot >= 0.707107 )
cockpit.AddToTitanHudDamageHistory( COCKPIT_PANEL_TOP, damageHistory.damage )
else if ( damageFrontDot <= -0.707107 )
cockpit.AddToTitanHudDamageHistory( COCKPIT_PANEL_BOTTOM, damageHistory.damage )
else
{
vector playerViewRight = localViewPlayer.GetViewRight()
playerViewRight.z = 0.0
playerViewRight = Normalize( playerViewRight )
float damageRightDot = DotProduct( dirToDamage, playerViewRight )
if ( damageRightDot >= 0.707107 )
cockpit.AddToTitanHudDamageHistory( COCKPIT_PANEL_RIGHT, damageHistory.damage )
else
cockpit.AddToTitanHudDamageHistory( COCKPIT_PANEL_LEFT, damageHistory.damage )
}
if ( damageHistory.attacker && damageHistory.attacker.GetParent() == localViewPlayer )
{
damageHistory.rodeoDamage = true
return
}
}
#if SP
if ( IsValid( damageHistory.attacker ) && damageHistory.attacker.IsTitan() )
arrowType = DAMAGEARROW_LARGE
else if ( playerIsTitan && damageHistory.damage < 50 )
return
else if ( !playerIsTitan && damageHistory.damage < 15 )
arrowType = DAMAGEARROW_SMALL
else
arrowType = DAMAGEARROW_MEDIUM
thread DamageIndicatorRui( damageHistory.origin, arrowType, playerIsTitan )
#else
bool show2DIndicator = true
bool show3DIndicator = false
const int DAMAGE_INDICATOR_STYLE_2D_ONLY = 0
const int DAMAGE_INDICATOR_STYLE_BOTH = 1
const int DAMAGE_INDICATOR_STYLE_3D_ONLY = 2
if ( playerIsTitan )
{
show2DIndicator = GetConVarInt( "damage_indicator_style_titan" ) != DAMAGE_INDICATOR_STYLE_3D_ONLY
show3DIndicator = GetConVarInt( "damage_indicator_style_titan" ) != DAMAGE_INDICATOR_STYLE_2D_ONLY
}
else
{
show2DIndicator = GetConVarInt( "damage_indicator_style_pilot" ) != DAMAGE_INDICATOR_STYLE_3D_ONLY
show3DIndicator = GetConVarInt( "damage_indicator_style_pilot" ) != DAMAGE_INDICATOR_STYLE_2D_ONLY
}
if ( show2DIndicator )
thread DamageIndicatorRui( damageHistory.origin, arrowType, playerIsTitan )
if ( show3DIndicator )
ShowDamageArrow( localViewPlayer, damageHistory.origin, arrowType, playerIsTitan, damageHistory.attacker )
#endif
}
const float DAMAGE_INDICATOR_DURATION = 4.0
void function DamageIndicatorRui( vector damageOrigin, int arrowType, bool playerIsTitan )
{
clGlobal.levelEnt.EndSignal( "KillReplayStarted" )
clGlobal.levelEnt.EndSignal( "KillReplayEnded" )
// slop
float distance = Length( damageOrigin - GetLocalViewPlayer().CameraPosition() )
float randomRange = GraphCapped( distance, 0.0, 2048, 0.0, 256.0 )
damageOrigin =
float startTime = Time()
var rui = RuiCreate( $"ui/damage_indicator.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 0 )
RuiSetResolutionToScreenSize( rui )
RuiSetGameTime( rui, "startTime", startTime )
RuiSetFloat( rui, "duration", DAMAGE_INDICATOR_DURATION )
RuiSetInt( rui, "attackerType", arrowType )
file.damageIndicatorCount++
int damageIndicatorThreshold = file.damageIndicatorCount + 8
OnThreadEnd(
function() : ( rui )
{
RuiDestroy( rui )
}
)
while ( Time() - startTime < DAMAGE_INDICATOR_DURATION && file.damageIndicatorCount < damageIndicatorThreshold )
{
vector vecToDamage = damageOrigin - GetLocalViewPlayer().CameraPosition()
vecToDamage.z = 0
vecToDamage = Normalize( vecToDamage )
RuiSetFloat3( rui, "vecToDamage2D", vecToDamage )
RuiSetFloat3( rui, "camVec2D", Normalize( AnglesToForward( < 0, GetLocalViewPlayer().CameraAngles().y, 0 > ) ) )
RuiSetFloat( rui, "sideDot", vecToDamage.Dot( CrossProduct( <0, 0, 1>, Normalize( AnglesToForward( < 0, GetLocalViewPlayer().CameraAngles().y, 0 > ) ) ) ) )
WaitFrame()
}
}
void function ShowGrenadeArrow( entity player, entity grenade, float damageRadius, float startDelay, bool requireLos = true )
{
thread GrenadeArrowThink( player, grenade, damageRadius, startDelay, requireLos )
}
vector function GetRandomOriginWithinBounds( entity ent )
{
vector boundingMins = ent.GetBoundingMins()
vector boundingMaxs = ent.GetBoundingMaxs()
vector randomOffset = < RandomFloatRange( boundingMins.x, boundingMaxs.x ), RandomFloatRange( boundingMins.y, boundingMaxs.y ), RandomFloatRange( boundingMins.z, boundingMaxs.z ) >
return ent.GetOrigin() + randomOffset
}
void function GrenadeArrowThink( entity player, entity grenade, float damageRadius, float startDelay, bool requireLos, string requiredPlayerState = "any" )
{
EndSignal( grenade, "OnDeath" ) //On death added primarily for frag_drones
EndSignal( grenade, "OnDestroy" )
EndSignal( player, "OnDeath" )
wait startDelay
asset grenadeModel = GRENADE_INDICATOR_FRAG_MODEL
vector grenadeOffset = < -5, 0, 0 >
if ( grenade instanceof C_Projectile )
{
if ( grenade.ProjectileGetWeaponClassName() == "mp_weapon_grenade_sonar" )
{
grenadeModel = GRENADE_INDICATOR_SONAR_MODEL
grenadeOffset = < -5, 0, 0 >
requireLos = false
}
}
else if ( grenade.IsNPC() )
{
switch ( grenade.GetSignifierName() )
{
#if MP
case "npc_stalker":
grenadeModel = GRENADE_INDICATOR_STALKER_MODEL
break
#endif
case "npc_frag_drone":
grenadeModel = GRENADE_INDICATOR_TICK_MODEL
break
}
}
entity arrow = CreateClientSidePropDynamic( < 0, 0, 0 >, < 0, 0, 0 >, GRENADE_INDICATOR_ARROW_MODEL )
entity mdl = CreateClientSidePropDynamic( < 0, 0, 0 >, < 0, 0, 0 >, grenadeModel )
OnThreadEnd(
function() : ( arrow, mdl )
{
if ( IsValid( arrow ) )
arrow.Destroy()
if ( IsValid( mdl ) )
mdl.Destroy()
}
)
entity cockpit = player.GetCockpit()
if ( !cockpit )
return
EndSignal( cockpit, "OnDestroy" )
arrow.SetParent( cockpit, "CAMERA_BASE" )
arrow.SetAttachOffsetOrigin( < 25.0, 0.0, -4.0 > )
mdl.SetParent( arrow, "BACK" )
mdl.SetAttachOffsetOrigin( grenadeOffset )
float lastVisibleTime = 0
bool shouldBeVisible = true
while ( true )
{
cockpit = player.GetCockpit()
switch ( requiredPlayerState )
{
case "any":
shouldBeVisible = true
break
case "pilot":
shouldBeVisible = !player.IsTitan()
break
case "titan":
shouldBeVisible = player.IsTitan()
break
default:
Assert( false, "Invalid player state! Allower states: 'any' 'pilot' 'titan'" )
}
if ( shouldBeVisible )
{
if ( Distance( player.GetOrigin(), grenade.GetOrigin() ) > damageRadius || !cockpit )
{
shouldBeVisible = false
}
else
{
bool tracePassed = false
if ( requireLos )
{
TraceResults result = TraceLine( grenade.GetOrigin(), GetRandomOriginWithinBounds( player ), grenade, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE )
if ( result.fraction == 1.0 )
tracePassed = true
}
if ( requireLos && !tracePassed )
{
shouldBeVisible = false
}
else
{
shouldBeVisible = true
lastVisibleTime = Time()
}
}
}
if ( shouldBeVisible || Time() - lastVisibleTime < 0.25 )
{
arrow.EnableDraw()
mdl.EnableDraw()
arrow.DisableRenderWithViewModelsNoZoom()
arrow.EnableRenderWithCockpit()
arrow.EnableRenderWithHud()
mdl.DisableRenderWithViewModelsNoZoom()
mdl.EnableRenderWithCockpit()
mdl.EnableRenderWithHud()
vector damageArrowAngles = AnglesInverse( player.EyeAngles() )
vector vecToDamage = grenade.GetOrigin() - (player.EyePosition() + (player.GetViewVector() * 20.0))
// reparent for embark/disembark
if ( arrow.GetParent() == null )
arrow.SetParent( cockpit, "CAMERA_BASE", true )
arrow.SetAttachOffsetAngles( damageArrowAngles.AnglesCompose( vecToDamage.VectorToAngles() ) )
}
else
{
mdl.DisableDraw()
arrow.DisableDraw()
}
WaitFrame()
}
}
function Create_DamageIndicatorHUD()
{
}
void function SCB_AddGrenadeIndicatorForEntity( int team, int ownerHandle, int eHandle, float damageRadius )
{
if ( !level.grenadeIndicatorEnabled )
return
#if DEV
if ( !level.clientScriptInitialized )
return
#endif
entity player = GetLocalViewPlayer()
entity owner = GetEntityFromEncodedEHandle( ownerHandle )
entity ent = GetEntityFromEncodedEHandle( eHandle )
if ( !IsValid( ent ) )
return
if ( team == player.GetTeam() && owner != player )
return
//TryAddGrenadeIndicator( ent, "" ) // TODO: make function handle non-grenade ents
}
function TryAddGrenadeIndicator( grenade, weaponName )
{
#if DEV
if ( !level.clientScriptInitialized )
return
#endif
if ( !level.grenadeIndicatorEnabled )
return
expect entity( grenade )
entity player = GetLocalViewPlayer()
// view player may be null when dead
if ( !IsValid( player ) )
return
var className = grenade.GetClassName()
float damageRadius = 0.0
if ( className == "grenade" )
{
damageRadius = grenade.GetDamageRadius()
}
else if ( grenade.ProjectileGetWeaponClassName() == "mp_titanweapon_arc_ball" )
{
// arc ball doesn't arc to pilots so no need to show the warning
if ( !player.IsTitan() )
return
damageRadius = BALL_LIGHTNING_ZAP_RADIUS
}
else
{
return
}
float radius = grenade.GetExplosionRadius()
if ( player.IsPhaseShifted() )
return
float startDelay = 0.0
if ( grenade.GetOwner() == player )
{
if ( !grenade.GetProjectileWeaponSettingBool( eWeaponVar.projectile_damages_owner ) && !grenade.GetProjectileWeaponSettingBool( eWeaponVar.explosion_damages_owner ) )
return
float relVelocity = Length( grenade.GetVelocity() - player.GetVelocity() )
if ( relVelocity < DAMAGEHUD_GRENADE_DEBOUNCE_TIME_LOWSPEED_VELOCITYCUTOFF )
startDelay = DAMAGEHUD_GRENADE_DEBOUNCE_TIME_LOWSPEED
else
startDelay = DAMAGEHUD_GRENADE_DEBOUNCE_TIME
}
else if ( grenade.GetTeam() == player.GetTeam() )
{
return
}
float padding = player.IsTitan() ? 204.0 : 65.0
//AddGrenadeIndicator( grenade, radius + padding, startDelay, false )
ShowGrenadeArrow( player, grenade, radius + padding, startDelay )
//thread ShowRuiGrenadeThreatIndicator( grenade, float( radius ) + padding )
}
void function ShowRuiGrenadeThreatIndicator( entity grenade, float radius )
{
var rui = RuiCreate( $"ui/grenade_threat_indicator.rpak", clGlobal.topoCockpitHudPermanent, RUI_DRAW_COCKPIT, 0 )
//var rui = CreateCockpitRui( $"ui/grenade_threat_indicator.rpak", 0 )
RuiSetGameTime( rui, "startTime", Time() )
RuiSetFloat( rui, "damageRadius", radius )
//RuiTrackFloat3( rui, "pos", grenade, RUI_TRACK_ABSORIGIN_FOLLOW )`
RuiTrackFloat3( rui, "pos", grenade, RUI_TRACK_POINT_FOLLOW, grenade.LookupAttachment( "BACK" ) )
OnThreadEnd(
function() : ( rui )
{
RuiDestroy( rui )
}
)
grenade.WaitSignal( "OnDestroy" )
}
void function InitDamageArrows()
{
for ( int i = 0; i < file.numDamageArrows; i++ )
{
table arrowData = {
grenade = null
grenadeRadius = 0.0
damageOrigin = < 0.0, 0.0, 0.0 >,
damageDirection = < 0.0, 0.0, 0.0 >,
endTime = -99.0 + DAMAGEARROW_DURATION,
startTime = -99.0,
isDying = false,
isVisible = false,
whizby = false, // hack until we get a new model/shader for the whizby indicator - Roger
attacker = null,
randomAngle = 0 // Repeated shots from the same attacker randomize the angle of the arrow.
}
entity arrow = CreateClientSidePropDynamic( < 0, 0, 0 >, < 0, 0, 0 >, DAMAGEARROW_MODEL )
arrow.SetCanCloak( false )
arrow.SetVisibleForLocalPlayer( 0 )
arrow.DisableDraw()
arrowData.arrow <- arrow
arrow.s.arrowData <- arrowData
file.damageArrows.append( arrowData )
}
entity arrow = CreateClientSidePropDynamic( < 0, 0, 0 >, < 0, 0, 0 >, DAMAGEARROW_MODEL )
file.damageArrowFadeDuration = arrow.GetSequenceDuration( DAMAGEARROW_FADEANIM ) // 0.266
arrow.Destroy()
}
void function DamageArrow_CockpitInit( entity cockpit )
{
entity localViewPlayer = GetLocalViewPlayer()
thread UpdateDamageArrows( localViewPlayer, cockpit )
}
function RefreshExistingDamageArrow( entity player, arrowData, int arrowType, damageOrigin )
{
//Hack - 10 tick rate is making damage feedback bunch up. If we improve that then shouldn't be threaded.
player.EndSignal( "OnDestroy" )
entity cockpit = player.GetCockpit()
if ( IsValid( cockpit ) )
cockpit.EndSignal( "OnDestroy" )
float time = Time()
if ( arrowData.startTime == time )
wait 0.05
if ( !arrowData.isVisible || arrowData.isDying )
return
time = Time()
arrowData.endTime = time + file.arrowIncomingAnims[ arrowType ].duration
arrowData.startTime = time
arrowData.damageOrigin = damageOrigin
arrowData.randomAngle = RandomIntRange( -3, 3 )
PulseDamageArrow( expect entity( arrowData.arrow ), arrowType )
UpdateDamageArrowVars( player )
UpdateDamageArrowAngle( arrowData )
}
function ShowDamageArrow( entity player, damageOrigin, int arrowType, playerIsTitan, attacker )
{
if ( file.damageArrows.len() == 0 ) // not yet initialized
return
table arrowData = file.damageArrows[file.currentDamageArrow]
entity arrow = expect entity( arrowData.arrow )
file.currentDamageArrow++
if ( file.currentDamageArrow >= file.numDamageArrows )
file.currentDamageArrow = 0
float time = Time()
arrow.s.arrowData.damageOrigin = damageOrigin
arrow.s.arrowData.grenade = null
arrow.s.arrowData.grenadeRadius = 0.0
arrow.s.arrowData.endTime = time + file.arrowIncomingAnims[ arrowType ].duration
arrow.s.arrowData.startTime = time
arrow.s.arrowData.isDying = false
arrow.s.arrowData.whizby = false // hack until we get a new model/shader for the whizby indicator
arrow.s.arrowData.attacker = attacker
if ( !arrow.s.arrowData.isVisible )
{
entity cockpit = player.GetCockpit()
if ( !cockpit )
return
arrow.s.arrowData.isVisible = true
arrow.EnableDraw()
arrow.DisableRenderWithViewModelsNoZoom()
arrow.EnableRenderWithCockpit()
arrow.EnableRenderWithHud()
arrow.SetParent( cockpit, "CAMERA_BASE" )
arrow.SetAttachOffsetOrigin( < 20.0, 0.0, -2.0 > )
}
PulseDamageArrow( arrow, arrowType )
UpdateDamageArrowVars( player )
UpdateDamageArrowAngle( arrowData )
}
function PulseDamageArrow( entity arrow, int arrowType )
{
arrow.Anim_NonScriptedPlay( file.arrowIncomingAnims[ arrowType ].anim )
}
function UpdateDamageArrowVars( entity localViewPlayer )
{
file.damageArrowTime = Time()
file.damageArrowAngles = AnglesInverse( localViewPlayer.EyeAngles() )
file.damageArrowPointCenter = localViewPlayer.EyePosition() + ( localViewPlayer.GetViewVector() * 20.0 )
}
function UpdateDamageArrowAngle( arrowData )
{
if ( IsValid( arrowData.grenade ) )
arrowData.damageOrigin = arrowData.grenade.GetOrigin()
vector vecToDamage = expect vector( arrowData.damageOrigin ) - file.damageArrowPointCenter
vector anglesToDamage = VectorToAngles( vecToDamage )
vector eyeAngles = GetLocalViewPlayer().EyeAngles()
float roll = sin( DegToRad( eyeAngles.y - anglesToDamage.y ) )
arrowData.arrow.SetAttachOffsetAngles( AnglesCompose( file.damageArrowAngles, anglesToDamage ) + < arrowData.randomAngle, 0, roll * 90.0 > )
arrowData.damageDirection = Normalize( vecToDamage )
}
function UpdateDamageArrows( entity localViewPlayer, entity cockpit )
{
localViewPlayer.EndSignal( "OnDestroy" )
cockpit.EndSignal( "OnDestroy" )
OnThreadEnd(
function() : ( localViewPlayer )
{
foreach ( arrowData in file.damageArrows )
{
if ( IsValid( arrowData.arrow ) )
{
arrowData.arrow.DisableDraw()
arrowData.arrow.ClearParent()
arrowData.attacker = null
arrowData.isVisible = false
arrowData.randomAngle = 0
}
}
}
)
bool varsUpdated = false
while ( true )
{
WaitEndFrame()
vector playerOrigin = localViewPlayer.GetOrigin()
varsUpdated = false
bool inPhaseShift = localViewPlayer.IsPhaseShifted()
foreach ( arrowData in file.damageArrows )
{
if ( !arrowData.isVisible )
{
continue
}
if ( arrowData.grenade != null )
{
if ( !IsValid( arrowData.grenade ) )
arrowData.endTime = 0.0
}
if ( (file.damageArrowTime >= arrowData.endTime) || inPhaseShift )
{
arrowData.arrow.DisableDraw()
arrowData.arrow.ClearParent()
arrowData.attacker = null
arrowData.isVisible = false
arrowData.randomAngle = 0
continue
}
if ( !varsUpdated ) // only call UpdateDamageArrowVars if one or more of the file.damageArrows is visible
{
varsUpdated = true
UpdateDamageArrowVars( localViewPlayer )
}
UpdateDamageArrowAngle( arrowData )
if ( !arrowData.isDying && ( ( arrowData.endTime - file.damageArrowTime ) <= file.damageArrowFadeDuration ) )
{
arrowData.isDying = true
arrowData.arrow.Anim_NonScriptedPlay( DAMAGEARROW_FADEANIM )
}
}
wait( 0.0 )
}
}