Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync spawning commands #2113

Merged
merged 2 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 SpawnConsoleCommand_OnConsoleCommand_PatchTest
{
[TestMethod]
public void Sanity()
{
IEnumerable<CodeInstruction> originalIl = PatchTestHelper.GetInstructionsFromMethod(SpawnConsoleCommand_OnConsoleCommand_Patch.TARGET_METHOD);
IEnumerable<CodeInstruction> transformedIl = SpawnConsoleCommand_OnConsoleCommand_Patch.Transpiler(originalIl);
transformedIl.Count().Should().Be(originalIl.Count() + 2);
}
}
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 SubConsoleCommand_OnConsoleCommand_sub_PatchTest
{
[TestMethod]
public void Sanity()
{
IEnumerable<CodeInstruction> originalIl = PatchTestHelper.GetInstructionsFromMethod(SubConsoleCommand_OnConsoleCommand_sub_Patch.TARGET_METHOD);
IEnumerable<CodeInstruction> transformedIl = SubConsoleCommand_OnConsoleCommand_sub_Patch.Transpiler(originalIl);
transformedIl.Count().Should().Be(originalIl.Count());
}
}
5 changes: 1 addition & 4 deletions NitroxClient/GameLogic/MobileVehicleBay.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.GameLogic.Helper;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;

namespace NitroxClient.GameLogic;
Expand Down Expand Up @@ -42,8 +40,7 @@ public void BeginCrafting(ConstructorInput constructor, GameObject constructedOb

NitroxId constructedObjectId = NitroxEntity.GenerateNewId(constructedObject);

VehicleWorldEntity vehicleEntity = new(constructorId, DayNightCycle.main.timePassedAsFloat, constructedObject.transform.ToLocalDto(), string.Empty, false, constructedObjectId, techType.ToDto(), null);
VehicleChildEntityHelper.PopulateChildren(constructedObjectId, constructedObject.GetFullHierarchyPath(), vehicleEntity.ChildEntities, constructedObject);
VehicleWorldEntity vehicleEntity = Vehicles.BuildVehicleWorldEntity(constructedObject, constructedObjectId, techType, constructorId);

packetSender.Send(new EntitySpawnedByClient(vehicleEntity));

Expand Down
52 changes: 28 additions & 24 deletions NitroxClient/GameLogic/NitroxConsole.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using System;
using NitroxClient.Communication.Abstract;
using NitroxClient.GameLogic.Helper;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using NitroxModel_Subnautica.Helper;
using UnityEngine;

Expand All @@ -16,29 +14,28 @@ public class NitroxConsole
public static bool DisableConsole { get; set; } = true;

private readonly IPacketSender packetSender;
private readonly Items item;
private readonly Items items;

public NitroxConsole(IPacketSender packetSender, Items item)
public NitroxConsole(IPacketSender packetSender, Items items)
{
this.packetSender = packetSender;
this.item = item;
this.items = items;
}

//List of things that can be spawned : https://subnauticacommands.com/items
public void Spawn(GameObject gameObject)
{
TechType techType = CraftData.GetTechType(gameObject);
TechType techType = GetObjectTechType(gameObject);

try
{
if (VehicleHelper.IsVehicle(techType))
{
SpawnVehicle(gameObject);
SpawnVehicle(gameObject, techType);
}
else
{
SpawnItem(gameObject);
//TODO: Add support for no AI creature that need to be spawned as well
DefaultSpawn(gameObject);
}
}
catch (Exception ex)
Expand All @@ -48,32 +45,39 @@ public void Spawn(GameObject gameObject)
}

/// <summary>
/// Spawns a Seamoth or an Exosuit
/// Spawns Seamoth, Exosuit or Cyclops
/// </summary>
private void SpawnVehicle(GameObject gameObject)
private void SpawnVehicle(GameObject gameObject, TechType techType)
{
TechType techType = CraftData.GetTechType(gameObject);

NitroxId id = NitroxEntity.GetIdOrGenerateNew(gameObject);

VehicleWorldEntity vehicleEntity = new VehicleWorldEntity(null, DayNightCycle.main.timePassedAsFloat, gameObject.transform.ToLocalDto(), "", false, id, techType.ToDto(), null);
VehicleChildEntityHelper.PopulateChildren(id, gameObject.GetFullHierarchyPath(), vehicleEntity.ChildEntities, gameObject);

VehicleWorldEntity vehicleEntity = Vehicles.BuildVehicleWorldEntity(gameObject, id, techType);

packetSender.Send(new EntitySpawnedByClient(vehicleEntity));

Log.Debug($"Spawning vehicle {techType} with id {techType} at {gameObject.transform.position}");
Log.Debug($"Spawning vehicle {techType} with id {id} at {gameObject.transform.position}");
}

/// <summary>
/// Spawns a Pickupable item
/// </summary>
private void SpawnItem(GameObject gameObject)
private void DefaultSpawn(GameObject gameObject)
{
items.Dropped(gameObject);
}

private static TechType GetObjectTechType(GameObject gameObject)
{
if (gameObject.TryGetComponent(out Pickupable pickupable))
TechType techType = CraftData.GetTechType(gameObject);
if (techType != TechType.None)
{
Log.Debug($"Spawning item {pickupable.GetTechName()} at {gameObject.transform.position}");
item.Dropped(gameObject, pickupable.GetTechType());
return techType;
}

// Cyclops' GameObject doesn't have a way to give its a TechType so we detect it differently
if (gameObject.TryGetComponent(out SubRoot subRoot) && subRoot.isCyclops)
{
return TechType.Cyclops;
}

return TechType.None;
}
}
}
13 changes: 11 additions & 2 deletions NitroxClient/GameLogic/Vehicles.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
using System.Collections;
using NitroxClient.Communication;
using NitroxClient.Communication.Abstract;
using NitroxClient.GameLogic.Helper;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel_Subnautica.DataStructures;
using NitroxModel_Subnautica.DataStructures.GameLogic;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using NitroxModel_Subnautica.DataStructures.GameLogic;
using UnityEngine;

