aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/scripts/vscripts/mp/_bleedout.gnut
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.CustomServers/scripts/vscripts/mp/_bleedout.gnut')
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_bleedout.gnut403
1 files changed, 403 insertions, 0 deletions
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_bleedout.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_bleedout.gnut
new file mode 100644
index 00000000..2192b4b1
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_bleedout.gnut
@@ -0,0 +1,403 @@
+//Bleed Out Mechanic Shared by several game modes.
+global function Bleedout_Init
+global function Bleedout_StartPlayerBleedout
+global function Bleedout_SetCallback_OnPlayerStartBleedout
+global function Bleedout_SetCallback_OnPlayerGiveFirstAid
+global function Bleedout_ShouldAIMissBleedingPlayer
+
+const asset FX_BLOODTRAIL = $"skit_blood_decal_LG"
+const float BLEEDOUT_MAX_USE_DIST2_MOD = 64 * 64
+
+struct
+{
+ table<entity,bool> isBleeding
+ table<entity, entity> IsGettingFirstAidFrom
+ table<entity,entity> lastAttacker
+ void functionref(entity) Callback_OnPlayerStartBleedout
+ void functionref(entity) Callback_OnPlayerGiveFirstAid
+ int firstAidAttemptID = 0 //The ID that identifies the first aid attempt. Used to distinguish between simultainous healing attempts on the client
+} file
+
+void function Bleedout_Init()
+{
+ RegisterSignal( "BleedOut_StopBleeding" )
+ RegisterSignal( "BleedOut_OnRevive" )
+ RegisterSignal( "BleedOut_OnStartDying" )
+ RegisterSignal( "OnContinousUseStopped" )
+
+ AddCallback_OnClientConnected( Bleedout_OnClientConnected )
+ AddCallback_OnClientDisconnected( Bleedout_OnClientDisconnected )
+
+ PrecacheParticleSystem( FX_BLOODTRAIL )
+}
+
+void function Bleedout_OnClientConnected( entity player )
+{
+ file.isBleeding[ player ] <- false
+ file.IsGettingFirstAidFrom[ player ] <- null
+ file.lastAttacker[ player ] <- svGlobal.worldspawn
+}
+
+void function Bleedout_OnClientDisconnected( entity player )
+{
+ delete file.isBleeding[ player ]
+ delete file.IsGettingFirstAidFrom[ player ]
+ delete file.lastAttacker[ player ]
+}
+
+void function Bleedout_SetCallback_OnPlayerStartBleedout( void functionref(entity) callback )
+{
+ file.Callback_OnPlayerStartBleedout = callback
+}
+
+void function Bleedout_SetCallback_OnPlayerGiveFirstAid( void functionref(entity) callback )
+{
+ file.Callback_OnPlayerGiveFirstAid = callback
+}
+
+void function Bleedout_StartPlayerBleedout( entity player, entity attacker )
+{
+ //if the player is already bleeding don't restart bleeding logic.
+ if ( file.isBleeding[ player ] )
+ return
+
+ player.Signal( "BleedOut_StopBleeding" )
+ player.Signal( "BleedOut_OnStartDying" )
+
+ file.lastAttacker[ player ] = attacker
+
+ if ( IsValid( file.Callback_OnPlayerStartBleedout ) && !file.isBleeding[ player ] )
+ file.Callback_OnPlayerStartBleedout( player )
+
+ thread BloodTrail( player )
+ thread PlayerDying( player )
+ thread EnablePlayerRes( player )
+
+ //Start selfhealing thread if enabled.
+ if ( Bleedout_GetSelfResEnabled() )
+ thread EnablePlayerSelfRes( player )
+
+ if ( Bleedout_GetDeathOnTeamBleedout() )
+ CheckForTeamBleedout( player.GetTeam() )
+}
+
+void function PlayerDying( entity player )
+{
+ Assert( IsNewThread(), "Must be threaded off." )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "BleedOut_OnRevive" )
+ player.EndSignal( "BleedOut_OnStartDying" )
+
+ float bleedoutTime = Bleedout_GetBleedoutTime()
+ bool forceHolster = Bleedout_GetForceWeaponHolster()
+
+ array<int> ids = []
+ ids.append( StatusEffect_AddEndless( player, eStatusEffect.move_slow, 0.25 ) )
+ ids.append( StatusEffect_AddEndless( player, eStatusEffect.turn_slow, 0.3 ) )
+
+ if ( bleedoutTime > 0 )
+ ids.append( StatusEffect_AddEndless( player, eStatusEffect.bleedoutDOF, 1.0 ) )
+
+ file.isBleeding[ player ] = true
+
+ player.ForceCrouch()
+ player.SetOneHandedWeaponUsageOn()
+
+ if ( forceHolster )
+ HolsterAndDisableWeapons( player )
+
+ OnThreadEnd(
+ function() : ( player, ids, forceHolster )
+ {
+ if ( IsValid( player ) )
+ {
+ foreach ( id in ids )
+ StatusEffect_Stop( player, id )
+
+ file.isBleeding[ player ] = false
+ file.lastAttacker[ player ] = svGlobal.worldspawn
+
+ player.UnforceCrouch()
+ player.SetOneHandedWeaponUsageOff()
+ //Remote_CallFunction_NonReplay( player, "ServerCallback_BLEEDOUT_PlayerRevivedDOF" )
+
+ if ( forceHolster )
+ DeployAndEnableWeapons( player )
+
+ //Hide wounded icon for wounded player's allies
+ int woundedPlayerEHandle = player.GetEncodedEHandle()
+ array<entity> teamPlayers = GetPlayerArrayOfTeam( player.GetTeam() )
+ foreach ( entity teamPlayer in teamPlayers )
+ {
+ if ( teamPlayer == player )
+ continue
+ Remote_CallFunction_NonReplay( teamPlayer, "ServerCallback_BLEEDOUT_HideWoundedMarker", woundedPlayerEHandle )
+ }
+ }
+ }
+ )
+
+ //if ( bleedoutTime > 0 )
+ // StatusEffect_AddTimed( player, eStatusEffect.bleedoutDOF, 1.0, bleedoutTime, 0.0 )
+ //Remote_CallFunction_NonReplay( player, "ServerCallback_BLEEDOUT_StartDyingDOF", bleedoutTime )
+
+ //Show wounded icon for wounded player's allies
+ int woundedPlayerEHandle = player.GetEncodedEHandle()
+ array<entity> teamPlayers = GetPlayerArrayOfTeam( player.GetTeam() )
+ foreach ( entity teamPlayer in teamPlayers )
+ {
+ if ( teamPlayer == player )
+ continue
+
+ Remote_CallFunction_NonReplay( teamPlayer, "ServerCallback_BLEEDOUT_ShowWoundedMarker", woundedPlayerEHandle, Time(), Time() + bleedoutTime )
+ }
+
+ if ( bleedoutTime > 0 )
+ wait bleedoutTime
+ else
+ WaitForever()
+
+ PlayerDiesFromBleedout( player, file.lastAttacker[ player ] )
+}
+
+void function EnablePlayerRes( entity player )
+{
+ Assert( IsNewThread(), "Must be threaded off." )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "BleedOut_OnStartDying" )
+ player.EndSignal( "BleedOut_OnRevive" )
+
+ Highlight_SetFriendlyHighlight( player, "interact_object_los_line" )
+
+ if ( IsPilotEliminationBased() )
+ SetPlayerEliminated( player )
+
+ OnThreadEnd(
+ function() : ( player )
+ {
+ if ( IsValid( player ) )
+ {
+ player.UnsetUsable()
+ Highlight_ClearFriendlyHighlight( player )
+ }
+ }
+ )
+
+ while ( true )
+ {
+ //If the player is not currently being treated or is self healing. (Team healing should always override self-healing)
+ if ( !IsPlayerGettingFirstAid( player ) || IsPlayerSelfHealing( player ) )
+ {
+ player.SetUsableByGroup( "friendlies pilot" )
+ player.SetUsePrompts( "#BLEEDOUT_USE_TEAMMATE_RES", "#BLEEDOUT_USE_TEAMMATE_RES_PC" )
+
+ entity playerHealer = expect entity ( player.WaitSignal( "OnPlayerUse" ).player )
+ player.UnsetUsable()
+
+ //Player can only res other players if they are not bleeding out themselves.
+ if ( !file.isBleeding[ playerHealer ] && ( !IsPlayerGettingFirstAid( player ) || IsPlayerSelfHealing( player ) ) )
+ waitthread PlayerAttemptRes( playerHealer, player )
+ }
+ else
+ {
+ WaitFrame()
+ }
+ }
+}
+
+void function EnablePlayerSelfRes( entity player )
+{
+ Assert( IsNewThread(), "Must be threaded off." )
+ player.EndSignal( "OnDeath" )
+ player.EndSignal( "BleedOut_OnStartDying" )
+ player.EndSignal( "BleedOut_OnRevive" )
+
+ while ( true )
+ {
+ if ( !IsPlayerGettingFirstAid( player ) )
+ MessageToPlayer( player, eEventNotifications.BLEEDOUT_SelfHealPrompt )
+
+ if ( player.UseButtonPressed() && !IsPlayerGettingFirstAid( player ) )
+ {
+ MessageToPlayer( player, eEventNotifications.Clear )
+ waitthread PlayerAttemptRes( player, player )
+ }
+
+ WaitFrame()
+ }
+}
+
+void function PlayerAttemptRes( entity playerHealer, entity playerToRes )
+{
+ Assert( IsNewThread(), "Must be threaded off." )
+ playerToRes.EndSignal( "OnDeath" )
+ playerHealer.EndSignal( "OnDeath" )
+ playerHealer.EndSignal( "OnContinousUseStopped" )
+
+ HolsterAndDisableWeapons( playerHealer )
+
+ playerHealer.MovementDisable()
+ playerToRes.MovementDisable()
+
+ float firstAidTime = playerHealer == playerToRes ? Bleedout_GetFirstAidTimeSelf() : Bleedout_GetFirstAidTime()
+ float firstAidHealPercent = Bleedout_GetFirstAidHealPercent()
+
+ float endTime = Time() + firstAidTime
+
+ int playerEHandle = playerToRes.GetEncodedEHandle()
+ int healerEHandle = playerHealer.GetEncodedEHandle()
+ int attemptID = GetNewFirstAidAttemptID()
+
+ Remote_CallFunction_NonReplay( playerToRes, "ServerCallback_BLEEDOUT_StartFirstAidProgressBar", endTime, playerEHandle, healerEHandle, attemptID )
+ Remote_CallFunction_NonReplay( playerHealer, "ServerCallback_BLEEDOUT_StartFirstAidProgressBar", endTime, playerEHandle, healerEHandle, attemptID )
+ file.IsGettingFirstAidFrom[ playerToRes ] = playerHealer
+
+ OnThreadEnd(
+ function() : ( playerHealer, playerToRes, attemptID )
+ {
+ if ( IsValid( playerHealer ) )
+ {
+ DeployAndEnableWeapons( playerHealer )
+ playerHealer.MovementEnable()
+ Remote_CallFunction_NonReplay( playerHealer, "ServerCallback_BLEEDOUT_StopFirstAidProgressBar", attemptID )
+ }
+
+ if ( IsValid( playerToRes ) )
+ {
+ file.IsGettingFirstAidFrom[ playerToRes ] = null
+ playerToRes.MovementEnable()
+ Remote_CallFunction_NonReplay( playerToRes, "ServerCallback_BLEEDOUT_StopFirstAidProgressBar", attemptID )
+ }
+ }
+ )
+
+ waitthread TrackContinuousUse( playerHealer, playerToRes, firstAidTime, true )
+
+ //Heal player health
+ playerToRes.SetHealth( playerToRes.GetMaxHealth() * firstAidHealPercent )
+ file.isBleeding[ playerToRes ] = false
+ file.lastAttacker[ playerToRes ] = svGlobal.worldspawn
+ if ( IsPilotEliminationBased() )
+ ClearPlayerEliminated( playerToRes )
+
+ if ( IsValid( file.Callback_OnPlayerGiveFirstAid ) )
+ {
+ //Do not run this callback if player is self healing.
+ if ( playerHealer != playerToRes )
+ file.Callback_OnPlayerGiveFirstAid( playerHealer )
+ }
+
+ playerToRes.Signal( "BleedOut_OnRevive" )
+
+}
+
+void function BloodTrail( entity player )
+{
+ player.EndSignal( "BleedOut_StopBleeding" )
+ player.EndSignal( "BleedOut_OnRevive" )
+ player.EndSignal( "OnDeath")
+
+ while ( true )
+ {
+ float interval = RandomFloatRange( 0.25, 0.5 )
+ PlayFXOnEntity( FX_BLOODTRAIL, player )
+ wait interval
+ }
+}
+
+void function PlayerDiesFromBleedout( entity player, entity attacker )
+{
+ if ( IsValid( attacker ) )
+ {
+ player.Die( attacker, attacker, { damageSourceId = eDamageSourceId.bleedout } )
+ //player.BecomeRagdoll( Vector(0,0,0), false )
+ }
+ else
+ {
+ player.Die( svGlobal.worldspawn, svGlobal.worldspawn, { damageSourceId = eDamageSourceId.bleedout } )
+ //player.BecomeRagdoll( Vector(0,0,0), false )
+ }
+
+
+}
+
+//This function checks to see if all players on a team are dead or bleeding out.
+//If all the players are dead/bleeding out, it kills the surviving team players.
+void function CheckForTeamBleedout( int team )
+{
+ array<entity> teamPlayers = GetPlayerArrayOfTeam( team )
+ foreach ( entity teamPlayer in teamPlayers )
+ {
+ if ( IsAlive( teamPlayer ) && !file.isBleeding[ teamPlayer ] )
+ return
+ }
+
+ //All players on team are bleeding out
+ foreach ( entity teamPlayer in teamPlayers )
+ {
+ if ( IsAlive( teamPlayer ) )
+ PlayerDiesFromBleedout( teamPlayer, file.lastAttacker[ teamPlayer ] )
+ }
+}
+
+bool function Bleedout_ShouldAIMissBleedingPlayer( entity player )
+{
+ //If the player is not bleeding
+ if ( !file.isBleeding[ player ] )
+ return false
+
+ //If the bleedout settings don't affect AI accuracy.
+ if ( !Bleedout_ShouldAIMissPlayer() )
+ return false
+
+ return true
+}
+
+bool function IsPlayerGettingFirstAid( entity player )
+{
+ return file.IsGettingFirstAidFrom[ player ] != null
+}
+
+bool function IsPlayerSelfHealing( entity player )
+{
+ return file.IsGettingFirstAidFrom[ player ] == player
+}
+
+//////////////
+//Utilities
+//////////////
+void function TrackContinuousUse( entity player, entity useTarget, float useTime, bool doRequireUseButtonHeld )
+{
+ player.EndSignal( "OnDeath" )
+ useTarget.EndSignal( "OnDeath" )
+ useTarget.EndSignal( "OnDestroy" )
+
+ table result = {}
+ result.success <- false
+
+ float maxDist2 = DistanceSqr( player.GetOrigin(), useTarget.GetOrigin() ) + BLEEDOUT_MAX_USE_DIST2_MOD
+
+ OnThreadEnd
+ (
+ function() : ( player, result )
+ {
+ if ( !result.success )
+ {
+ player.Signal( "OnContinousUseStopped" )
+ }
+ }
+ )
+
+ float startTime = Time()
+ while ( Time() < startTime + useTime && (!doRequireUseButtonHeld || player.UseButtonPressed()) && !player.IsPhaseShifted() && DistanceSqr( player.GetOrigin(), useTarget.GetOrigin() ) <= maxDist2 )
+ WaitFrame()
+
+ if ( ( !doRequireUseButtonHeld || player.UseButtonPressed() ) && DistanceSqr( player.GetOrigin(), useTarget.GetOrigin() ) <= maxDist2 )
+ result.success = true
+}
+
+int function GetNewFirstAidAttemptID()
+{
+ file.firstAidAttemptID += 1
+ return file.firstAidAttemptID
+} \ No newline at end of file