diff options
Diffstat (limited to 'Northstar.CustomServers/scripts/vscripts/mp/_revive.gnut')
-rw-r--r-- | Northstar.CustomServers/scripts/vscripts/mp/_revive.gnut | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_revive.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_revive.gnut new file mode 100644 index 00000000..b2f5c467 --- /dev/null +++ b/Northstar.CustomServers/scripts/vscripts/mp/_revive.gnut @@ -0,0 +1,352 @@ +untyped + + +global function Revive_Init + +global function PlayerRevivesOrBleedsOut +global function DeathPackage_PlayerRevive +global function ShouldRevivePlayer + +const float REVIVE_BLEED_OUT_TIME = 15.0 +global const float REVIVE_DEATH_TIME = 2.0 +const float REVIVE_DIST_OUTER = 135.0 +const float REVIVE_DIST_INNER = 120.0 + +struct +{ + table fakePlayers +} file + +function Revive_Init() +{ + if ( !ReviveEnabled() ) + return + + RegisterSignal( "KillReviveNag" ) + RegisterSignal( "DoneBleedingOut" ) + RegisterSignal( "ReviveSucceeded" ) + RegisterSignal( "ReviveFailed" ) + RegisterSignal( "ForceBleedOut" ) + + AddCallback_OnClientDisconnected( ReviveOnClientDisconnect ) +} + +void function PlayerRevivesOrBleedsOut( entity player ) +{ + player.EndSignal( "OnDestroy" ) + player.EndSignal( "ForceBleedOut" ) + svGlobal.levelEnt.EndSignal( "RoundEnd" ) + + table e = { revived = false } + //thread PlayerReviveVONag( player, 0.5 ) + + OnThreadEnd( + function() : ( player, e ) + { + if ( !IsValid( player ) ) + return + + player.Signal( "KillReviveNag" ) + player.Signal( "DoneBleedingOut" ) + player.nv.reviveBleedingOut = -1.0 //-1 means off + + if ( e.revived ) + { + player.Signal( "ReviveSucceeded") + thread PlayerStandsBackUp( player ) + } + else + { + file.fakePlayers[ player ].Destroy() + player.Signal( "ReviveFailed" ) + DecideRespawnPlayer( player ) + } + } + ) + + wait( REVIVE_DEATH_TIME ) + player.StartObserverMode( OBS_MODE_DEATHCAM ) + + ForceRespawnIfEntireTeamIsDead( player ) + + float endTime = Time() + REVIVE_BLEED_OUT_TIME + player.nv.reviveBleedingOut = endTime + + bool reviving = false + float doneReviveTime = Time() + 100 + + float distOuterSqr = pow( REVIVE_DIST_OUTER, 2 ) + float distInnerSqr = pow( REVIVE_DIST_INNER, 2 ) + + while ( true ) + { + array<entity> healers = Revive_GetAvailablePlayerHealers( player ) + + //we were reviving but aren't anymore - set revive to false. + if ( reviving && !FriendlyIsReviving( healers, player, distOuterSqr ) ) + { + //thread PlayerReviveVONag( player ) + reviving = false + player.nv.reviveHealedTime = -1.0 //-1 means off + } + //we were not reviving but now we are? set the new revive done time. + else if ( !reviving && FriendlyIsReviving( healers, player, distInnerSqr ) ) + { + player.Signal( "KillReviveNag" ) + doneReviveTime = Time() + REVIVE_TIME_TO_REVIVE + player.nv.reviveHealedTime = doneReviveTime + reviving = true + } + + //are we done reviving? then set the value and return + if ( reviving && Time() > doneReviveTime ) + { + e.revived = true + return + } + + //we didn't make it + if ( !reviving && Time() > endTime ) + return + + wait 0.2 + } +} + +void function ForceRespawnIfEntireTeamIsDead( entity player ) +{ + int playerTeam = player.GetTeam() + array<entity> victimTeamMembers = GetPlayerArrayOfTeam( playerTeam ) + foreach ( member in victimTeamMembers ) + { + if ( member.p.isReviving || IsAlive( member ) ) + return + } + foreach ( member in victimTeamMembers ) + { + if ( player != member && member.p.isReviving == false ) + member.Signal( "ForceBleedOut" ) + } + MessageToTeam( GetOtherTeam( playerTeam ), eEventNotifications.EnemyTeamEliminated ) + player.Signal( "ForceBleedOut" ) +} + +void function PlayerReviveVONag( entity player, float delay = 0.5 ) +{ + player.EndSignal( "OnDestroy" ) + player.EndSignal( "KillReviveNag" ) + + OnThreadEnd( + function() : ( player ) + { + if ( IsValid( player ) ) + StopSoundOnEntity( player, "diag_coop_bleedout_help" ) + } + ) + + if ( delay > 0 ) + wait delay + + while ( true ) + { + float time = EmitSoundOnEntity( player, "diag_coop_bleedout_help" ) + wait time + + wait RandomFloatRange( 10, 15 ) + } +} + +bool function FriendlyIsReviving( array<entity> healers, entity player, float distSqr ) +{ + vector origin = player.GetOrigin() + + foreach ( friend in healers ) + { + if ( !IsAlive( friend ) ) + continue + + if ( DistanceSqr( friend.GetOrigin(), origin ) < distSqr ) + return true + } + + return false +} + +array<entity> function Revive_GetAvailablePlayerHealers( entity player ) +{ + int team = player.GetTeam() + array<entity> players = GetPlayerArrayOfTeam( team ) + array<entity> playersCanRevive = [] + foreach ( player in players ) + { + if ( !IsAlive( player ) ) + continue + + playersCanRevive.append( player ) + } + + return playersCanRevive +} + +bool function ShouldRevivePlayer( entity player, var damageInfo ) +{ + if ( !ReviveEnabled() ) + return false + + if ( !GamePlaying() ) + return false + + if ( player.ContextAction_IsMeleeExecution() ) + return false + + if ( player.IsTitan() ) + return false + + int source = DamageInfo_GetDamageSourceIdentifier( damageInfo ) + + if ( source == eDamageSourceId.fall || + source == eDamageSourceId.submerged || + source == eDamageSourceId.outOfBounds || + source == eDamageSourceId.indoor_inferno ) + return false + + return true +} + +entity function SpawnFakePlayer( entity player, int team, vector origin, vector angles, asset weaponModel, asset model ) +{ + float fadeDist = 10000.0 + int solidType = 0// 0 = no collision, 2 = bounding box, 6 = use vPhysics, 8 = hitboxes only + + entity fakePlayer = CreatePropDynamic( model, origin, angles, solidType, fadeDist ) + if ( !( player in file.fakePlayers ) ) + { + file.fakePlayers[ player ] <- null + } + file.fakePlayers[ player ] = fakePlayer + + thread FakePlayerTrack( fakePlayer, player ) + + if ( weaponModel != $"" ) + { + entity gun = CreatePropDynamic( weaponModel, origin, angles, 0, fadeDist ) + gun.SetParent( fakePlayer, "PROPGUN" ) + } + + return fakePlayer +} + +void function FakePlayerTrack( entity fakePlayer, entity player ) +{ + fakePlayer.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDestroy" ) + vector lastPlayerOrg = Vector( 0.0, 0.0, 0.0 ) + + while ( true ) + { + if ( player.GetOrigin() == lastPlayerOrg ) + player.SetVelocity( Vector( 0.0, 0.0, 0.0 ) ) + lastPlayerOrg = player.GetOrigin() + + fakePlayer.SetOrigin( player.GetOrigin() ) + WaitFrame() + } +} + +void function DeathPackage_PlayerRevive( entity player ) +{ + player.kv.VisibilityFlags = ENTITY_VISIBLE_TO_NOBODY + + vector deathOrg = player.GetOrigin() + + vector mins = Vector( -16.0, -16.0, 0.0 ) + vector maxs = Vector( 16.0, 16.0, 72.0 ) + TraceResults result = TraceHull( deathOrg + Vector( 0.0, 0.0, 8.0 ), deathOrg + Vector( 0.0, 0.0, -16000.0 ), mins, maxs, player, ( TRACE_MASK_PLAYERSOLID_BRUSHONLY | TRACE_MASK_BLOCKLOS ), TRACE_COLLISION_GROUP_NONE ) + + player.SetVelocity( Vector( 0.0, 0.0, 0.0 ) ) + thread ReviveLerpToOrigin( player, deathOrg, result.endPos ) + + entity activeWeapon = player.GetActiveWeapon() + + asset weaponModel = activeWeapon == null ? $"" : activeWeapon.GetModelName() + + entity fakePlayer = SpawnFakePlayer( player, player.GetTeam(), deathOrg, player.GetAngles(), weaponModel, player.GetModelName() ) + fakePlayer.Anim_Play( "pt_wounded_drag_zinger_A_idle" ) + player.Anim_Play( "pt_wounded_drag_zinger_A_idle" ) +} + +void function ReviveLerpToOrigin( entity player, vector deathOrg, vector endPos ) +{ + player.EndSignal( "DoneBleedingOut" ) + player.EndSignal( "OnDestroy" ) + + entity mover = CreateScriptMover() + + OnThreadEnd( + function() : ( player, mover ) + { + if ( IsValid( player ) ) + player.ClearParent() + + if ( IsValid( mover ) ) + mover.Destroy() + } + ) + + mover.SetOrigin( deathOrg ) + player.SetOrigin( deathOrg ) + player.SetParent( mover ) + + float moveTime = GraphCapped( deathOrg.z - endPos.z, 0.0, 768.0, 0.1, 2.0 ) + + mover.NonPhysicsMoveTo( endPos, moveTime, moveTime, 0.0 ) + wait( moveTime ) + player.ClearParent() + + while ( true ) + { + player.SetOrigin( endPos ) + WaitFrame() + } +} + +void function PlayerStandsBackUp( entity player ) +{ + player.EndSignal( "OnDestroy" ) + svGlobal.levelEnt.EndSignal( "RoundEnd" ) + + entity fakePlayer = expect entity( file.fakePlayers[ player ] ) + file.fakePlayers[ player ] = null + + player.p.isReviving = true + + OnThreadEnd( + function() : ( player, fakePlayer ) + { + if ( IsValid( fakePlayer ) ) + fakePlayer.Destroy() + + if ( IsValid( player ) ) + player.p.isReviving = false + } + ) + + fakePlayer.Anim_Play( "CQB_knockback_pain_react" ) + fakePlayer.Anim_SetInitialTime( 2.0 ) + wait( 1.5 ) + + var settings = player.GetPlayerSettings() + player.SetPlayerSettings( "spectator" ) + player.SetPlayerSettings( settings ) + player.RespawnPlayer( null ) +} + +void function ReviveOnClientDisconnect( entity player ) +{ + if ( player in file.fakePlayers ) + { + if ( IsValid( file.fakePlayers[ player ] ) ) + file.fakePlayers[ player ].Destroy() + delete file.fakePlayers[ player ] + } +}
\ No newline at end of file |