namespace NitroxClient.GameLogic;
Expand Down Expand Up @@ -232,4 +234,11 @@ public static void RemoveNitroxEntitiesTagging(GameObject constructedObject)
UnityEngine.Object.DestroyImmediate(nitroxEntity);
}
}

public static VehicleWorldEntity BuildVehicleWorldEntity(GameObject constructedObject, NitroxId constructedObjectId, TechType techType, NitroxId constructorId = null)
{
VehicleWorldEntity vehicleEntity = new(constructorId, DayNightCycle.main.timePassedAsFloat, constructedObject.transform.ToLocalDto(), string.Empty, false, constructedObjectId, techType.ToDto(), null);
VehicleChildEntityHelper.PopulateChildren(constructedObjectId, constructedObject.GetFullHierarchyPath(), vehicleEntity.ChildEntities, constructedObject);
return vehicleEntity;
}
}
Original file line number Diff line number Diff line change
@@ -1,47 +1,24 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using NitroxClient.GameLogic;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Helper;
using UnityEngine;

namespace NitroxPatcher.Patches.Dynamic;

/// <summary>
/// Prevents local player from using "spawn" command without at least the <see cref="Perms.MODERATOR"/> permissions.
/// </summary>
public sealed partial class SpawnConsoleCommand_OnConsoleCommand_Patch : NitroxPatch, IDynamicPatch
{
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SpawnConsoleCommand t) => t.OnConsoleCommand_spawn(default(NotificationCenter.Notification)));
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((SpawnConsoleCommand t) => t.OnConsoleCommand_spawn(default));

private static readonly OpCode INJECTION_CODE = OpCodes.Call;
private static readonly object INJECTION_OPERAND = Reflect.Method(() => Utils.CreatePrefab(default(GameObject), default(float), default(bool)));

public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
public static bool Prefix(NotificationCenter.Notification n)
{
Validate.NotNull(INJECTION_OPERAND);

foreach (CodeInstruction instruction in instructions)
if (Resolve<LocalPlayer>().Permissions < Perms.MODERATOR)
{
yield return instruction;

/*
* GameObject gameObject = global::Utils.CreatePrefab(prefabForTechType, maxDist, i > 0);
* -> SpawnConsoleCommand_OnConsoleCommand_Patch.Callback(gameObject);
* LargeWorldEntity.Register(gameObject);
* CrafterLogic.NotifyCraftEnd(gameObject, techType);
* gameObject.SendMessage("StartConstruction", SendMessageOptions.DontRequireReceiver);
*/
if (instruction.opcode == INJECTION_CODE && instruction.operand.Equals(INJECTION_OPERAND))
{
yield return new CodeInstruction(OpCodes.Dup);
yield return new CodeInstruction(OpCodes.Call, ((Action<GameObject>)Callback).Method);
}

Log.InGame(Language.main.Get("Nitrox_MissingPermission").Replace("{PERMISSION}", Perms.MODERATOR.ToString()));
return false;
}
}

