Skip to content

Commit

Permalink
Fix Player Extraction Sync (#311)
Browse files Browse the repository at this point in the history
- Sends a packet when a Player Extracts
- Fixes bug where host could not extract
  • Loading branch information
paulov-t authored May 7, 2024
2 parents 4bb2517 + 224aab7 commit 6343f59
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 108 deletions.
103 changes: 1 addition & 102 deletions Source/Coop/Components/CoopGameComponents/SITGameComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ private void GameWorld_AfterGameStarted()

this.GetOrAddComponent<SITGameGCComponent>();
this.GetOrAddComponent<SITGameTimeAndWeatherSyncComponent>();
this.GetOrAddComponent<SITGameExtractionComponent>();

StartCoroutine(SendPlayerStatePacket());

Expand Down Expand Up @@ -410,105 +411,8 @@ private IEnumerator EverySecondCoroutine()

var coopGame = Singleton<ISITGame>.Instance;

var playersToExtract = new HashSet<string>();
foreach (var exfilPlayer in coopGame.ExtractingPlayers)
{
var exfilTime = new TimeSpan(0, 0, (int)exfilPlayer.Value.Item1);
var timeInExfil = new TimeSpan(DateTime.Now.Ticks - exfilPlayer.Value.Item2);
if (timeInExfil >= exfilTime)
{
if (!playersToExtract.Contains(exfilPlayer.Key))
{
Logger.LogDebug(exfilPlayer.Key + " should extract");
playersToExtract.Add(exfilPlayer.Key);
}
}
else
{
Logger.LogDebug(exfilPlayer.Key + " extracting " + timeInExfil);

}
}

// Trigger all countdown exfils (e.g. car), clients are responsible for their own extract
// since exfilpoint.Entered is local because of collision logic being local
// we start from the end because we remove as we go in `CoopSITGame.ExfiltrationPoint_OnStatusChanged`
for (int i = coopGame.EnabledCountdownExfils.Count - 1; i >= 0; i--)
{
var ep = coopGame.EnabledCountdownExfils[i];
if (coopGame.PastTime - ep.ExfiltrationStartTime >= ep.Settings.ExfiltrationTime)
{
var game = Singleton<ISITGame>.Instance;
foreach (var player in ep.Entered)
{
var hasUnmetRequirements = ep.UnmetRequirements(player).Any();
if (player != null && player.HealthController.IsAlive && !hasUnmetRequirements)
{
game.ExtractingPlayers.Remove(player.ProfileId);
game.ExtractedPlayers.Add(player.ProfileId);
}
}
ep.SetStatusLogged(ep.Reusable ? EExfiltrationStatus.UncompleteRequirements : EExfiltrationStatus.NotPresent, nameof(EverySecondCoroutine));
}
}

foreach (var player in playersToExtract)
{
coopGame.ExtractingPlayers.Remove(player);
coopGame.ExtractedPlayers.Add(player);
}

var world = Singleton<GameWorld>.Instance;

// Hide extracted Players
foreach (var profileId in coopGame.ExtractedPlayers)
{
var player = world.RegisteredPlayers.Find(x => x.ProfileId == profileId) as EFT.Player;
if (player == null)
continue;

if (!ExtractedProfilesSent.Contains(profileId))
{
ExtractedProfilesSent.Add(profileId);
if (player.Profile.Side == EPlayerSide.Savage)
{
player.Profile.EftStats.SessionCounters.AddDouble(0.01,
[
CounterTag.FenceStanding,
EFenceStandingSource.ExitStanding
]);
}
AkiBackendCommunicationCoop.PostLocalPlayerData(player
, new Dictionary<string, object>() { { "m", "Extraction" }, { "Extracted", true } }
);
}

if (player.ActiveHealthController != null)
{
if (!player.ActiveHealthController.MetabolismDisabled)
{
player.ActiveHealthController.AddDamageMultiplier(0);
player.ActiveHealthController.SetDamageCoeff(0);
player.ActiveHealthController.DisableMetabolism();
player.ActiveHealthController.PauseAllEffects();

//player.SwitchRenderer(false);

// TODO: Currently. Destroying your own Player just breaks the game and it appears to be "frozen". Need to learn a new way to do a FreeCam!
//if (Singleton<GameWorld>.Instance.MainPlayer.ProfileId != profileId)
// Destroy(player);
}
}
//force close all screens to disallow looting open crates after extract
if (profileId == world.MainPlayer.ProfileId)
{
ScreenManager instance = ScreenManager.Instance;
instance.CloseAllScreensForced();
}

PlayerUtils.MakeVisible(player, false);
}

// Add players who have joined to the AI Enemy Lists
var botController = (BotsController)ReflectionHelpers.GetFieldFromTypeByFieldType(typeof(BaseLocalGame<GamePlayerOwner>), typeof(BotsController)).GetValue(Singleton<ISITGame>.Instance);
if (botController != null)
Expand Down Expand Up @@ -552,8 +456,6 @@ private void ProcessOtherModsSpawnedPlayers()
}
}

