Skip to content

Commit

Permalink
Synced flares
Browse files Browse the repository at this point in the history
  • Loading branch information
tornac1234 authored and dartasen committed Dec 3, 2023
1 parent 2c48110 commit aae2e47
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 17 deletions.
20 changes: 20 additions & 0 deletions Nitrox.Test/Patcher/Patches/Dynamic/Flare_Update_PatchTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using HarmonyLib;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NitroxTest.Patcher;

namespace NitroxPatcher.Patches.Dynamic;

[TestClass]
public class Flare_Update_PatchTest
{
[TestMethod]
public void Sanity()
{
IEnumerable<CodeInstruction> originalIl = PatchTestHelper.GetInstructionsFromMethod(Flare_Update_Patch.TARGET_METHOD);
IEnumerable<CodeInstruction> transformedIl = Flare_Update_Patch.Transpiler(originalIl);
transformedIl.Count().Should().Be(originalIl.Count());
}
}
15 changes: 15 additions & 0 deletions Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,14 @@ private static void EntityTest(Entity entity, Entity entityAfter)
Assert.AreEqual(metadata.TimeNextBreed, metadataAfter.TimeNextBreed);
Assert.AreEqual(metadata.BornInside, metadataAfter.BornInside);
break;
case FlareMetadata metadata when entityAfter.Metadata is FlareMetadata metadataAfter:
Assert.AreEqual(metadata.EnergyLeft, metadataAfter.EnergyLeft);
Assert.AreEqual(metadata.HasBeenThrown, metadataAfter.HasBeenThrown);
Assert.AreEqual(metadata.FlareActivateTime, metadataAfter.FlareActivateTime);
break;
case BeaconMetadata metadata when entityAfter.Metadata is BeaconMetadata metadataAfter:
Assert.AreEqual(metadata.Label, metadataAfter.Label);
break;
default:
Assert.Fail($"Runtime type of {nameof(Entity)}.{nameof(Entity.Metadata)} is not equal: {entity.Metadata?.GetType().Name} - {entityAfter.Metadata?.GetType().Name}");
break;
Expand Down Expand Up @@ -332,6 +340,13 @@ private static void EntityTest(Entity entity, Entity entityAfter)
break;
case CellRootEntity _ when worldEntityAfter is CellRootEntity _:
break;
case PlacedWorldEntity _ when worldEntityAfter is PlacedWorldEntity _:
break;
case OxygenPipeEntity oxygenPipeEntity when worldEntityAfter is OxygenPipeEntity oxygenPipeEntityAfter:
Assert.AreEqual(oxygenPipeEntity.ParentPipeId, oxygenPipeEntityAfter.ParentPipeId);
Assert.AreEqual(oxygenPipeEntity.RootPipeId, oxygenPipeEntityAfter.RootPipeId);
Assert.AreEqual(oxygenPipeEntity.ParentPosition, oxygenPipeEntityAfter.ParentPosition);
break;
case GlobalRootEntity globalRootEntity when worldEntityAfter is GlobalRootEntity globalRootEntityAfter:
if (globalRootEntity.GetType() != typeof(GlobalRootEntity))
{
Expand Down
25 changes: 13 additions & 12 deletions NitroxClient/ClientAutoFacRegistrar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public void RegisterDependencies(ContainerBuilder containerBuilder)
}

RegisterCoreDependencies(containerBuilder);
RegisterMetadataDependencies(containerBuilder);
RegisterPacketProcessors(containerBuilder);
RegisterColorSwapManagers(containerBuilder);
RegisterInitialSyncProcessors(containerBuilder);
Expand Down Expand Up @@ -99,18 +100,6 @@ private static void RegisterCoreDependencies(ContainerBuilder containerBuilder)
.As<IMap>()
.InstancePerLifetimeScope();

containerBuilder.RegisterAssemblyTypes(currentAssembly)
.AssignableTo<EntityMetadataExtractor>()
.As<EntityMetadataExtractor>()
.AsSelf()
.SingleInstance();

containerBuilder.RegisterAssemblyTypes(currentAssembly)
.AssignableTo<EntityMetadataProcessor>()
.As<EntityMetadataProcessor>()
.AsSelf()
.SingleInstance();

containerBuilder.RegisterType<PlayerManager>().InstancePerLifetimeScope();
containerBuilder.RegisterType<PlayerModelManager>().InstancePerLifetimeScope();
containerBuilder.RegisterType<PlayerVitalsManager>().InstancePerLifetimeScope();
Expand Down Expand Up @@ -144,6 +133,18 @@ private static void RegisterCoreDependencies(ContainerBuilder containerBuilder)
containerBuilder.RegisterType<TimeManager>().InstancePerLifetimeScope();
}

private void RegisterMetadataDependencies(ContainerBuilder containerBuilder)
{
containerBuilder.RegisterAssemblyTypes(currentAssembly)
.AssignableTo<EntityMetadataExtractor>()
.As<EntityMetadataExtractor>()
.InstancePerLifetimeScope();
containerBuilder.RegisterAssemblyTypes(currentAssembly)
.AssignableTo<EntityMetadataProcessor>()
.As<EntityMetadataProcessor>()
.InstancePerLifetimeScope();
}