public static void Callback(GameObject gameObject)
{
Resolve<NitroxConsole>().Spawn(gameObject);
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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>
/// Syncs "spawn" command.
/// </summary>
public sealed partial class SpawnConsoleCommand_SpawnAsync_Patch : NitroxPatch, IDynamicPatch
{
internal static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((SpawnConsoleCommand t) => t.SpawnAsync(default)));

/*
* MODIFIED:
* GameObject gameObject = global::Utils.CreatePrefab(prefabForTechType, maxDist, i > 0);
* SpawnConsoleCommand_OnConsoleCommand_Patch.WrappedCallback(gameObject); <---- INSERTED LINE
* LargeWorldEntity.Register(gameObject);
* CrafterLogic.NotifyCraftEnd(gameObject, techType);
* gameObject.SendMessage("StartConstruction", SendMessageOptions.DontRequireReceiver);
*/
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
return new CodeMatcher(instructions).MatchStartForward([
new CodeMatch(OpCodes.Call, Reflect.Method(() => Utils.CreatePrefab(default, default, default)))
])
.Advance(1)
.Insert([
new CodeInstruction(OpCodes.Dup),
new CodeInstruction(OpCodes.Call, Reflect.Method(() => Callback(default)))
])
.InstructionEnumeration();
}

public static void Callback(GameObject gameObject)
{
Resolve<NitroxConsole>().Spawn(gameObject);
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,63 @@
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using NitroxClient.GameLogic;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Helper;
using UnityEngine;

namespace NitroxPatcher.Patches.Dynamic;

/// <summary>
/// Called whenever a Cyclops or Seamoth is spawned. Nitrox already has its own command to spawn vehicles.
/// This patch is only meant to block the method from executing, causing two vehicles to be spawned instead of one
/// Prevents local player from using "sub" command without at least the <see cref="Perms.MODERATOR"/> permissions.
/// Once they have the permissions, sync this command.
/// </summary>
public sealed partial class SubConsoleCommand_OnConsoleCommand_sub_Patch : NitroxPatch, IDynamicPatch
{
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SubConsoleCommand t) => t.OnConsoleCommand_sub(default));
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((SubConsoleCommand t) => t.OnConsoleCommand_sub(default));

public static bool Prefix()
public static bool Prefix(NotificationCenter.Notification n)
{
Log.InGame(Language.main.Get("Nitrox_CommandNotAvailable"));
return false;
if (Resolve<LocalPlayer>().Permissions < Perms.MODERATOR)
{
Log.InGame(Language.main.Get("Nitrox_MissingPermission").Replace("{PERMISSION}", Perms.MODERATOR.ToString()));
return false;
}

string text = (string)n.data[0];
if (!string.IsNullOrEmpty(text) && !text.ToLowerInvariant().Equals("cyclops"))
{
Log.InGame(Language.main.Get("Nitrox_CommandNotAvailable"));
return false;
}
return true;
}

/*
* REPLACE:
* LightmappedPrefabs.main.RequestScenePrefab(text, new LightmappedPrefabs.OnPrefabLoaded(this.OnSubPrefabLoaded));
* BY:
* LightmappedPrefabs.main.RequestScenePrefab(text, new LightmappedPrefabs.OnPrefabLoaded(SubConsoleCommand_OnConsoleCommand_sub_Patch.WrappedCallback));
*/
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
return new CodeMatcher(instructions).MatchEndForward([
new CodeMatch(OpCodes.Stfld),
new CodeMatch(OpCodes.Ldsfld),
new CodeMatch(OpCodes.Ldloc_0),
new CodeMatch(OpCodes.Ldarg_0)
])
.SetOpcodeAndAdvance(OpCodes.Ldnull)
tornac1234 marked this conversation as resolved.
Show resolved Hide resolved
.SetOperandAndAdvance(Reflect.Method(() => WrappedCallback(default)))
.InstructionEnumeration();
}

public static void WrappedCallback(GameObject prefab)
tornac1234 marked this conversation as resolved.
Show resolved Hide resolved
{
tornac1234 marked this conversation as resolved.
Show resolved Hide resolved
SubConsoleCommand instance = SubConsoleCommand.main;
// Call the original callback and then get the object it created to broadcast its creation
instance.OnSubPrefabLoaded(prefab);
Resolve<NitroxConsole>().Spawn(instance.lastCreatedSub);
}
}