211 lines
8.4 KiB
C#
211 lines
8.4 KiB
C#
using System;
|
|
using NitroxModel.DataStructures.GameLogic;
|
|
using NitroxModel.Packets;
|
|
using NitroxServer.Helper;
|
|
using NitroxServer.GameLogic.Unlockables;
|
|
using NitroxModel.Helper;
|
|
using NitroxModel;
|
|
|
|
namespace NitroxServer.GameLogic;
|
|
|
|
/// <summary>
|
|
/// Keeps track of time and Aurora-related events.
|
|
/// </summary>
|
|
public class StoryManager : IDisposable
|
|
{
|
|
private readonly PlayerManager playerManager;
|
|
private readonly PDAStateData pdaStateData;
|
|
private readonly StoryGoalData storyGoalData;
|
|
private readonly TimeKeeper timeKeeper;
|
|
private readonly string seed;
|
|
|
|
/// <summary>
|
|
/// Time at which the Aurora explosion countdown will start (last warning is sent).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// It is required to calculate the time at which the Aurora warnings will be sent (along with <see cref="AuroraWarningTimeMs"/>, look into AuroraWarnings.cs and CrashedShipExploder.cs for more information).
|
|
/// </remarks>
|
|
public double AuroraCountdownTimeMs;
|
|
/// <summary>
|
|
/// Time at which the Aurora Events start (you start receiving warnings).
|
|
/// </summary>
|
|
public double AuroraWarningTimeMs;
|
|
|
|
/// <summary>
|
|
/// In seconds
|
|
/// </summary>
|
|
public double AuroraRealExplosionTime;
|
|
|
|
private double ElapsedMilliseconds => timeKeeper.ElapsedMilliseconds;
|
|
private double ElapsedSeconds => timeKeeper.ElapsedSeconds;
|
|
|
|
public StoryManager(PlayerManager playerManager, PDAStateData pdaStateData, StoryGoalData storyGoalData, TimeKeeper timeKeeper, string seed, double? auroraExplosionTime, double? auroraWarningTime, double? auroraRealExplosionTime)
|
|
{
|
|
this.playerManager = playerManager;
|
|
this.pdaStateData = pdaStateData;
|
|
this.storyGoalData = storyGoalData;
|
|
this.timeKeeper = timeKeeper;
|
|
this.seed = seed;
|
|
|
|
AuroraCountdownTimeMs = auroraExplosionTime ?? GenerateDeterministicAuroraTime(seed);
|
|
AuroraWarningTimeMs = auroraWarningTime ?? ElapsedMilliseconds;
|
|
// +27 is from CrashedShipExploder.IsExploded, -480 is from the default time (see TimeKeeper)
|
|
AuroraRealExplosionTime = auroraRealExplosionTime ?? AuroraCountdownTimeMs * 0.001 + 27 - TimeKeeper.DEFAULT_TIME;
|
|
|
|
timeKeeper.TimeSkipped += ReadjustAuroraRealExplosionTime;
|
|
}
|
|
|
|
public void ReadjustAuroraRealExplosionTime(double skipAmount)
|
|
{
|
|
// Readjust the aurora real explosion time when time skipping because it's based on in-game time
|
|
if (AuroraRealExplosionTime > timeKeeper.RealTimeElapsed)
|
|
{
|
|
double newTime = timeKeeper.RealTimeElapsed + skipAmount;
|
|
if (newTime > AuroraRealExplosionTime)
|
|
{
|
|
AuroraRealExplosionTime = timeKeeper.RealTimeElapsed;
|
|
}
|
|
else
|
|
{
|
|
AuroraRealExplosionTime -= skipAmount;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <param name="instantaneous">Whether we should make Aurora explode instantly or after a short countdown</param>
|
|
public void BroadcastExplodeAurora(bool instantaneous)
|
|
{
|
|
// Calculations from CrashedShipExploder.OnConsoleCommand_countdownship()
|
|
// We add 3 seconds to the cooldown (Subnautica adds only 1) so that players have enough time to receive the packet and process it
|
|
AuroraCountdownTimeMs = ElapsedMilliseconds + 3000;
|
|
AuroraWarningTimeMs = AuroraCountdownTimeMs;
|
|
AuroraRealExplosionTime = timeKeeper.RealTimeElapsed + 30; // 27 + 3
|
|
|
|
if (instantaneous)
|
|
{
|
|
// Calculations from CrashedShipExploder.OnConsoleCommand_explodeship()
|
|
// Removes 25 seconds to the countdown time, jumping to the exact moment of the explosion
|
|
AuroraCountdownTimeMs -= 25000;
|
|
// Is 1 second less than countdown time to have the game understand that we only want the explosion.
|
|
AuroraWarningTimeMs = AuroraCountdownTimeMs - 1000;
|
|
AuroraRealExplosionTime -= 25;
|
|
Log.Info("Aurora's explosion initiated");
|
|
}
|
|
else
|
|
{
|
|
Log.Info("Aurora's explosion countdown will start in 3 seconds");
|
|
}
|
|
|
|
playerManager.SendPacketToAllPlayers(new AuroraAndTimeUpdate(GetTimeData(), false));
|
|
}
|
|
|
|
public void BroadcastRestoreAurora()
|
|
{
|
|
AuroraWarningTimeMs = ElapsedMilliseconds;
|
|
AuroraCountdownTimeMs = GenerateDeterministicAuroraTime(seed);
|
|
// Current time + deltaTime before countdown + 27 seconds before explosion
|
|
AuroraRealExplosionTime = timeKeeper.RealTimeElapsed + (AuroraCountdownTimeMs - timeKeeper.ElapsedMilliseconds) * 0.001 + 27;
|
|
|
|
// We need to clear these entries from PdaLog and CompletedGoals to make sure that the client, when reconnecting, doesn't have false information
|
|
foreach (string eventKey in AuroraEventData.GoalNames)
|
|
{
|
|
pdaStateData.PdaLog.RemoveAll(entry => entry.Key == eventKey);
|
|
storyGoalData.CompletedGoals.Remove(eventKey);
|
|
}
|
|
|
|
playerManager.SendPacketToAllPlayers(new AuroraAndTimeUpdate(GetTimeData(), true));
|
|
Log.Info($"Restored Aurora, will explode again in {GetMinutesBeforeAuroraExplosion()} minutes");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate the time at Aurora's explosion countdown will begin.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Takes the current time into account.
|
|
/// </remarks>
|
|
private double GenerateDeterministicAuroraTime(string seed)
|
|
{
|
|
// Copied from CrashedShipExploder.SetExplodeTime() and changed from seconds to ms
|
|
DeterministicGenerator generator = new(seed, nameof(StoryManager));
|
|
return ElapsedMilliseconds + generator.NextDouble(2.3d, 4d) * 1200d * 1000d;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the already completed sunbeam events to come and broadcasts it to all players along with the rescheduling of the specified sunbeam event.
|
|
/// </summary>
|
|
public void StartSunbeamEvent(string sunbeamEventKey)
|
|
{
|
|
int beginIndex = PlaySunbeamEvent.SunbeamGoals.GetIndex(sunbeamEventKey);
|
|
if (beginIndex == -1)
|
|
{
|
|
Log.Error($"Couldn't find the corresponding sunbeam event in {nameof(PlaySunbeamEvent.SunbeamGoals)} for key {sunbeamEventKey}");
|
|
return;
|
|
}
|
|
for (int i = beginIndex; i < PlaySunbeamEvent.SunbeamGoals.Length; i++)
|
|
{
|
|
storyGoalData.CompletedGoals.Remove(PlaySunbeamEvent.SunbeamGoals[i]);
|
|
}
|
|
playerManager.SendPacketToAllPlayers(new PlaySunbeamEvent(sunbeamEventKey));
|
|
}
|
|
|
|
/// <returns>Either the time in before Aurora explodes or -1 if it has already exploded.</returns>
|
|
private double GetMinutesBeforeAuroraExplosion()
|
|
{
|
|
return AuroraCountdownTimeMs > ElapsedMilliseconds ? Math.Round((AuroraCountdownTimeMs - ElapsedMilliseconds) / 60000) : -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Makes a nice status of the Aurora events progress for the summary command.
|
|
/// </summary>
|
|
public string GetAuroraStateSummary()
|
|
{
|
|
double minutesBeforeExplosion = GetMinutesBeforeAuroraExplosion();
|
|
if (minutesBeforeExplosion < 0)
|
|
{
|
|
return "already exploded";
|
|
}
|
|
// Based on AuroraWarnings.Update calculations
|
|
// auroraWarningNumber is the amount of received Aurora warnings (there are 4 in total)
|
|
int auroraWarningNumber = 0;
|
|
if (ElapsedMilliseconds >= AuroraCountdownTimeMs)
|
|
{
|
|
auroraWarningNumber = 4;
|
|
}
|
|
else if (ElapsedMilliseconds >= Mathf.Lerp((float)AuroraWarningTimeMs, (float)AuroraCountdownTimeMs, 0.8f))
|
|
{
|
|
auroraWarningNumber = 3;
|
|
}
|
|
else if (ElapsedMilliseconds >= Mathf.Lerp((float)AuroraWarningTimeMs, (float)AuroraCountdownTimeMs, 0.5f))
|
|
{
|
|
auroraWarningNumber = 2;
|
|
}
|
|
else if (ElapsedMilliseconds >= Mathf.Lerp((float)AuroraWarningTimeMs, (float)AuroraCountdownTimeMs, 0.2f))
|
|
{
|
|
auroraWarningNumber = 1;
|
|
}
|
|
|
|
return $"explodes in {minutesBeforeExplosion} minutes [{auroraWarningNumber}/4]";
|
|
}
|
|
|
|
public AuroraEventData MakeAuroraData()
|
|
{
|
|
return new((float)AuroraCountdownTimeMs * 0.001f, (float)AuroraWarningTimeMs * 0.001f, (float)AuroraRealExplosionTime);
|
|
}
|
|
|
|
public TimeData GetTimeData()
|
|
{
|
|
return new(timeKeeper.MakeTimePacket(), MakeAuroraData());
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
timeKeeper.TimeSkipped -= ReadjustAuroraRealExplosionTime;
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
public enum TimeModification
|
|
{
|
|
DAY, NIGHT, SKIP
|
|
}
|
|
}
|