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 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 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 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 function Revive_GetAvailablePlayerHealers( entity player ) { int team = player.GetTeam() array players = GetPlayerArrayOfTeam( team ) array 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 ] } }