Skip to content

Commit

Permalink
Sync radiation leaks, added a "real elapsed time"
Browse files Browse the repository at this point in the history
  • Loading branch information
tornac1234 committed Jan 7, 2024
1 parent 8abb67b commit b8eb8c4
Show file tree
Hide file tree
Showing 23 changed files with 418 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public override void Process(AuroraAndTimeUpdate packet)
{
timeManager.ProcessUpdate(packet.TimeData.TimePacket);
StoryManager.UpdateAuroraData(packet.TimeData.AuroraEventData);
timeManager.AuroraRealExplosionTime = packet.TimeData.AuroraEventData.AuroraRealExplosionTime;
if (packet.Restore)
{
StoryManager.RestoreAurora();
Expand Down
3 changes: 2 additions & 1 deletion NitroxClient/GameLogic/Entities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class Entities
public List<Entity> EntitiesToSpawn { get; private init; }
private bool spawningEntities;

public Entities(IPacketSender packetSender, ThrottledPacketSender throttledPacketSender, EntityMetadataManager entityMetadataManager, PlayerManager playerManager, ILocalNitroxPlayer localPlayer)
public Entities(IPacketSender packetSender, ThrottledPacketSender throttledPacketSender, EntityMetadataManager entityMetadataManager, PlayerManager playerManager, ILocalNitroxPlayer localPlayer, TimeManager timeManager)
{
this.packetSender = packetSender;
this.throttledPacketSender = throttledPacketSender;
Expand All @@ -60,6 +60,7 @@ public Entities(IPacketSender packetSender, ThrottledPacketSender throttledPacke
entitySpawnersByType[typeof(VehicleWorldEntity)] = entitySpawnersByType[typeof(WorldEntity)];
entitySpawnersByType[typeof(SerializedWorldEntity)] = entitySpawnersByType[typeof(WorldEntity)];
entitySpawnersByType[typeof(GlobalRootEntity)] = new GlobalRootEntitySpawner();
entitySpawnersByType[typeof(RadiationLeakEntity)] = new RadiationLeakEntitySpawner(timeManager);
entitySpawnersByType[typeof(BuildEntity)] = new BuildEntitySpawner(this);
entitySpawnersByType[typeof(ModuleEntity)] = new ModuleEntitySpawner(this);
entitySpawnersByType[typeof(GhostEntity)] = new GhostEntitySpawner();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,5 +163,7 @@ private static IEnumerator RefreshStoryWithLatestData()
private void SetTimeData(InitialPlayerSync packet)
{
timeManager.ProcessUpdate(packet.TimeData.TimePacket);
timeManager.InitRealTimeElapsed(packet.TimeData.RealTimeElapsed, packet.TimeData.TimePacket.UpdateTime);
timeManager.AuroraRealExplosionTime = packet.TimeData.AuroraEventData.AuroraRealExplosionTime;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using NitroxClient.GameLogic.Spawning.Metadata.Extractor.Abstract;
using NitroxModel.Core;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;

namespace NitroxClient.GameLogic.Spawning.Metadata.Extractor;

public class RadiationMetadataExtractor : EntityMetadataExtractor<RadiationLeak, RadiationMetadata>
{
public override RadiationMetadata Extract(RadiationLeak leak)
{
// Note: this extractor should only be used when this radiation leak is being repaired
TimeManager timeManager = NitroxServiceLocator.LocateService<TimeManager>();
float realTimeFix = leak.liveMixin.IsFullHealth() ? (float)timeManager.RealTimeElapsed : -1;
return new(leak.liveMixin.health, realTimeFix);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using NitroxClient.Communication;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.Core;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.Packets;
using UnityEngine;

namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;

public class RadiationMetadataProcessor : EntityMetadataProcessor<RadiationMetadata>
{
public override void ProcessMetadata(GameObject gameObject, RadiationMetadata metadata)
{
if (!gameObject.TryGetComponent(out LiveMixin liveMixin))
{
Log.Error($"[{nameof(RadiationMetadataProcessor)}] Couldn't find LiveMixin on {gameObject}");
return;
}
LiveMixinManager liveMixinManager = NitroxServiceLocator.LocateService<LiveMixinManager>();
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
liveMixinManager.SyncRemoteHealth(liveMixin, metadata.Health);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NitroxClient.GameLogic.Spawning.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.DataStructures.Util;
using UnityEngine;

namespace NitroxClient.GameLogic.Spawning.WorldEntities;

public class RadiationLeakEntitySpawner : SyncEntitySpawner<RadiationLeakEntity>
{
private const int TOTAL_LEAKS = 11;
private readonly TimeManager timeManager;
private readonly List<float> registeredLeaksFixTime = new();

public RadiationLeakEntitySpawner(TimeManager timeManager)
{
this.timeManager = timeManager;
}

protected override IEnumerator SpawnAsync(RadiationLeakEntity entity, TaskResult<Optional<GameObject>> result)
{
SpawnSync(entity, result);
yield break;
}

protected override bool SpawnSync(RadiationLeakEntity entity, TaskResult<Optional<GameObject>> result)
{
// This script is located under (Aurora Scene) //Aurora-Main/Aurora so it's a good starting point to search through the GameObjects
CrashedShipExploder crashedShipExploder = CrashedShipExploder.main;
LeakingRadiation leakingRadiation = LeakingRadiation.main;
if (!crashedShipExploder || !leakingRadiation || entity.Metadata is not RadiationMetadata metadata)
{
return true;
}
Transform radiationLeaksHolder = crashedShipExploder.transform.Find("radiationleaks").GetChild(0);
RadiationLeak radiationLeak = radiationLeaksHolder.GetChild(entity.ObjectIndex).GetComponent<RadiationLeak>();
NitroxEntity.SetNewId(radiationLeak.gameObject, entity.Id);
radiationLeak.liveMixin.health = metadata.Health;
registeredLeaksFixTime.Add(metadata.FixRealTime);

// We can only calculate the radiation increment and dissipation once we got all radiation leaks info
if (crashedShipExploder.IsExploded() && registeredLeaksFixTime.Count == TOTAL_LEAKS)
{
RecalculateRadiationRadius(leakingRadiation);
}

return true;
}

public void RecalculateRadiationRadius(LeakingRadiation leakingRadiation)
{
float realElapsedTime = (float)timeManager.RealTimeElapsed;
// We substract the explosion time from the real time because before that, the radius doesn't increment
float realExplosionTime = timeManager.AuroraRealExplosionTime;
float maxRegisteredLeakFixTime = registeredLeaksFixTime.Max();

// Note: Only increment radius if leaks were fixed AFTER explosion (before, game code doesn't increase radius)

// If leaks aren't all fixed yet we calculate from current real elapsed time
float deltaTimeAfterExplosion = realElapsedTime - realExplosionTime;
if (maxRegisteredLeakFixTime == -1)
{
if (deltaTimeAfterExplosion > 0)
{
float radiusIncrement = deltaTimeAfterExplosion * leakingRadiation.kGrowRate;
// Calculation lines from LeakingRadiation.Update
leakingRadiation.currentRadius = Mathf.Clamp(leakingRadiation.kStartRadius + radiusIncrement, 0f, leakingRadiation.kMaxRadius);
leakingRadiation.damagePlayerInRadius.damageRadius = leakingRadiation.currentRadius;
leakingRadiation.radiatePlayerInRange.radiateRadius = leakingRadiation.currentRadius;
}
// If leaks aren't fixed, we won't need to calculate a radius decrement
return;
}
leakingRadiation.radiationFixed = true;

// If all leaks are fixed we calculate from the time they were fixed
float deltaAliveTime = maxRegisteredLeakFixTime - realExplosionTime;
if (deltaAliveTime > 0)
{
float radiusIncrement = deltaAliveTime * leakingRadiation.kGrowRate;
leakingRadiation.currentRadius = Mathf.Clamp(leakingRadiation.kStartRadius + radiusIncrement, 0f, leakingRadiation.kMaxRadius);
}

// Now calculate the natural dissipation decrement from the time leaks are fixed
// If they were fixed before real explosion time, we calculate from real explosion time
float deltaFixedTimeAfterExplosion = realElapsedTime - Mathf.Max(maxRegisteredLeakFixTime, realExplosionTime);
if (deltaFixedTimeAfterExplosion > 0)
{
float radiusDecrement = deltaFixedTimeAfterExplosion * leakingRadiation.kNaturalDissipation;
leakingRadiation.currentRadius = Mathf.Clamp(leakingRadiation.currentRadius + radiusDecrement, 0f, leakingRadiation.kMaxRadius);
}
leakingRadiation.damagePlayerInRadius.damageRadius = leakingRadiation.currentRadius;
leakingRadiation.radiatePlayerInRange.radiateRadius = leakingRadiation.currentRadius;
}

protected override bool SpawnsOwnChildren(RadiationLeakEntity entity) => false;
}
30 changes: 28 additions & 2 deletions NitroxClient/GameLogic/TimeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,34 @@ namespace NitroxClient.GameLogic;
public class TimeManager
{
/// <summary>
/// Latest moment at which we updated the time
/// Latest moment at which we updated the time
/// </summary>
private DateTimeOffset latestRegistrationTime;
/// <summary>
/// Latest registered value of the time
/// Latest registered value of the time
/// </summary>
private double latestRegisteredTime;

/// <summary>
/// Moment at which real time elapsed was determined
/// </summary>
private DateTimeOffset realTimeElapsedRegistrationTime;
/// <summary>
/// Only registered value of real time elapsed given when connecting. Associated to <see cref="realTimeElapsedRegistrationTime"/>
/// </summary>
private double realTimeElapsed;

public float AuroraRealExplosionTime { get; set; }

/// <summary>
/// Calculates the exact real time elapsed from an offset (<see cref="realTimeElapsedRegistrationTime"/>) and the delta time between
/// <see cref="DateTimeOffset.UtcNow"/> and the offset's exact <see cref="DateTimeOffset"/> (<see cref="latestRegistrationTime"/>).
/// </summary>
public double RealTimeElapsed
{
get => (DateTimeOffset.UtcNow - realTimeElapsedRegistrationTime).TotalMilliseconds * 0.001 + realTimeElapsed;
}

private const double DEFAULT_TIME = 480;

/// <summary>
Expand Down Expand Up @@ -76,4 +96,10 @@ public double CalculateCurrentTime()
DeltaTime = (float)(currentTime - DayNightCycle.main.timePassedAsDouble);
return currentTime;
}

public void InitRealTimeElapsed(double realTimeElapsed, long registrationTime)
{
this.realTimeElapsed = realTimeElapsed;
realTimeElapsedRegistrationTime = DateTimeOffset.FromUnixTimeMilliseconds(registrationTime);
}
}
9 changes: 8 additions & 1 deletion NitroxModel/DataStructures/GameLogic/AuroraEventData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,23 @@ public class AuroraEventData
[DataMember(Order = 2)]
public float TimeToStartWarning;

/// <summary>
/// Real time in seconds at which Aurora's considered exploded
/// </summary>
[DataMember(Order = 3)]
public float AuroraRealExplosionTime;

[IgnoreConstructor]
protected AuroraEventData()
{
// Constructor for serialization. Has to be "protected" for json serialization.
}

public AuroraEventData(float timeToStartCountdown, float timeToStartWarning)
public AuroraEventData(float timeToStartCountdown, float timeToStartWarning, float auroraRealExplosionTime)
{
TimeToStartCountdown = timeToStartCountdown;
TimeToStartWarning = timeToStartWarning;
AuroraRealExplosionTime = auroraRealExplosionTime;
}

[NonSerialized]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace NitroxModel.DataStructures.GameLogic.Entities;
[ProtoInclude(56, typeof(PlanterEntity))]
[ProtoInclude(57, typeof(PlayerWorldEntity))]
[ProtoInclude(58, typeof(VehicleWorldEntity))]
[ProtoInclude(59, typeof(RadiationLeakEntity))]
public class GlobalRootEntity : WorldEntity
{
[IgnoreConstructor]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace NitroxModel.DataStructures.GameLogic.Entities.Metadata
[ProtoInclude(73, typeof(NamedColoredMetadata))]
[ProtoInclude(74, typeof(BeaconMetadata))]
[ProtoInclude(75, typeof(FlareMetadata))]
[ProtoInclude(76, typeof(RadiationMetadata))]
public abstract class EntityMetadata
{
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Runtime.Serialization;

namespace NitroxModel.DataStructures.GameLogic.Entities.Metadata;

[Serializable, DataContract]
public class RadiationMetadata : EntityMetadata
{
[DataMember(Order = 1)]
public float Health { get; set; }

[DataMember(Order = 2)]
public float FixRealTime { get; set; }

public RadiationMetadata(float health, float fixRealTime = -1)
{
Health = health;
FixRealTime = fixRealTime;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using BinaryPack.Attributes;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.DataStructures.Unity;

namespace NitroxModel.DataStructures.GameLogic.Entities;

[Serializable, DataContract]
public class RadiationLeakEntity : GlobalRootEntity
{
[DataMember(Order = 1)]
public int ObjectIndex { get; set; }

[IgnoreConstructor]
protected RadiationLeakEntity()
{
// Constructor for serialization. Has to be "protected" for json serialization.
}

public RadiationLeakEntity(NitroxId id, int objectIndex, RadiationMetadata metadata)
{
Id = id;
ObjectIndex = objectIndex;
Metadata = metadata;
}

/// <remarks>Used for deserialization</remarks>
public RadiationLeakEntity(int objectIndex, NitroxTransform transform, int level, string classId, bool spawnedByServer, NitroxId id, NitroxTechType techType, EntityMetadata metadata, NitroxId parentId, List<Entity> childEntities) :
base(transform, level, classId, spawnedByServer, id, techType, metadata, parentId, childEntities)
{
ObjectIndex = objectIndex;
}
}
4 changes: 3 additions & 1 deletion NitroxModel/DataStructures/GameLogic/TimeData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ public class TimeData
{
public TimeChange TimePacket;
public AuroraEventData AuroraEventData;
public double RealTimeElapsed;

public TimeData(TimeChange timePacket, AuroraEventData auroraEventData)
public TimeData(TimeChange timePacket, AuroraEventData auroraEventData, double realTimeElapsed)
{
TimePacket = timePacket;
AuroraEventData = auroraEventData;
RealTimeElapsed = realTimeElapsed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Reflection;
using NitroxModel.Helper;

namespace NitroxPatcher.Patches.Dynamic;

/// <summary>
/// Disables the "decontaminate" command
/// </summary>
public sealed partial class LeakingRadiation_OnConsoleCommand_decontaminate_Patch : NitroxPatch, IDynamicPatch
{
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((LeakingRadiation t) => t.OnConsoleCommand_decontaminate());

public static bool Prefix()
{
// This command can't be synced because it would break how radiation leak is currently synced
// Currently all radiation radius calculations depend on the fixed leaks
// But this command would work even if leaks aren't fixed (modifying the radius but only on local client)
Log.InGame(Language.main.Get("Nitrox_CommandNotAvailable"));
return false;
}
}
34 changes: 34 additions & 0 deletions NitroxPatcher/Patches/Dynamic/LeakingRadiation_Update_Patch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using NitroxClient.GameLogic;
using NitroxModel.Helper;
using UnityEngine;

namespace NitroxPatcher.Patches.Dynamic;

/// <summary>
/// Replaces local use of <see cref="Time.deltaTime"/> by <see cref="TimeManager.DeltaTime"/>
/// </summary>
public sealed partial class LeakingRadiation_Update_Patch : NitroxPatch, IDynamicPatch
{
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((LeakingRadiation t) => t.Update());
private static readonly MethodInfo INSERTED_METHOD = Reflect.Method(() => GetDeltaTime());
private static readonly MethodInfo MATCHING_FIELD = Reflect.Property(() => Time.deltaTime).GetGetMethod();

public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
return new CodeMatcher(instructions).MatchStartForward(new CodeMatch(OpCodes.Call, MATCHING_FIELD))
.SetOperandAndAdvance(INSERTED_METHOD)
.InstructionEnumeration();
}

/// <summary>
/// Wrapper for dependency resolving and variable querying
/// </summary>
public static float GetDeltaTime()
{
return Resolve<TimeManager>().DeltaTime;
}
}
Loading

0 comments on commit b8eb8c4

Please sign in to comment.