first commit
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
|
||||
public interface IInitialSyncProcessor<in TPacket> where TPacket : Packet
|
||||
{
|
||||
HashSet<Type> DependentProcessors { get; }
|
||||
|
||||
IEnumerator Process(TPacket packet, WaitScreen.ManualWaitItem waitScreenItem);
|
||||
}
|
||||
|
||||
public interface IInitialSyncProcessor : IInitialSyncProcessor<InitialPlayerSync>
|
||||
{
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
|
||||
public abstract class InitialSyncProcessor : IInitialSyncProcessor
|
||||
{
|
||||
public virtual List<Func<InitialPlayerSync, IEnumerator>> Steps { get; } = new();
|
||||
public virtual HashSet<Type> DependentProcessors { get; } = new();
|
||||
|
||||
public virtual IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualWaitItem waitScreenItem)
|
||||
{
|
||||
for (int i = 0; i < Steps.Count; i++)
|
||||
{
|
||||
yield return Steps[i](packet);
|
||||
waitScreenItem.SetProgress((float)i / Steps.Count);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDependency<TDependency>() where TDependency : IInitialSyncProcessor
|
||||
{
|
||||
DependentProcessors.Add(typeof(TDependency));
|
||||
}
|
||||
|
||||
public void AddStep(Func<InitialPlayerSync, IEnumerator> step)
|
||||
{
|
||||
Steps.Add(step);
|
||||
}
|
||||
|
||||
public void AddStep(Action<InitialPlayerSync> step)
|
||||
{
|
||||
Steps.Add(sync =>
|
||||
{
|
||||
step(sync);
|
||||
return Array.Empty<object>().GetEnumerator();
|
||||
});
|
||||
}
|
||||
|
||||
public void AddStep(Func<IEnumerator> step)
|
||||
{
|
||||
Steps.Add(_ => step());
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.NetworkingLayer.LiteNetLib;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxClient.GameLogic.Settings;
|
||||
using NitroxClient.MonoBehaviours.Gui.Modals;
|
||||
using NitroxModel.Networking;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync;
|
||||
|
||||
public class ClockSyncInitialSyncProcessor : InitialSyncProcessor
|
||||
{
|
||||
private readonly TimeManager timeManager;
|
||||
private readonly NtpSyncer ntpSyncer;
|
||||
private readonly LiteNetLibClient liteNetLibClient;
|
||||
|
||||
public ClockSyncInitialSyncProcessor(TimeManager timeManager, NtpSyncer ntpSyncer, IClient client)
|
||||
{
|
||||
this.timeManager = timeManager;
|
||||
this.ntpSyncer = ntpSyncer;
|
||||
liteNetLibClient = (LiteNetLibClient)client;
|
||||
|
||||
AddStep(initialSync => NTPSync(initialSync.TimeData.TimePacket));
|
||||
}
|
||||
|
||||
public IEnumerator NTPSync(TimeChange timeData)
|
||||
{
|
||||
timeManager.SetServerCorrectionData(timeData.OnlineMode, timeData.UtcCorrectionTicks);
|
||||
|
||||
ntpSyncer.Setup(true);
|
||||
ntpSyncer.RequestNtpService();
|
||||
|
||||
yield return new WaitUntil(() => ntpSyncer.Finished);
|
||||
|
||||
if (ntpSyncer.OnlineMode)
|
||||
{
|
||||
timeManager.SetClientCorrectionData(true, ntpSyncer.CorrectionOffset);
|
||||
// If server AND client are in online mode, we have everything we need
|
||||
if (timeData.OnlineMode)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Warn($"Both client ({(ntpSyncer.OnlineMode ? "ONLINE" : "OFFLINE")}) and server ({(timeData.OnlineMode ? "ONLINE" : "OFFLINE")}) aren't in ONLINE mode. Falling back to {nameof(ClockSyncProcedure)}");
|
||||
|
||||
yield return GetAveragePing();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Procedure to calculate an average time delta with the server
|
||||
/// </summary>
|
||||
private IEnumerator GetAveragePing()
|
||||
{
|
||||
int procedureDuration = (int)NitroxPrefs.OfflineClockSyncDuration.Value; // seconds
|
||||
using ClockSyncProcedure clockSyncProcedure = ClockSyncProcedure.Start(liteNetLibClient, procedureDuration);
|
||||
yield return new WaitForSecondsRealtime(procedureDuration);
|
||||
bool success = clockSyncProcedure.TryGetSafeAverageRTD(out long remoteTimeDelta);
|
||||
|
||||
Log.Info($"[success: {success}] calculated RTD: {remoteTimeDelta}");
|
||||
timeManager.SetCorrectionDelta(remoteTimeDelta);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
yield return Modal.Get<InfoModal>().ShowAsync("Clock desync fixer failed. Ensure both you and the server are connected to the internet. Or try increasing the \"Offline Clock Sync Duration\" value in the settings, and restart your game.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NitroxClient.Communication;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic.Helper;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync;
|
||||
|
||||
public sealed class EquippedItemInitialSyncProcessor : InitialSyncProcessor
|
||||
{
|
||||
public EquippedItemInitialSyncProcessor()
|
||||
{
|
||||
AddDependency<PlayerInitialSyncProcessor>();
|
||||
AddDependency<RemotePlayerInitialSyncProcessor>();
|
||||
AddDependency<GlobalRootInitialSyncProcessor>();
|
||||
}
|
||||
|
||||
public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualWaitItem waitScreenItem)
|
||||
{
|
||||
int totalEquippedItemsDone = 0;
|
||||
|
||||
using (PacketSuppressor<EntitySpawnedByClient>.Suppress())
|
||||
{
|
||||
foreach (KeyValuePair<string, NitroxId> equippedItem in packet.EquippedItems)
|
||||
{
|
||||
string slot = equippedItem.Key;
|
||||
NitroxId id = equippedItem.Value;
|
||||
|
||||
waitScreenItem.SetProgress(totalEquippedItemsDone, packet.EquippedItems.Count);
|
||||
|
||||
GameObject gameObject = NitroxEntity.RequireObjectFrom(id);
|
||||
Pickupable pickupable = gameObject.RequireComponent<Pickupable>();
|
||||
|
||||
GameObject player = Player.mainObject;
|
||||
Optional<Equipment> opEquipment = EquipmentHelper.FindEquipmentComponent(player);
|
||||
|
||||
if (opEquipment.HasValue)
|
||||
{
|
||||
Equipment equipment = opEquipment.Value;
|
||||
InventoryItem inventoryItem = new(pickupable);
|
||||
inventoryItem.container = equipment;
|
||||
inventoryItem.item.Reparent(equipment.tr);
|
||||
|
||||
Dictionary<string, InventoryItem> itemsBySlot = equipment.equipment;
|
||||
itemsBySlot[slot] = inventoryItem;
|
||||
|
||||
equipment.UpdateCount(pickupable.GetTechType(), true);
|
||||
Equipment.SendEquipmentEvent(pickupable, 0, player, slot);
|
||||
equipment.NotifyEquip(slot, inventoryItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info($"Could not find equipment type for {gameObject.name}");
|
||||
}
|
||||
|
||||
totalEquippedItemsDone++;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Info($"Recieved initial sync with {totalEquippedItemsDone} pieces of equipped items");
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.MonoBehaviours.Cyclops;
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync;
|
||||
|
||||
/// <summary>
|
||||
/// Makes sure players can be spawned in entities in the global root (such as vehicles/escape pod).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This allows for:<br/>
|
||||
/// - vehicles to use equipment
|
||||
/// - other players to be set as drivers of some vehicle
|
||||
/// </remarks>
|
||||
public sealed class GlobalRootInitialSyncProcessor : InitialSyncProcessor
|
||||
{
|
||||
private readonly Entities entities;
|
||||
private readonly Vehicles vehicles;
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly BulletManager bulletManager;
|
||||
|
||||
public GlobalRootInitialSyncProcessor(Entities entities, Vehicles vehicles, PlayerManager playerManager, BulletManager bulletManager)
|
||||
{
|
||||
this.entities = entities;
|
||||
this.vehicles = vehicles;
|
||||
this.playerManager = playerManager;
|
||||
this.bulletManager = bulletManager;
|
||||
|
||||
// As we migrate systems over to entities, we want to ensure the required components are in place to spawn these entities.
|
||||
// For example, migrating inventories to the entity system requires players are spawned in the world before we try to add
|
||||
// inventory items to them. Eventually, all of the below processors will become entities on their own
|
||||
AddDependency<PlayerInitialSyncProcessor>();
|
||||
AddDependency<RemotePlayerInitialSyncProcessor>();
|
||||
AddDependency<StoryGoalInitialSyncProcessor>();
|
||||
|
||||
AddStep(WorldSettledForBuildings);
|
||||
AddStep(SpawnEntities);
|
||||
AddStep(RestoreDrivers);
|
||||
}
|
||||
|
||||
public IEnumerator WorldSettledForBuildings(InitialPlayerSync packet)
|
||||
{
|
||||
yield return new WaitUntil(LargeWorldStreamer.main.IsWorldSettled);
|
||||
// Make sure all building-related prefabs are fully loaded (happen to bug when launching multiple clients locally)
|
||||
yield return Base.InitializeAsync();
|
||||
yield return BaseGhost.InitializeAsync();
|
||||
yield return BaseDeconstructable.InitializeAsync();
|
||||
yield return VirtualCyclops.InitializeConstructablesCache();
|
||||
yield return bulletManager.Initialize();
|
||||
|
||||
BuildingHandler.Main.InitializeOperations(packet.BuildOperationIds);
|
||||
}
|
||||
|
||||
public IEnumerator SpawnEntities(InitialPlayerSync packet)
|
||||
{
|
||||
Log.Info($"Received initial sync packet with {packet.GlobalRootEntities.Count} global root entities");
|
||||
yield return entities.SpawnBatchAsync(packet.GlobalRootEntities);
|
||||
}
|
||||
|
||||
public void RestoreDrivers(InitialPlayerSync packet)
|
||||
{
|
||||
// At this step, vehicles have been spawned already (by SpawnEntities)
|
||||
foreach (PlayerContext playerContext in packet.OtherPlayers)
|
||||
{
|
||||
if (playerContext.DrivingVehicle != null)
|
||||
{
|
||||
Log.Info($"Restoring driver state of {playerContext.PlayerName} in {playerContext.DrivingVehicle}");
|
||||
vehicles.SetOnPilotMode(playerContext.DrivingVehicle, playerContext.PlayerId, true);
|
||||
if (playerManager.TryFind(playerContext.PlayerId, out RemotePlayer remotePlayer))
|
||||
{
|
||||
// As remote players are still driving, they aren't updating their IsUnderwater state so AnimationSender.Update
|
||||
// isn't going to send a packet. Therefore we need to set this by hand
|
||||
remotePlayer.UpdateAnimationAndCollider(AnimChangeType.UNDERWATER, AnimChangeState.OFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NitroxClient.Communication;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync;
|
||||
|
||||
public sealed class PdaInitialSyncProcessor : InitialSyncProcessor
|
||||
{
|
||||
public PdaInitialSyncProcessor()
|
||||
{
|
||||
AddDependency<ClockSyncInitialSyncProcessor>();
|
||||
}
|
||||
|
||||
// The steps are ordered like their call order in Player.OnProtoDeserialize
|
||||
public override List<Func<InitialPlayerSync, IEnumerator>> Steps { get; } =
|
||||
[
|
||||
RestoreKnownTech,
|
||||
RestorePDALog,
|
||||
RestoreEncyclopediaEntries,
|
||||
RestorePDAScanner
|
||||
];
|
||||
|
||||
private static IEnumerator RestoreKnownTech(InitialPlayerSync packet)
|
||||
{
|
||||
List<TechType> knownTech = packet.PDAData.KnownTechTypes.Select(techType => techType.ToUnity()).ToList();
|
||||
HashSet<TechType> analyzedTech = new(packet.PDAData.AnalyzedTechTypes.Select(techType => techType.ToUnity()));
|
||||
Log.Info($"Received initial sync packet with {knownTech.Count} KnownTech.knownTech types and {analyzedTech.Count} KnownTech.analyzedTech types.");
|
||||
|
||||
using (PacketSuppressor<KnownTechEntryAdd>.Suppress())
|
||||
{
|
||||
KnownTech.Deserialize(knownTech, analyzedTech);
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
private static IEnumerator RestorePDALog(InitialPlayerSync packet)
|
||||
{
|
||||
List<PDALogEntry> logEntries = packet.PDAData.PDALogEntries;
|
||||
Log.Info($"Received initial sync packet with {logEntries.Count} pda log entries");
|
||||
|
||||
using (PacketSuppressor<PDALogEntryAdd>.Suppress())
|
||||
{
|
||||
// We just need the timestamp and the key because everything else is provided by PDALog.InitDataForEntries
|
||||
PDALog.Deserialize(logEntries.ToDictionary(m => m.Key, m => new PDALog.Entry() { timestamp = m.Timestamp }));
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
private static IEnumerator RestoreEncyclopediaEntries(InitialPlayerSync packet)
|
||||
{
|
||||
List<string> entries = packet.PDAData.EncyclopediaEntries;
|
||||
Log.Info($"Received initial sync packet with {entries.Count} encyclopedia entries");
|
||||
|
||||
using (PacketSuppressor<PDAEncyclopediaEntryAdd>.Suppress())
|
||||
{
|
||||
// We don't do as in PDAEncyclopedia.Deserialize because we don't persist the entry's fields which are useless
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
PDAEncyclopedia.Add(entry, false);
|
||||
}
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
private static IEnumerator RestorePDAScanner(InitialPlayerSync packet)
|
||||
{
|
||||
InitialPDAData pdaData = packet.PDAData;
|
||||
|
||||
PDAScanner.Data data = new()
|
||||
{
|
||||
fragments = pdaData.ScannerFragments.ToDictionary(m => m.ToString(), m => 1f),
|
||||
partial = pdaData.ScannerPartial.Select(entry => entry.ToUnity()).ToList(),
|
||||
complete = new HashSet<TechType>(pdaData.ScannerComplete.Select(techType => techType.ToUnity()))
|
||||
};
|
||||
PDAScanner.Deserialize(data);
|
||||
yield break;
|
||||
}
|
||||
}
|
162
NitroxClient/GameLogic/InitialSync/PlayerInitialSyncProcessor.cs
Normal file
162
NitroxClient/GameLogic/InitialSync/PlayerInitialSyncProcessor.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Server;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync;
|
||||
|
||||
/// <summary>
|
||||
/// Makes sure the player is configured.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This allows the player to:<br/>
|
||||
/// - use equipment
|
||||
/// </remarks>
|
||||
public sealed class PlayerInitialSyncProcessor : InitialSyncProcessor
|
||||
{
|
||||
private readonly Items item;
|
||||
private readonly ItemContainers itemContainers;
|
||||
private readonly LocalPlayer localPlayer;
|
||||
|
||||
public PlayerInitialSyncProcessor(Items item, ItemContainers itemContainers, LocalPlayer localPlayer)
|
||||
{
|
||||
this.item = item;
|
||||
this.itemContainers = itemContainers;
|
||||
this.localPlayer = localPlayer;
|
||||
|
||||
AddStep(sync => SetupEscapePod(sync.FirstTimeConnecting));
|
||||
AddStep(sync => SetPlayerPermissions(sync.Permissions));
|
||||
AddStep(sync => SetPlayerIntroCinematicMode(sync.IntroCinematicMode));
|
||||
AddStep(sync => SetPlayerGameObjectId(sync.PlayerGameObjectId));
|
||||
AddStep(sync => AddStartingItemsToPlayer(sync.FirstTimeConnecting));
|
||||
AddStep(sync => SetPlayerStats(sync.PlayerStatsData));
|
||||
AddStep(sync => SetPlayerGameMode(sync.GameMode));
|
||||
AddStep(sync => ApplySettings(sync.KeepInventoryOnDeath, sync.SessionSettings.FastHatch, sync.SessionSettings.FastGrow));
|
||||
}
|
||||
|
||||
private void SetPlayerPermissions(Perms permissions)
|
||||
{
|
||||
localPlayer.Permissions = permissions;
|
||||
}
|
||||
|
||||
private void SetPlayerIntroCinematicMode(IntroCinematicMode introCinematicMode)
|
||||
{
|
||||
if (localPlayer.IntroCinematicMode < introCinematicMode)
|
||||
{
|
||||
localPlayer.IntroCinematicMode = introCinematicMode;
|
||||
Log.Info($"Received initial sync player IntroCinematicMode: {introCinematicMode}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPlayerGameObjectId(NitroxId id)
|
||||
{
|
||||
EcoTarget playerEcoTarget = Player.mainObject.AddComponent<EcoTarget>();
|
||||
playerEcoTarget.SetTargetType(RemotePlayer.PLAYER_ECO_TARGET_TYPE);
|
||||
|
||||
NitroxEntity.SetNewId(Player.mainObject, id);
|
||||
Log.Info($"Received initial sync player GameObject Id: {id}");
|
||||
}
|
||||
|
||||
private void SetupEscapePod(bool firstTimeConnecting)
|
||||
{
|
||||
EscapePod escapePod = EscapePod.main;
|
||||
if (escapePod)
|
||||
{
|
||||
Log.Info($"Setting up escape pod, FirstTimeConnecting: {firstTimeConnecting}");
|
||||
|
||||
escapePod.bottomHatchUsed = !firstTimeConnecting;
|
||||
escapePod.topHatchUsed = !firstTimeConnecting;
|
||||
|
||||
// Call code we suppressed inside EscapePodFirstUseCinematicsController_OnSceneObjectsLoaded_Patch
|
||||
EscapePodFirstUseCinematicsController cinematicController = escapePod.GetComponentInChildren<EscapePodFirstUseCinematicsController>(true);
|
||||
if (cinematicController)
|
||||
{
|
||||
cinematicController.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator AddStartingItemsToPlayer(bool firstTimeConnecting)
|
||||
{
|
||||
if (firstTimeConnecting)
|
||||
{
|
||||
if (!Player.main.TryGetIdOrWarn(out NitroxId localPlayerId))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (TechType techType in LootSpawner.main.GetEscapePodStorageTechTypes())
|
||||
{
|
||||
TaskResult<GameObject> result = new();
|
||||
yield return CraftData.InstantiateFromPrefabAsync(techType, result);
|
||||
GameObject gameObject = result.Get();
|
||||
Pickupable pickupable = gameObject.GetComponent<Pickupable>();
|
||||
pickupable.Initialize();
|
||||
|
||||
item.PickedUp(gameObject, techType);
|
||||
itemContainers.AddItem(gameObject, localPlayerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPlayerStats(PlayerStatsData statsData)
|
||||
{
|
||||
if (statsData != null)
|
||||
{
|
||||
Player.main.oxygenMgr.AddOxygen(statsData.Oxygen);
|
||||
// Spawning a player with 0 health makes them invincible so we'd rather set it to 1 HP
|
||||
Player.main.liveMixin.health = Mathf.Max(1f, statsData.Health);
|
||||
Survival survivalComponent = Player.main.GetComponent<Survival>();
|
||||
survivalComponent.food = statsData.Food;
|
||||
survivalComponent.water = statsData.Water;
|
||||
Player.main.infectedMixin.SetInfectedAmount(statsData.InfectionAmount);
|
||||
|
||||
//If InfectionAmount is at least 1f then the infection reveal should have happened already.
|
||||
//If InfectionAmount is below 1f then the reveal has not.
|
||||
if (statsData.InfectionAmount >= 1f)
|
||||
{
|
||||
Player.main.infectionRevealed = true;
|
||||
}
|
||||
|
||||
// We need to make the player invincible before he finishes loading because in some cases he will eventually die before loading
|
||||
Player.main.liveMixin.invincible = true;
|
||||
Player.main.FreezeStats();
|
||||
}
|
||||
|
||||
// We need to start it at least once for everything that's in the PDA to load
|
||||
Player.main.GetPDA().Open(PDATab.Inventory);
|
||||
Player.main.GetPDA().Close();
|
||||
}
|
||||
|
||||
private static void SetPlayerGameMode(NitroxGameMode gameMode)
|
||||
{
|
||||
Log.Info($"Received initial sync packet with gamemode {gameMode}");
|
||||
GameModeUtils.SetGameMode((GameModeOption)(int)gameMode, GameModeOption.None);
|
||||
}
|
||||
|
||||
private void ApplySettings(bool keepInventoryOnDeath, bool fastHatch, bool fastGrow)
|
||||
{
|
||||
localPlayer.KeepInventoryOnDeath = keepInventoryOnDeath;
|
||||
NoCostConsoleCommand.main.fastHatchCheat = fastHatch;
|
||||
NoCostConsoleCommand.main.fastGrowCheat = fastGrow;
|
||||
if (!fastHatch && !fastGrow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder cheatsEnabled = new("Cheats enabled:");
|
||||
if (fastHatch)
|
||||
{
|
||||
cheatsEnabled.Append(" fastHatch");
|
||||
}
|
||||
if (fastGrow)
|
||||
{
|
||||
cheatsEnabled.Append(" fastGrow");
|
||||
}
|
||||
Log.InGame(cheatsEnabled.ToString());
|
||||
}
|
||||
}
|
@@ -0,0 +1,120 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.Communication;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
using Math = System.Math;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync;
|
||||
|
||||
public sealed class PlayerPositionInitialSyncProcessor : InitialSyncProcessor
|
||||
{
|
||||
private static readonly Vector3 spawnRelativeToEscapePod = new(0.9f, 2.1f, 0);
|
||||
|
||||
public PlayerPositionInitialSyncProcessor()
|
||||
{
|
||||
AddDependency<PlayerInitialSyncProcessor>();
|
||||
AddDependency<GlobalRootInitialSyncProcessor>();
|
||||
}
|
||||
|
||||
public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualWaitItem waitScreenItem)
|
||||
{
|
||||
// We freeze the player so that he doesn't fall before the cells around him have loaded
|
||||
// Is disabled manually or in Terrain.WaitForWorldLoad()
|
||||
Player.main.cinematicModeActive = true;
|
||||
|
||||
AttachPlayerToEscapePod(packet.AssignedEscapePodId);
|
||||
|
||||
Vector3 position = packet.PlayerSpawnData.ToUnity();
|
||||
Quaternion rotation = packet.PlayerSpawnRotation.ToUnity();
|
||||
if (Math.Abs(position.x) < 0.0002 && Math.Abs(position.y) < 0.0002 && Math.Abs(position.z) < 0.0002)
|
||||
{
|
||||
position = Player.mainObject.transform.position;
|
||||
}
|
||||
Player.main.SetPosition(position, rotation);
|
||||
|
||||
// Player.ValidateEscapePod is setting currentEscapePod to null if player is not inside EscapePod
|
||||
using (PacketSuppressor<EscapePodChanged>.Suppress())
|
||||
{
|
||||
Player.main.ValidateEscapePod();
|
||||
}
|
||||
|
||||
Optional<NitroxId> subRootId = packet.PlayerSubRootId;
|
||||
if (!subRootId.HasValue)
|
||||
{
|
||||
yield return Terrain.WaitForWorldLoad();
|
||||
yield break;
|
||||
}
|
||||
|
||||
Optional<GameObject> sub = NitroxEntity.GetObjectFrom(subRootId.Value);
|
||||
if (!sub.HasValue)
|
||||
{
|
||||
Log.Error($"Could not spawn player into subroot with id: {subRootId.Value}");
|
||||
yield return Terrain.WaitForWorldLoad();
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (sub.Value.TryGetComponent(out SubRoot subRoot))
|
||||
{
|
||||
Player.main.SetCurrentSub(subRoot, true);
|
||||
if (subRoot.TryGetComponent(out Base @base))
|
||||
{
|
||||
SetupPlayerIfInWaterPark(@base);
|
||||
}
|
||||
}
|
||||
else if (sub.Value.GetComponent<EscapePod>())
|
||||
{
|
||||
Player.main.escapePod.Update(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("SubRootId-GameObject has no SubRoot or EscapePod component");
|
||||
}
|
||||
|
||||
// If the player's in a base/cyclops we don't need to wait for the world to load
|
||||
Player.main.UpdateIsUnderwater();
|
||||
Player.main.cinematicModeActive = false;
|
||||
}
|
||||
|
||||
private static void AttachPlayerToEscapePod(NitroxId escapePodId)
|
||||
{
|
||||
GameObject escapePod = NitroxEntity.RequireObjectFrom(escapePodId);
|
||||
|
||||
EscapePod.main.transform.position = escapePod.transform.position;
|
||||
EscapePod.main.playerSpawn.position = escapePod.transform.position + spawnRelativeToEscapePod;
|
||||
|
||||
Player.main.transform.position = EscapePod.main.playerSpawn.position;
|
||||
Player.main.transform.rotation = EscapePod.main.playerSpawn.rotation;
|
||||
|
||||
Player.main.currentEscapePod = escapePod.GetComponent<EscapePod>();
|
||||
}
|
||||
|
||||
private static void SetupPlayerIfInWaterPark(Base @base)
|
||||
{
|
||||
foreach (Transform baseChild in @base.transform)
|
||||
{
|
||||
if (baseChild.TryGetComponent(out WaterPark waterPark))
|
||||
{
|
||||
if (waterPark is LargeRoomWaterPark)
|
||||
{
|
||||
// LargeRoomWaterPark.VerifyPlayerWaterPark sets Player.main.currentWaterPark to the right value
|
||||
waterPark.VerifyPlayerWaterPark(Player.main);
|
||||
}
|
||||
else if (waterPark.IsPointInside(Player.main.transform.position))
|
||||
{
|
||||
Player.main.currentWaterPark = waterPark;
|
||||
}
|
||||
}
|
||||
|
||||
if (Player.main.currentWaterPark)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NitroxClient.Communication;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync;
|
||||
|
||||
public sealed class PlayerPreferencesInitialSyncProcessor : InitialSyncProcessor
|
||||
{
|
||||
public PlayerPreferencesInitialSyncProcessor()
|
||||
{
|
||||
// list of processors which may cause the spawn of Signal pings
|
||||
AddDependency<PlayerInitialSyncProcessor>();
|
||||
AddDependency<GlobalRootInitialSyncProcessor>();
|
||||
AddDependency<StoryGoalInitialSyncProcessor>();
|
||||
AddDependency<PdaInitialSyncProcessor>();
|
||||
AddDependency<RemotePlayerInitialSyncProcessor>();
|
||||
}
|
||||
|
||||
public override List<Func<InitialPlayerSync, IEnumerator>> Steps { get; } =
|
||||
[
|
||||
UpdatePins,
|
||||
UpdatePingInstancePreferences
|
||||
];
|
||||
|
||||
private static IEnumerator UpdatePins(InitialPlayerSync packet)
|
||||
{
|
||||
using (PacketSuppressor<RecipePinned>.Suppress())
|
||||
{
|
||||
PinManager.main.Deserialize(packet.Preferences.PinnedTechTypes.Select(techType => (TechType)techType).ToList());
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
private static IEnumerator UpdatePingInstancePreferences(InitialPlayerSync packet)
|
||||
{
|
||||
Dictionary<string, PingInstancePreference> pingPreferences = packet.Preferences.PingPreferences;
|
||||
void UpdateInstance(PingInstance instance)
|
||||
{
|
||||
ModifyPingInstanceIfPossible(instance, pingPreferences, () => UpdateInstance(instance));
|
||||
RefreshPingEntryInPDA(instance);
|
||||
}
|
||||
|
||||
PingManager.onAdd += UpdateInstance;
|
||||
UnityEngine.Object.FindObjectsOfType<PingInstance>().ForEach(UpdateInstance);
|
||||
yield break;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the given pingInstance if it has a specified preference
|
||||
/// </summary>
|
||||
private static void ModifyPingInstanceIfPossible(PingInstance pingInstance, Dictionary<string, PingInstancePreference> preferences, Action callback)
|
||||
{
|
||||
if (!TryGetKeyForPingInstance(pingInstance, out string pingKey, out bool isRemotePlayerPing, callback) ||
|
||||
!preferences.TryGetValue(pingKey, out PingInstancePreference preference))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (PacketSuppressor<SignalPingPreferenceChanged>.Suppress())
|
||||
{
|
||||
// We don't want to set the color for a remote player's signal
|
||||
if (!isRemotePlayerPing)
|
||||
{
|
||||
pingInstance.SetColor(preference.Color);
|
||||
}
|
||||
pingInstance.SetVisible(preference.Visible);
|
||||
}
|
||||
}
|
||||
|
||||
// Right after initial sync modifications, uGUI_PingEntry elements don't show their updated state
|
||||
private static void RefreshPingEntryInPDA(PingInstance pingInstance)
|
||||
{
|
||||
if (!uGUI_PDA.main || !uGUI_PDA.main.tabs.TryGetValue(PDATab.Ping, out uGUI_PDATab pdaTab))
|
||||
{
|
||||
return;
|
||||
}
|
||||
uGUI_PingTab pingTab = pdaTab as uGUI_PingTab;
|
||||
if (pingTab && pingTab.entries.TryGetValue(pingInstance.Id, out uGUI_PingEntry pingEntry))
|
||||
{
|
||||
pingEntry.SetColor(pingInstance.colorIndex);
|
||||
pingEntry.SetVisible(pingInstance.visible);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the identifier of a PingInstance depending on its type and container
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We need to differentiate three types of pings, the "normal pings" from objects that emit a signal, these objects generally contain a NitroxEntity
|
||||
/// Another type is Signal pings that are generated by the story events, they are located in the Global Root and don't contain a NitroxEntity, to be identified, they have another object: a SignalPing which contains a description key
|
||||
/// The last type possible is RemotePlayers' pings which are located in a GameObject that is 2 steps under the main object
|
||||
/// </remarks>
|
||||
public static bool TryGetKeyForPingInstance(PingInstance pingInstance, out string pingKey, out bool isRemotePlayerPing, Action failCallback = null)
|
||||
{
|
||||
isRemotePlayerPing = false;
|
||||
if (pingInstance.TryGetComponent(out SignalPing signalPing))
|
||||
{
|
||||
pingKey = signalPing.descriptionKey;
|
||||
// Sometimes, the SignalPing will not have loaded properly so we need to postpone the key detection
|
||||
if (pingKey == null)
|
||||
{
|
||||
pingInstance.StartCoroutine(DelayPingKeyDetection(failCallback));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (pingInstance.TryGetComponent(out NitroxEntity nitroxEntity))
|
||||
{
|
||||
pingKey = nitroxEntity.Id.ToString();
|
||||
return true;
|
||||
}
|
||||
if (pingInstance.transform.TryGetComponentInAscendance(2, out nitroxEntity))
|
||||
{
|
||||
pingKey = nitroxEntity.Id.ToString();
|
||||
isRemotePlayerPing = true;
|
||||
return true;
|
||||
}
|
||||
// Known issue for a ping named "xSignal(Clone)" that appears temporarily when another player joins
|
||||
if (pingInstance.name.Equals("xSignal(Clone)"))
|
||||
{
|
||||
pingKey = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.Warn($"Couldn't find PingInstance identifier for {pingInstance.name} under {pingInstance.transform.parent}");
|
||||
pingKey = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerator DelayPingKeyDetection(Action delayedAction)
|
||||
{
|
||||
yield return Yielders.WaitForHalfSecond;
|
||||
delayedAction?.Invoke();
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync;
|
||||
|
||||
public sealed class QuickSlotInitialSyncProcessor : InitialSyncProcessor
|
||||
{
|
||||
public QuickSlotInitialSyncProcessor()
|
||||
{
|
||||
AddDependency<PlayerInitialSyncProcessor>(); // the player needs to be configured before we can set quick slots.
|
||||
AddDependency<EquippedItemInitialSyncProcessor>(); // we need to have the items spawned into our inventory before we can quick slot them.
|
||||
}
|
||||
|
||||
public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualWaitItem waitScreenItem)
|
||||
{
|
||||
int nonEmptySlots = 0;
|
||||
|
||||
Dictionary<NitroxId, InventoryItem> inventoryItemsById = GetItemsById();
|
||||
|
||||
for (int i = 0; i < packet.QuickSlotsBindingIds.Length; i++)
|
||||
{
|
||||
waitScreenItem.SetProgress(i, packet.QuickSlotsBindingIds.Length);
|
||||
|
||||
Optional<NitroxId> opId = packet.QuickSlotsBindingIds[i];
|
||||
|
||||
if (opId.HasValue && inventoryItemsById.TryGetValue(opId.Value, out InventoryItem inventoryItem) )
|
||||
{
|
||||
Inventory.main.quickSlots.binding[i] = inventoryItem;
|
||||
Inventory.main.quickSlots.NotifyBind(i, state: true);
|
||||
nonEmptySlots++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unbind any default stuff from equipment addition.
|
||||
Inventory.main.quickSlots.Unbind(i);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Log.Info($"Received initial sync with {nonEmptySlots} quick slots populated with items");
|
||||
}
|
||||
|
||||
private Dictionary<NitroxId, InventoryItem> GetItemsById()
|
||||
{
|
||||
Dictionary<NitroxId, InventoryItem> itemsById = new();
|
||||
|
||||
foreach (InventoryItem inventoryItem in Inventory.main.container)
|
||||
{
|
||||
if (inventoryItem.item.TryGetIdOrWarn(out NitroxId itemId))
|
||||
{
|
||||
itemsById.Add(itemId, inventoryItem);
|
||||
}
|
||||
}
|
||||
|
||||
return itemsById;
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync;
|
||||
|
||||
/// <summary>
|
||||
/// Makes sure the remote player object is loaded.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This allows for the remote player to:<br/>
|
||||
/// - use equipment
|
||||
/// </remarks>
|
||||
public sealed class RemotePlayerInitialSyncProcessor : InitialSyncProcessor
|
||||
{
|
||||
private readonly PlayerManager remotePlayerManager;
|
||||
|
||||
public RemotePlayerInitialSyncProcessor(PlayerManager remotePlayerManager)
|
||||
{
|
||||
this.remotePlayerManager = remotePlayerManager;
|
||||
}
|
||||
|
||||
public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualWaitItem waitScreenItem)
|
||||
{
|
||||
int remotePlayersSynced = 0;
|
||||
|
||||
foreach (PlayerContext otherPlayer in packet.OtherPlayers)
|
||||
{
|
||||
waitScreenItem.SetProgress(remotePlayersSynced, packet.OtherPlayers.Count);
|
||||
|
||||
remotePlayerManager.Create(otherPlayer);
|
||||
|
||||
remotePlayersSynced++;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync;
|
||||
|
||||
public sealed class SimulationOwnershipInitialSyncProcessor : InitialSyncProcessor
|
||||
{
|
||||
private readonly SimulationOwnership simulationOwnership;
|
||||
|
||||
public SimulationOwnershipInitialSyncProcessor(SimulationOwnership simulationOwnership)
|
||||
{
|
||||
this.simulationOwnership = simulationOwnership;
|
||||
|
||||
AddDependency<GlobalRootInitialSyncProcessor>();
|
||||
}
|
||||
|
||||
public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualWaitItem waitScreenItem)
|
||||
{
|
||||
int entitiesSynced = 0;
|
||||
foreach (SimulatedEntity simulatedEntity in packet.InitialSimulationOwnerships)
|
||||
{
|
||||
simulationOwnership.TreatSimulatedEntity(simulatedEntity);
|
||||
|
||||
if (entitiesSynced++ % 5 == 0)
|
||||
{
|
||||
waitScreenItem.SetProgress(entitiesSynced, packet.InitialSimulationOwnerships.Count);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
}
|
@@ -0,0 +1,197 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using Story;
|
||||
|
||||
namespace NitroxClient.GameLogic.InitialSync;
|
||||
|
||||
public sealed class StoryGoalInitialSyncProcessor : InitialSyncProcessor
|
||||
{
|
||||
private readonly TimeManager timeManager;
|
||||
|
||||
public StoryGoalInitialSyncProcessor(TimeManager timeManager)
|
||||
{
|
||||
this.timeManager = timeManager;
|
||||
|
||||
AddStep(SetTimeData);
|
||||
AddStep(SetupStoryGoalManager);
|
||||
AddStep(SetupTrackers);
|
||||
AddStep(SetupAuroraAndSunbeam);
|
||||
AddStep(SetScheduledGoals);
|
||||
}
|
||||
|
||||
private static void SetupStoryGoalManager(InitialPlayerSync packet)
|
||||
{
|
||||
List<string> completedGoals = packet.StoryGoalData.CompletedGoals;
|
||||
List<string> radioQueue = packet.StoryGoalData.RadioQueue;
|
||||
Dictionary<string, float> personalGoals = packet.StoryGoalData.PersonalCompletedGoalsWithTimestamp;
|
||||
StoryGoalManager storyGoalManager = StoryGoalManager.main;
|
||||
|
||||
storyGoalManager.completedGoals.AddRange(completedGoals);
|
||||
|
||||
storyGoalManager.pendingRadioMessages.AddRange(radioQueue);
|
||||
storyGoalManager.PulsePendingMessages();
|
||||
|
||||
// Restore states of GoalManager and the (tutorial) arrow system
|
||||
foreach (KeyValuePair<string, float> entry in personalGoals)
|
||||
{
|
||||
Goal entryGoal = GoalManager.main.goals.Find(goal => goal.customGoalName.Equals(entry.Key));
|
||||
if (entryGoal != null)
|
||||
{
|
||||
entryGoal.SetTimeCompleted(entry.Value);
|
||||
}
|
||||
}
|
||||
GoalManager.main.completedGoalNames.AddRange(personalGoals.Keys);
|
||||
PlayerWorldArrows.main.completedCustomGoals.AddRange(personalGoals.Keys);
|
||||
|
||||
// Deactivate the current arrow if it was completed
|
||||
if (personalGoals.Any(goal => goal.Key.Equals(WorldArrowManager.main.currentGoalText)))
|
||||
{
|
||||
WorldArrowManager.main.DeactivateArrow();
|
||||
}
|
||||
|
||||
Log.Info($"""
|
||||
Received initial sync packet with:
|
||||
- Completed story goals : {completedGoals.Count}
|
||||
- Personal goals : {personalGoals.Count}
|
||||
- Radio queue : {radioQueue.Count}
|
||||
""");
|
||||
}
|
||||
|
||||
private static void SetupTrackers(InitialPlayerSync packet)
|
||||
{
|
||||
List<string> completedGoals = packet.StoryGoalData.CompletedGoals;
|
||||
StoryGoalManager storyGoalManager = StoryGoalManager.main;
|
||||
OnGoalUnlockTracker onGoalUnlockTracker = storyGoalManager.onGoalUnlockTracker;
|
||||
CompoundGoalTracker compoundGoalTracker = storyGoalManager.compoundGoalTracker;
|
||||
|
||||
// Initializing CompoundGoalTracker and OnGoalUnlockTracker again (with OnSceneObjectsLoaded) requires us to
|
||||
// we first clear what was done in the first iteration of OnSceneObjectsLoaded
|
||||
onGoalUnlockTracker.goalUnlocks.Clear();
|
||||
compoundGoalTracker.goals.Clear();
|
||||
// we force initialized to false so OnSceneObjectsLoaded actually does something
|
||||
storyGoalManager.initialized = false;
|
||||
storyGoalManager.OnSceneObjectsLoaded();
|
||||
|
||||
// Clean LocationGoalTracker, BiomeGoalTracker and ItemGoalTracker already completed goals
|
||||
storyGoalManager.locationGoalTracker.goals.RemoveAll(goal => completedGoals.Contains(goal.key));
|
||||
storyGoalManager.biomeGoalTracker.goals.RemoveAll(goal => completedGoals.Contains(goal.key));
|
||||
|
||||
List<TechType> techTypesToRemove = new();
|
||||
foreach (KeyValuePair<TechType, List<ItemGoal>> entry in storyGoalManager.itemGoalTracker.goals)
|
||||
{
|
||||
// Goals are all triggered at the same time but we don't know if some entries share certain goals
|
||||
if (entry.Value.All(goal => completedGoals.Contains(goal.key)))
|
||||
{
|
||||
techTypesToRemove.Add(entry.Key);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
techTypesToRemove.ForEach(techType => storyGoalManager.itemGoalTracker.goals.Remove(techType));
|
||||
|
||||
// OnGoalUnlock might trigger the creation of a signal which is later on set to invisible when getting close to it
|
||||
// the invisibility is managed by PingInstance_Set_Patches and is restored during PlayerPreferencesInitialSyncProcessor
|
||||
// So we still need to recreate the signals at every game launch
|
||||
|
||||
// To avoid having the SignalPing play its sound we just make its notification null while triggering it
|
||||
// (the sound is something like "coordinates added to the gps" or something)
|
||||
SignalPing prefabSignalPing = onGoalUnlockTracker.signalPrefab.GetComponent<SignalPing>();
|
||||
PDANotification pdaNotification = prefabSignalPing.vo;
|
||||
prefabSignalPing.vo = null;
|
||||
|
||||
foreach (OnGoalUnlock onGoalUnlock in onGoalUnlockTracker.unlockData.onGoalUnlocks)
|
||||
{
|
||||
if (completedGoals.Contains(onGoalUnlock.goal))
|
||||
{
|
||||
// Code adapted from OnGoalUnlock.Trigger
|
||||
foreach (UnlockSignalData unlockSignalData in onGoalUnlock.signals)
|
||||
{
|
||||
unlockSignalData.Trigger(onGoalUnlockTracker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recover the notification sound
|
||||
prefabSignalPing.vo = pdaNotification;
|
||||
}
|
||||
|
||||
// Must happen after CompletedGoals
|
||||
private static void SetupAuroraAndSunbeam(InitialPlayerSync packet)
|
||||
{
|
||||
TimeData timeData = packet.TimeData;
|
||||
|
||||
AuroraWarnings auroraWarnings = Player.mainObject.GetComponentInChildren<AuroraWarnings>(true);
|
||||
auroraWarnings.timeSerialized = DayNightCycle.main.timePassedAsFloat;
|
||||
auroraWarnings.OnProtoDeserialize(null);
|
||||
|
||||
CrashedShipExploder.main.version = 2;
|
||||
CrashedShipExploder.main.initialized = true;
|
||||
StoryManager.UpdateAuroraData(timeData.AuroraEventData);
|
||||
CrashedShipExploder.main.timeSerialized = DayNightCycle.main.timePassedAsFloat;
|
||||
CrashedShipExploder.main.OnProtoDeserialize(null);
|
||||
|
||||
// Sunbeam countdown is deducted from the scheduled goal PrecursorGunAimCheck
|
||||
NitroxScheduledGoal sunbeamCountdownGoal = packet.StoryGoalData.ScheduledGoals.Find(goal => string.Equals(goal.GoalKey, "PrecursorGunAimCheck", StringComparison.OrdinalIgnoreCase));
|
||||
if (sunbeamCountdownGoal != null)
|
||||
{
|
||||
StoryGoalCustomEventHandler.main.countdownActive = true;
|
||||
StoryGoalCustomEventHandler.main.countdownStartingTime = sunbeamCountdownGoal.TimeExecute - 2370;
|
||||
// See StoryGoalCustomEventHandler.endTime for calculation (endTime - 30 seconds)
|
||||
}
|
||||
}
|
||||
|
||||
// Must happen after CompletedGoals
|
||||
private static void SetScheduledGoals(InitialPlayerSync packet)
|
||||
{
|
||||
List<NitroxScheduledGoal> scheduledGoals = packet.StoryGoalData.ScheduledGoals;
|
||||
|
||||
// We don't want any scheduled goal we add now to be executed before initial sync has finished, else they might not get broadcasted
|
||||
StoryGoalScheduler.main.paused = true;
|
||||
Multiplayer.OnLoadingComplete += () => StoryGoalScheduler.main.paused = false;
|
||||
|
||||
foreach (NitroxScheduledGoal scheduledGoal in scheduledGoals)
|
||||
{
|
||||
// Clear duplicated goals that might have appeared during loading and before sync
|
||||
StoryGoalScheduler.main.schedule.RemoveAll(goal => goal.goalKey == scheduledGoal.GoalKey);
|
||||
|
||||
ScheduledGoal goal = new()
|
||||
{
|
||||
goalKey = scheduledGoal.GoalKey,
|
||||
goalType = (Story.GoalType)scheduledGoal.GoalType,
|
||||
timeExecute = scheduledGoal.TimeExecute,
|
||||
};
|
||||
if (!StoryGoalManager.main.completedGoals.Contains(goal.goalKey))
|
||||
{
|
||||
StoryGoalScheduler.main.schedule.Add(goal);
|
||||
}
|
||||
}
|
||||
|
||||
RefreshStoryWithLatestData();
|
||||
}
|
||||
|
||||
// Must happen after CompletedGoals
|
||||
private static void RefreshStoryWithLatestData()
|
||||
{
|
||||
// If those aren't set up yet, they'll initialize correctly in time
|
||||
// Else, we need to force them to acquire the right data
|
||||
if (StoryGoalCustomEventHandler.main)
|
||||
{
|
||||
StoryGoalCustomEventHandler.main.Awake();
|
||||
}
|
||||
if (PrecursorGunStoryEvents.main)
|
||||
{
|
||||
PrecursorGunStoryEvents.main.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTimeData(InitialPlayerSync packet)
|
||||
{
|
||||
timeManager.ProcessUpdate(packet.TimeData.TimePacket);
|
||||
timeManager.InitRealTimeElapsed(packet.TimeData.TimePacket.RealTimeElapsed, packet.TimeData.TimePacket.UpdateTime, packet.IsFirstPlayer);
|
||||
timeManager.AuroraRealExplosionTime = packet.TimeData.AuroraEventData.AuroraRealExplosionTime;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user