private HashSet<string> ExtractedProfilesSent = new();

void OnDestroy()
{
StayInTarkovHelperConstants.Logger.LogDebug($"CoopGameComponent:OnDestroy");
Expand All @@ -571,9 +473,6 @@ void OnDestroy()
Instance = null;
}

TimeSpan LateUpdateSpan = TimeSpan.Zero;
Stopwatch swActionPackets { get; } = new Stopwatch();
bool PerformanceCheck_ActionPackets { get; set; } = false;
public bool RequestQuitGame { get; set; }
int ForceQuitGamePressed = 0;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using BepInEx.Logging;
using Comfort.Common;
using EFT.Counters;
using EFT;
using EFT.Interactive;
using HarmonyLib.Tools;
using StayInTarkov.Coop.Players;
using StayInTarkov.Coop.SITGameModes;
using StayInTarkov.Coop.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using StayInTarkov.Networking;
using StayInTarkov.Coop.NetworkPacket.Raid;

namespace StayInTarkov.Coop.Components.CoopGameComponents
{
public sealed class SITGameExtractionComponent : MonoBehaviour
{
ManualLogSource Logger { get; set; }
HashSet<string> ExtractedProfilesSent {get;set;} = new HashSet<string>();


void Awake()
{
Logger = BepInEx.Logging.Logger.CreateLogSource(nameof(SITGameExtractionComponent));
}

void Update()
{
if (!Singleton<ISITGame>.Instantiated)
return;

if (!Singleton<GameWorld>.Instantiated)
return;

ProcessExtractingPlayers();
ProcessExtractionRequirements();
HideExtractedPlayers();

}

private void HideExtractedPlayers()
{
var world = Singleton<GameWorld>.Instance;
var gameInstance = Singleton<ISITGame>.Instance;

// Hide extracted Players
foreach (var profileId in gameInstance.ExtractedPlayers)
{
var player = world.RegisteredPlayers.Find(x => x.ProfileId == profileId) as EFT.Player;
if (player == null)
continue;

if (!ExtractedProfilesSent.Contains(profileId))
{
ExtractedProfilesSent.Add(profileId);
if (player.Profile.Side == EPlayerSide.Savage)
{
player.Profile.EftStats.SessionCounters.AddDouble(0.01,
[
CounterTag.FenceStanding,
EFenceStandingSource.ExitStanding
]);
}
// Send the Extracted Packet to other Clients
GameClient.SendData(new ExtractedPlayerPacket(profileId).Serialize());
}

if (player.ActiveHealthController != null)
{
if (!player.ActiveHealthController.MetabolismDisabled)
{
player.ActiveHealthController.AddDamageMultiplier(0);
player.ActiveHealthController.SetDamageCoeff(0);
player.ActiveHealthController.DisableMetabolism();
player.ActiveHealthController.PauseAllEffects();
}
}

//force close all screens to disallow looting open crates after extract
if (profileId == world.MainPlayer.ProfileId)
{
ScreenManager instance = ScreenManager.Instance;
instance.CloseAllScreensForced();
}

PlayerUtils.MakeVisible(player, false);
}
}

private void ProcessExtractionRequirements()
{
var gameInstance = Singleton<ISITGame>.Instance;
// Trigger all countdown exfils (e.g. car), clients are responsible for their own extract
// since exfilpoint.Entered is local because of collision logic being local
// we start from the end because we remove as we go in `CoopSITGame.ExfiltrationPoint_OnStatusChanged`
for (int i = gameInstance.EnabledCountdownExfils.Count - 1; i >= 0; i--)
{
var ep = gameInstance.EnabledCountdownExfils[i];
if (gameInstance.PastTime - ep.ExfiltrationStartTime >= ep.Settings.ExfiltrationTime)
{
var game = Singleton<ISITGame>.Instance;
foreach (var player in ep.Entered)
{
var hasUnmetRequirements = ep.UnmetRequirements(player).Any();
if (player != null && player.HealthController.IsAlive && !hasUnmetRequirements)
{
game.ExtractingPlayers.Remove(player.ProfileId);
game.ExtractedPlayers.Add(player.ProfileId);
}
}
ep.SetStatusLogged(ep.Reusable ? EExfiltrationStatus.UncompleteRequirements : EExfiltrationStatus.NotPresent, nameof(ProcessExtractionRequirements));
}
}
}

private void ProcessExtractingPlayers()
{
var gameInstance = Singleton<ISITGame>.Instance;
var playersToExtract = new HashSet<string>();
foreach (var exfilPlayer in gameInstance.ExtractingPlayers)
{
var exfilTime = new TimeSpan(0, 0, (int)exfilPlayer.Value.Item1);
var timeInExfil = new TimeSpan(DateTime.Now.Ticks - exfilPlayer.Value.Item2);
if (timeInExfil >= exfilTime)
{
if (!playersToExtract.Contains(exfilPlayer.Key))
{
#if DEBUG
Logger.LogDebug(exfilPlayer.Key + " should extract");
#endif
playersToExtract.Add(exfilPlayer.Key);
}
}
#if DEBUG
else
{
Logger.LogDebug(exfilPlayer.Key + " extracting " + timeInExfil);
}
#endif
}

foreach (var player in playersToExtract)
{
gameInstance.ExtractingPlayers.Remove(player);
gameInstance.ExtractedPlayers.Add(player);
}
}
}
}
33 changes: 33 additions & 0 deletions Source/Coop/NetworkPacket/Raid/ExtractedPlayerPacket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Comfort.Common;
using EFT;
using StayInTarkov.Coop.NetworkPacket.Player;
using StayInTarkov.Coop.Players;
using StayInTarkov.Coop.SITGameModes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StayInTarkov.Coop.NetworkPacket.Raid
{
public sealed class ExtractedPlayerPacket : BasePlayerPacket
{
public ExtractedPlayerPacket() : base("", nameof(ExtractedPlayerPacket))
{
}

public ExtractedPlayerPacket(string profileId) : base(profileId, nameof(ExtractedPlayerPacket))
{
this.ProfileId = profileId;
}

protected override void Process(CoopPlayerClient client)
{
StayInTarkovHelperConstants.Logger.LogDebug($"{nameof(ExtractedPlayerPacket)}:Process({client.ProfileId})");
var gameInstance = Singleton<ISITGame>.Instance;
if (!gameInstance.ExtractedPlayers.Contains(client.ProfileId))
gameInstance.ExtractedPlayers.Add(client.ProfileId);
}
}
}
21 changes: 16 additions & 5 deletions Source/Coop/SITGameModes/CoopSITGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
Expand Down Expand Up @@ -209,9 +210,11 @@ InputTree inputTree