private void RegisterPacketProcessors(ContainerBuilder containerBuilder)
{
containerBuilder
Expand Down
2 changes: 1 addition & 1 deletion NitroxClient/GameLogic/Entities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ public Type RequireEntityType(NitroxId id)

public bool IsParentReady(NitroxId id)
{
return WasParentSpawned(id) || (NitroxEntity.TryGetObjectFrom(id, out GameObject o) && o);
return WasParentSpawned(id) || NitroxEntity.TryGetObjectFrom(id, out GameObject _);
}

public bool WasParentSpawned(NitroxId id)
Expand Down
8 changes: 4 additions & 4 deletions NitroxClient/GameLogic/Items.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.DataStructures.Util;
using NitroxModel.Helper;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
Expand All @@ -21,13 +20,12 @@ namespace NitroxClient.GameLogic;
public class Items
{
private readonly IPacketSender packetSender;
private readonly IMap map;
private readonly Entities entities;
public GameObject PickingUpObject;

public Items(IPacketSender packetSender, IMap map, Entities entities)
public Items(IPacketSender packetSender, Entities entities)
{
this.packetSender = packetSender;
this.map = map;
this.entities = entities;
}

Expand All @@ -39,6 +37,7 @@ public void UpdatePosition(NitroxId id, Vector3 location, Quaternion rotation)

public void PickedUp(GameObject gameObject, TechType techType)
{
PickingUpObject = gameObject;
// We want to remove any remote tracking immediately on pickup as it can cause weird behavior like holding a ghost item still in the world.
RemoveAnyRemoteControl(gameObject);

Expand All @@ -62,6 +61,7 @@ public void PickedUp(GameObject gameObject, TechType techType)

PickupItem pickupItem = new(id, inventoryItemEntity);
packetSender.Send(pickupItem);
PickingUpObject = null;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;

namespace NitroxClient.GameLogic.Spawning.Metadata.Extractor;

public class FlareMetadataExtractor : GenericEntityMetadataExtractor<Flare, FlareMetadata>
{
private readonly Items items;

public FlareMetadataExtractor(Items items)
{
this.items = items;
}

public override FlareMetadata Extract(Flare flare)
{
// If the flare is being picked up, its metadata must be set accordingly
if (flare.flareActiveState && items.PickingUpObject != flare.gameObject)
{
return new(flare.energyLeft, flare.hasBeenThrown, flare.flareActivateTime);
}
return new(flare.energyLeft, flare.hasBeenThrown, null);
}
}
38 changes: 38 additions & 0 deletions NitroxClient/GameLogic/Spawning/Metadata/FlareMetadataProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;

namespace NitroxClient.GameLogic.Spawning.Metadata;

public class FlareMetadataProcessor : GenericEntityMetadataProcessor<FlareMetadata>
{
public override void ProcessMetadata(GameObject gameObject, FlareMetadata metadata)
{
if (!gameObject.TryGetComponent(out Flare flare))
{
Log.Error($"[{nameof(FlareMetadataProcessor)}] Can't apply metadata to {gameObject} because it doesn't have a {nameof(Flare)} component");
return;
}
flare.energyLeft = metadata.EnergyLeft;
flare.hasBeenThrown = metadata.HasBeenThrown;

if (metadata.FlareActivateTime.HasValue)
{
flare.flareActivateTime = metadata.FlareActivateTime.Value;
flare.flareActiveState = true;
// From Flare.OnDrop
flare.useRigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
// Subtract the passed time to get to the current real amount of energy
flare.energyLeft -= DayNightCycle.main.timePassedAsFloat - metadata.FlareActivateTime.Value;
flare.GetComponent<WorldForces>().enabled = true;

// From Flare.Awake but without the part disabling the light
flare.capRenderer.enabled = true;
if (flare.fxControl && !flare.fxIsPlaying)
{
flare.fxControl.Play(1);
flare.fxIsPlaying = true;
flare.light.enabled = true;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace NitroxModel.DataStructures.GameLogic.Entities.Metadata
[ProtoInclude(280, typeof(GhostMetadata))]
[ProtoInclude(290, typeof(WaterParkCreatureMetadata))]
[ProtoInclude(300, typeof(BeaconMetadata))]
[ProtoInclude(310, typeof(FlareMetadata))]
public abstract class EntityMetadata
{
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Runtime.Serialization;
using BinaryPack.Attributes;

namespace NitroxModel.DataStructures.GameLogic.Entities.Metadata;

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

[DataMember(Order = 2)]
public bool HasBeenThrown { get; }

[DataMember(Order = 3)]
public float? FlareActivateTime { get; }

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

public FlareMetadata(float energyLeft, bool hasBeenThrown, float? flareActivateTime)
{
EnergyLeft = energyLeft;
HasBeenThrown = hasBeenThrown;
FlareActivateTime = flareActivateTime;
}

public override string ToString()
{
return $"[FlareMetadata EnergyLeft: {EnergyLeft}, HasBeenThrown: {HasBeenThrown}, FlareActivateTime: {FlareActivateTime}]";
}
}
25 changes: 25 additions & 0 deletions NitroxPatcher/Patches/Dynamic/Flare_OnDestroy_Patch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Reflection;
using NitroxClient.Communication.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.DataStructures;
using NitroxModel.Helper;
using NitroxModel.Packets;

namespace NitroxPatcher.Patches.Dynamic;

/// <summary>
/// Broadcasts the destruction of a Flare once it's out of energy if the local player simulates it.
/// </summary>
public sealed partial class Flare_OnDestroy_Patch : NitroxPatch, IDynamicPatch
{
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Flare t) => t.OnDestroy());

public static void Prefix(Flare __instance)
{
if (__instance.TryGetNitroxId(out NitroxId flareId) &&
Resolve<SimulationOwnership>().HasAnyLockType(flareId))
{
Resolve<IPacketSender>().Send(new EntityDestroyed(flareId));
}
}
}
34 changes: 34 additions & 0 deletions NitroxPatcher/Patches/Dynamic/Flare_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 Flare_Update_Patch : NitroxPatch, IDynamicPatch
{
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Flare 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;
}
}

0 comments on commit aae2e47

Please sign in to comment.