return coopGame;
}

void OnDestroy()
{
Logger.LogDebug("OnDestroy()");
Singleton<GameWorld>.Instance.AfterGameStarted -= Instance_AfterGameStarted;

Comfort.Common.Singleton<ISITGame>.TryRelease(this);
}
Expand Down Expand Up @@ -1193,9 +1196,6 @@ private void ExfiltrationPoint_OnCancelExtraction(ExfiltrationPoint point, EFT.P

private void ExfiltrationPoint_OnStartExtraction(ExfiltrationPoint point, EFT.Player player)
{
if (!player.IsYourPlayer)
return;

Logger.LogDebug($"{nameof(ExfiltrationPoint_OnStartExtraction)} {point.Settings.Name} {point.Status} {point.Settings.ExfiltrationTime}");
bool playerHasMetRequirements = !point.UnmetRequirements(player).Any();
if (!ExtractingPlayers.ContainsKey(player.ProfileId) && !ExtractedPlayers.Contains(player.ProfileId))
Expand All @@ -1204,8 +1204,12 @@ private void ExfiltrationPoint_OnStartExtraction(ExfiltrationPoint point, EFT.Pl
Logger.LogDebug($"Added {player.ProfileId} to {nameof(ExtractingPlayers)}");
}

MyExitLocation = point.Settings.Name;
MyExitStatus = ExitStatus.Survived;
// Setup MyExitLocation and MyExitStatus only if this is My Player
if (player.IsYourPlayer)
{
MyExitLocation = point.Settings.Name;
MyExitStatus = ExitStatus.Survived;
}
}

private void ExfiltrationPoint_OnStatusChanged(ExfiltrationPoint point, EExfiltrationStatus prevStatus)
Expand Down Expand Up @@ -1446,6 +1450,7 @@ private IEnumerator DisposingCo()
public BossLocationSpawn[] BossWaves { get; private set; }
public int ReadyPlayers { get; set; }
public bool HostReady { get; set; }
public bool GameWorldStarted { get; set; }

private NonWavesSpawnScenario nonWavesSpawnScenario_0;

Expand All @@ -1462,6 +1467,7 @@ public async Task Run(BotControllerSettings botsSettings, string backendUrl, Inv
{
Logger.LogDebug(nameof(Run));

Singleton<GameWorld>.Instance.AfterGameStarted += Instance_AfterGameStarted;
base.Status = GameStatus.Running;
UnityEngine.Random.InitState((int)DateTime.UtcNow.Ticks);
LocationSettingsClass.Location location;
Expand Down Expand Up @@ -1500,6 +1506,11 @@ public async Task Run(BotControllerSettings botsSettings, string backendUrl, Inv
method_5(botsSettings, SpawnSystem, runCallback);
}

private void Instance_AfterGameStarted()
{
GameWorldStarted = true;
}

class PlayerLoopSystemType
{
public PlayerLoopSystemType()
Expand Down
Loading

0 comments on commit 6343f59

Please sign in to comment.