first commit
This commit is contained in:
14
NitroxPatcher/App.config
Normal file
14
NitroxPatcher/App.config
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Configuration" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
158
NitroxPatcher/Main.cs
Normal file
158
NitroxPatcher/Main.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
extern alias JB;
|
||||
global using NitroxModel.Logger;
|
||||
global using static NitroxClient.Helpers.NitroxEntityExtensions;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using JB::JetBrains.Annotations;
|
||||
using Microsoft.Win32;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel_Subnautica.Logger;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher;
|
||||
|
||||
public static class Main
|
||||
{
|
||||
/// <summary>
|
||||
/// Lazily (i.e. when called, unlike immediately on class load) gets the path to the Nitrox Launcher folder.
|
||||
/// This path can be anywhere on the system because it's placed somewhere the user likes.
|
||||
/// </summary>
|
||||
private static readonly Lazy<string> nitroxLauncherDir = new(() =>
|
||||
{
|
||||
// Get path from command args.
|
||||
string[] args = Environment.GetCommandLineArgs();
|
||||
for (int i = 0; i < args.Length - 1; i++)
|
||||
{
|
||||
if (args[i].Equals("--nitrox", StringComparison.OrdinalIgnoreCase) && Directory.Exists(args[i + 1]))
|
||||
{
|
||||
return Path.GetFullPath(args[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Get path from environment variable.
|
||||
string envPath = Environment.GetEnvironmentVariable("NITROX_LAUNCHER_PATH", EnvironmentVariableTarget.Process);
|
||||
if (Directory.Exists(envPath))
|
||||
{
|
||||
return envPath;
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// Get path from windows registry.
|
||||
using RegistryKey nitroxRegKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Nitrox");
|
||||
if (nitroxRegKey == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string path = nitroxRegKey.GetValue("LauncherPath") as string;
|
||||
return Directory.Exists(path) ? path : null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
private static readonly char[] newLineChars = Environment.NewLine.ToCharArray();
|
||||
|
||||
/// <summary>
|
||||
/// Entrypoint of Nitrox. Code in this method cannot use other dependencies (DLLs) without crashing
|
||||
/// due to <see cref="AppDomain.AssemblyResolve" /> not being called.
|
||||
/// Use the <see cref="Init" /> method or later before using dependency code.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void Execute()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
|
||||
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomainOnAssemblyResolve;
|
||||
|
||||
if (!Directory.Exists(Environment.GetEnvironmentVariable("NITROX_LAUNCHER_PATH")))
|
||||
{
|
||||
Environment.SetEnvironmentVariable("NITROX_LAUNCHER_PATH", nitroxLauncherDir.Value, EnvironmentVariableTarget.Process);
|
||||
}
|
||||
if (!Directory.Exists(Environment.GetEnvironmentVariable("NITROX_LAUNCHER_PATH")))
|
||||
{
|
||||
Console.WriteLine("Nitrox will not load because launcher path was not provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
InitWithDependencies();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method must not be inlined since the AppDomain dependency resolve will be triggered when the JIT compiles this method. If it's inlined it will cause dependencies to
|
||||
/// resolve in <see cref="Execute" /> *before* the dependency resolve listener is applied.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void InitWithDependencies()
|
||||
{
|
||||
Log.Setup(gameLogger: new SubnauticaInGameLogger(), useConsoleLogging: false);
|
||||
|
||||
// Capture unity errors to be logged by our logging framework.
|
||||
Application.logMessageReceived += (condition, stackTrace, type) =>
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LogType.Error:
|
||||
case LogType.Exception:
|
||||
string toWrite = condition;
|
||||
if (!string.IsNullOrWhiteSpace(stackTrace))
|
||||
{
|
||||
toWrite += $"{Environment.NewLine}{stackTrace}";
|
||||
}
|
||||
Log.ErrorUnity(toWrite.Trim(newLineChars));
|
||||
break;
|
||||
case LogType.Warning:
|
||||
case LogType.Log:
|
||||
case LogType.Assert:
|
||||
// These logs from Unity spam too much uninteresting stuff
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
};
|
||||
|
||||
Log.Info($"Using Nitrox {NitroxEnvironment.ReleasePhase} V{NitroxEnvironment.Version} built on {NitroxEnvironment.BuildDate}");
|
||||
try
|
||||
{
|
||||
Patcher.Initialize();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Placeholder for popup gui
|
||||
Log.Error(ex, "Unhandled exception occurred while initializing Nitrox:");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nitrox DLL location resolver.
|
||||
/// <p/>
|
||||
/// Required to load the files from the Nitrox Launcher subfolder which would otherwise not be found.
|
||||
/// </summary>
|
||||
private static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
|
||||
{
|
||||
string dllFileName = args.Name.Split(',')[0];
|
||||
if (!dllFileName.EndsWith(".dll"))
|
||||
{
|
||||
dllFileName += ".dll";
|
||||
}
|
||||
|
||||
// Load DLLs where Nitrox launcher is first, if not found, use Subnautica's DLLs.
|
||||
string dllPath = Path.Combine(nitroxLauncherDir.Value, "lib", "net472", dllFileName);
|
||||
if (!File.Exists(dllPath))
|
||||
{
|
||||
Console.Write($"Did not find '{dllFileName}' at '{dllPath}'");
|
||||
dllPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), dllFileName);
|
||||
Console.WriteLine($", looking at {dllPath}");
|
||||
}
|
||||
|
||||
if (!File.Exists(dllPath))
|
||||
{
|
||||
Console.WriteLine($"Nitrox dll missing: {dllPath}");
|
||||
}
|
||||
|
||||
return Assembly.LoadFile(dllPath);
|
||||
}
|
||||
}
|
24
NitroxPatcher/Modules/NitroxPatchesModule.cs
Normal file
24
NitroxPatcher/Modules/NitroxPatchesModule.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Reflection;
|
||||
using Autofac;
|
||||
using NitroxPatcher.Patches;
|
||||
|
||||
namespace NitroxPatcher.Modules;
|
||||
|
||||
/// <summary>
|
||||
/// Simple Dependency Injection (DI) container for registering the patch classes with AutoFac.
|
||||
/// </summary>
|
||||
public class NitroxPatchesModule : Autofac.Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
builder
|
||||
.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
|
||||
.AssignableTo<IPersistentPatch>()
|
||||
.AsImplementedInterfaces().SingleInstance();
|
||||
|
||||
builder
|
||||
.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
|
||||
.AssignableTo<IDynamicPatch>()
|
||||
.AsImplementedInterfaces().SingleInstance();
|
||||
}
|
||||
}
|
26
NitroxPatcher/NitroxPatcher.csproj
Normal file
26
NitroxPatcher/NitroxPatcher.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net472;net9.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NitroxClient\NitroxClient.csproj" />
|
||||
<ProjectReference Include="..\NitroxModel-Subnautica\NitroxModel-Subnautica.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="IncludeGameReferences" AfterTargets="FindGameAndIncludeReferences">
|
||||
<ItemGroup>
|
||||
<Reference Include="FMODUnity">
|
||||
<HintPath>$(GameManagedDir)\FMODUnity.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Sentry">
|
||||
<HintPath>$(GameManagedDir)\Sentry.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
161
NitroxPatcher/Patcher.cs
Normal file
161
NitroxPatcher/Patcher.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Autofac;
|
||||
using HarmonyLib;
|
||||
using HarmonyLib.Tools;
|
||||
using NitroxClient;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxPatcher.Modules;
|
||||
using NitroxPatcher.Patches;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "DIMA001:Dependency Injection container is used directly")]
|
||||
internal static class Patcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Dependency Injection container used by NitroxPatcher only.
|
||||
/// </summary>
|
||||
private static IContainer container;
|
||||
|
||||
private static readonly Harmony harmony = new("com.nitroxmod.harmony");
|
||||
private static bool isApplied;
|
||||
|
||||
/// <summary>
|
||||
/// Applies all the dynamic patches defined in Patches namespace.
|
||||
/// Persistent patches are applied when the game initializes (i.e. before main menu).
|
||||
/// See <see cref="Patcher.Initialize"/>.
|
||||
/// </summary>
|
||||
public static void Apply()
|
||||
{
|
||||
Validate.NotNull(container, "No patches have been discovered yet! Run Execute() first.");
|
||||
if (isApplied)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (IDynamicPatch patch in container.Resolve<IDynamicPatch[]>())
|
||||
{
|
||||
Log.Debug($"Applying dynamic patch {patch.GetType().Name}");
|
||||
try
|
||||
{
|
||||
patch.Patch(harmony);
|
||||
}
|
||||
catch (HarmonyException e)
|
||||
{
|
||||
Exception innerMost = e;
|
||||
while (innerMost.InnerException != null)
|
||||
{
|
||||
innerMost = innerMost.InnerException;
|
||||
}
|
||||
Log.Error($"Error patching {patch.GetType().Name}{Environment.NewLine}{innerMost}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Error patching {patch.GetType().Name}{Environment.NewLine}{e}");
|
||||
}
|
||||
}
|
||||
|
||||
isApplied = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all the dynamic patches defined by <see cref="NitroxPatcher"/>.
|
||||
/// <p/>
|
||||
/// If the player starts the main menu for the first time, or returns from a (multiplayer) session, get rid of all the
|
||||
/// patches if applicable.
|
||||
/// </summary>
|
||||
public static void Restore()
|
||||
{
|
||||
Validate.NotNull(container, "No patches have been discovered yet! Run Execute() first.");
|
||||
if (!isApplied)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (IDynamicPatch patch in container.Resolve<IDynamicPatch[]>())
|
||||
{
|
||||
Log.Debug($"Restoring dynamic patch {patch.GetType().Name}");
|
||||
patch.Restore(harmony);
|
||||
}
|
||||
|
||||
isApplied = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void Initialize()
|
||||
{
|
||||
Optional.ApplyHasValueCondition<UnityEngine.Object>(o => (bool)o);
|
||||
|
||||
if (container != null)
|
||||
{
|
||||
throw new Exception($"Patches have already been detected! Call {nameof(Apply)} or {nameof(Restore)} instead.");
|
||||
}
|
||||
Log.Info("Registering dependencies");
|
||||
container = CreatePatchingContainer();
|
||||
try
|
||||
{
|
||||
NitroxServiceLocator.InitializeDependencyContainer(new ClientAutoFacRegistrar());
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
Log.Error($"Failed to load one or more dependency types for Nitrox. Assembly: {ex.Types.FirstOrDefault()?.Assembly.FullName ?? "unknown"}");
|
||||
foreach (Exception loaderEx in ex.LoaderExceptions)
|
||||
{
|
||||
Log.Error(loaderEx);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while initializing and loading dependencies.");
|
||||
throw;
|
||||
}
|
||||
|
||||
InitPatches();
|
||||
ApplyNitroxBehaviours();
|
||||
}
|
||||
|
||||
private static void InitPatches()
|
||||
{
|
||||
Log.Info("Patching Subnautica...");
|
||||
|
||||
// Enabling this creates a log file on your desktop (why there?), showing the emitted IL instructions.
|
||||
HarmonyFileLog.Enabled = false;
|
||||
|
||||
foreach (IPersistentPatch patch in container.Resolve<IEnumerable<IPersistentPatch>>())
|
||||
{
|
||||
Log.Debug($"Applying persistent patch {patch.GetType().Name}");
|
||||
patch.Patch(harmony);
|
||||
}
|
||||
|
||||
Multiplayer.OnBeforeMultiplayerStart += Apply;
|
||||
Multiplayer.OnAfterMultiplayerEnd += Restore;
|
||||
Log.Info("Completed patching");
|
||||
}
|
||||
|
||||
private static IContainer CreatePatchingContainer()
|
||||
{
|
||||
ContainerBuilder builder = new();
|
||||
builder.RegisterModule(new NitroxPatchesModule());
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
private static void ApplyNitroxBehaviours()
|
||||
{
|
||||
Log.Info("Applying Nitrox behaviours..");
|
||||
|
||||
GameObject nitroxRoot = new();
|
||||
nitroxRoot.name = "Nitrox";
|
||||
nitroxRoot.AddComponent<NitroxBootstrapper>();
|
||||
|
||||
Log.Info("Behaviours applied.");
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.PlayerLogic;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Allow creatures to choose remote players as targets only if they can be attacked (<see cref="RemotePlayer.CanBeAttacked"/>)
|
||||
/// </summary>
|
||||
public sealed partial class AggressiveWhenSeeTarget_IsTargetValid_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((AggressiveWhenSeeTarget t) => t.IsTargetValid(default(GameObject)));
|
||||
|
||||
public static bool Prefix(GameObject target, ref bool __result)
|
||||
{
|
||||
if (!target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// We only want to cancel if the target is a remote player which can't be attacked
|
||||
if (target.TryGetComponent(out RemotePlayerIdentifier remotePlayerIdentifier) &&
|
||||
!remotePlayerIdentifier.RemotePlayer.CanBeAttacked())
|
||||
{
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class AggressiveWhenSeeTarget_ScanForAggressionTarget_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((AggressiveWhenSeeTarget t) => t.ScanForAggressionTarget());
|
||||
|
||||
public static bool Prefix(AggressiveWhenSeeTarget __instance)
|
||||
{
|
||||
if (!__instance.TryGetNitroxId(out NitroxId creatureId) ||
|
||||
Resolve<SimulationOwnership>().HasAnyLockType(creatureId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Debug.DrawLine(aggressionTarget.transform.position, base.transform.position, Color.white);
|
||||
* this.creature.Aggression.Add(num6);
|
||||
* BroadcastTargetChange(this, aggressionTarget); <--- [INSERTED LINE]
|
||||
* this.lastTarget.SetTarget(aggressionTarget);
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions).MatchStartForward(new CodeMatch(OpCodes.Callvirt, Reflect.Method((CreatureTrait t) => t.Add(default))))
|
||||
.Advance(1)
|
||||
.InsertAndAdvance([
|
||||
new CodeInstruction(OpCodes.Ldarg_0),
|
||||
new CodeInstruction(OpCodes.Ldloc_0),
|
||||
new CodeInstruction(OpCodes.Call, Reflect.Method(() => BroadcastTargetChange(default, default)))
|
||||
]).InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void BroadcastTargetChange(AggressiveWhenSeeTarget aggressiveWhenSeeTarget, GameObject aggressionTarget)
|
||||
{
|
||||
if (!Resolve<AI>().IsCreatureWhitelisted(aggressiveWhenSeeTarget.creature))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the function was called to this point, either it'll return because it doesn't have an id or it'll be evident that we have ownership over the aggressive creature
|
||||
LastTarget lastTarget = aggressiveWhenSeeTarget.lastTarget;
|
||||
// If there's already (likely another) locked target, we get its id over aggressionTarget
|
||||
GameObject realTarget = lastTarget.targetLocked ? lastTarget.target : aggressionTarget;
|
||||
|
||||
if (realTarget && realTarget.TryGetNitroxId(out NitroxId targetId) &&
|
||||
aggressiveWhenSeeTarget.TryGetNitroxId(out NitroxId creatureId))
|
||||
{
|
||||
float aggressionAmount = aggressiveWhenSeeTarget.creature.Aggression.Value;
|
||||
|
||||
Resolve<IPacketSender>().Send(new AggressiveWhenSeeTargetChanged(creatureId, targetId, lastTarget.targetLocked, aggressionAmount));
|
||||
}
|
||||
}
|
||||
}
|
19
NitroxPatcher/Patches/Dynamic/Application_IsFocused_Patch.cs
Normal file
19
NitroxPatcher/Patches/Dynamic/Application_IsFocused_Patch.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Once multiplayer is initiated, prevent game logic from sleeping (i.e. freezing).
|
||||
/// </summary>
|
||||
public sealed partial class Application_IsFocused_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Property(() => Application.isFocused).GetMethod;
|
||||
|
||||
public static bool Prefix(ref bool __result)
|
||||
{
|
||||
__result = true;
|
||||
return false;
|
||||
}
|
||||
}
|
14
NitroxPatcher/Patches/Dynamic/ArmsController_Start_Patch.cs
Normal file
14
NitroxPatcher/Patches/Dynamic/ArmsController_Start_Patch.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class ArmsController_Start_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((ArmsController t) => t.Start());
|
||||
|
||||
public static void Postfix(ArmsController __instance)
|
||||
{
|
||||
__instance.Reconfigure(null);
|
||||
}
|
||||
}
|
28
NitroxPatcher/Patches/Dynamic/ArmsController_Update_Patch.cs
Normal file
28
NitroxPatcher/Patches/Dynamic/ArmsController_Update_Patch.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class ArmsController_Update_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((ArmsController t) => t.Update());
|
||||
|
||||
public static bool Prefix(ArmsController __instance)
|
||||
{
|
||||
if (__instance.smoothSpeedAboveWater == 0)
|
||||
{
|
||||
if (__instance.reconfigureWorldTarget)
|
||||
{
|
||||
__instance.Reconfigure(null);
|
||||
__instance.reconfigureWorldTarget = false;
|
||||
}
|
||||
|
||||
__instance.leftAim.Update(__instance.ikToggleTime);
|
||||
__instance.rightAim.Update(__instance.ikToggleTime);
|
||||
__instance.UpdateHandIKWeights();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Cancels <see cref="AttackCyclops.OnCollisionEnter"/> on players not simulating the creature.
|
||||
/// Replaces the bad cyclops detection to also find out about remote players in the collisioned cyclops.
|
||||
/// </summary>
|
||||
public sealed partial class AttackCyclops_OnCollisionEnter_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((AttackCyclops t) => t.OnCollisionEnter(default));
|
||||
|
||||
public static bool Prefix(AttackCyclops __instance)
|
||||
{
|
||||
return !__instance.TryGetNitroxId(out NitroxId creatureId) ||
|
||||
Resolve<SimulationOwnership>().HasAnyLockType(creatureId);
|
||||
}
|
||||
|
||||
/*
|
||||
* REPLACE:
|
||||
* if (Player.main != null && Player.main.currentSub != null && Player.main.currentSub.isCyclops && Player.main.currentSub.gameObject == collision.gameObject)
|
||||
* {
|
||||
* if (this.isActive)
|
||||
* BY:
|
||||
* if (AttackCyclops_OnCollisionEnter_Patch.ShouldCollisionAnnoyCreature(collision))
|
||||
* {
|
||||
* if (this.isActive)
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions).Advance(1)
|
||||
.RemoveInstructions(19)
|
||||
.InsertAndAdvance([
|
||||
new CodeInstruction(OpCodes.Ldarg_1),
|
||||
new CodeInstruction(OpCodes.Call, Reflect.Method(() => ShouldCollisionAnnoyCreature(default)))
|
||||
]).InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static bool ShouldCollisionAnnoyCreature(Collision collision)
|
||||
{
|
||||
return AttackCyclops_UpdateAggression_Patch.IsTargetAValidInhabitedCyclops(collision.gameObject);
|
||||
}
|
||||
}
|
@@ -0,0 +1,119 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours.Cyclops;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Patch responsible for broadcasting <see cref="AttackCyclops"/>' latest data and cancelling <see cref="AttackCyclops.UpdateAggression"/> on players not simulating the creature.
|
||||
/// </summary>
|
||||
public sealed partial class AttackCyclops_UpdateAggression_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((AttackCyclops t) => t.UpdateAggression());
|
||||
|
||||
// TODO: Sync attacksub command
|
||||
|
||||
public static bool Prefix(AttackCyclops __instance)
|
||||
{
|
||||
return !__instance.TryGetNitroxId(out NitroxId creatureId) ||
|
||||
Resolve<SimulationOwnership>().HasAnyLockType(creatureId);
|
||||
}
|
||||
|
||||
/*
|
||||
* REPLACE:
|
||||
* if (Player.main != null && Player.main.currentSub != null && Player.main.currentSub.isCyclops)
|
||||
* {
|
||||
* cyclopsNoiseManager = Player.main.currentSub.noiseManager;
|
||||
* }
|
||||
* else if (this.forcedNoiseManager != null)
|
||||
* {
|
||||
* cyclopsNoiseManager = this.forcedNoiseManager;
|
||||
* }
|
||||
* BY:
|
||||
* cyclopsNoiseManager = AttackCyclops_UpdateAggression_Patch.FindClosestCyclopsNoiseManagerIfAny(this);
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions).MatchStartForward([
|
||||
new CodeMatch(OpCodes.Ldsfld),
|
||||
new CodeMatch(OpCodes.Ldnull),
|
||||
new CodeMatch(OpCodes.Call),
|
||||
new CodeMatch(OpCodes.Brfalse),
|
||||
new CodeMatch(OpCodes.Ldsfld),
|
||||
new CodeMatch(OpCodes.Callvirt, Reflect.Property((Player t) => t.currentSub).GetGetMethod())
|
||||
]).RemoveInstructions(25)
|
||||
.InsertAndAdvance([
|
||||
new CodeInstruction(OpCodes.Ldarg_0),
|
||||
new CodeInstruction(OpCodes.Call, Reflect.Method(() => FindClosestCyclopsNoiseManagerIfAny(default)))
|
||||
]).InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void Postfix(AttackCyclops __instance)
|
||||
{
|
||||
if (__instance.currentTarget && __instance.currentTarget.TryGetNitroxId(out NitroxId targetId) &&
|
||||
__instance.TryGetNitroxId(out NitroxId creatureId) && Resolve<SimulationOwnership>().HasAnyLockType(creatureId))
|
||||
{
|
||||
float aggressiveToNoise = __instance.aggressiveToNoise.Value;
|
||||
|
||||
Resolve<IPacketSender>().Send(new AttackCyclopsTargetChanged(creatureId, targetId, aggressiveToNoise));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is executed every 0.5s for each spawned leviathan so we can focus on optimization
|
||||
/// </summary>
|
||||
public static CyclopsNoiseManager FindClosestCyclopsNoiseManagerIfAny(AttackCyclops attackCyclops)
|
||||
{
|
||||
// Limit for optimization
|
||||
if (NitroxCyclops.ScaledNoiseByCyclops.Count > 100)
|
||||
{
|
||||
// Cyclops are marked with EcoTargetType.Whale
|
||||
IEcoTarget ecoTarget = EcoRegionManager.main.FindNearestTarget(EcoTargetType.Whale, attackCyclops.transform.position, IsTargetAValidInhabitedCyclops, 3);
|
||||
if (ecoTarget == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ecoTarget.GetGameObject().GetComponent<CyclopsNoiseManager>();
|
||||
}
|
||||
|
||||
float minDistance = float.MaxValue;
|
||||
NitroxCyclops closest = null;
|
||||
foreach (KeyValuePair<NitroxCyclops, float> cyclopsEntry in NitroxCyclops.ScaledNoiseByCyclops)
|
||||
{
|
||||
if (cyclopsEntry.Key.Pawns.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculations from the "if (closestDecoy != null || cyclopsNoiseManager != null)" part
|
||||
float distance = Vector3.Distance(cyclopsEntry.Key.transform.position, attackCyclops.transform.position);
|
||||
|
||||
if (distance < cyclopsEntry.Value && distance < minDistance)
|
||||
{
|
||||
minDistance = distance;
|
||||
closest = cyclopsEntry.Key;
|
||||
}
|
||||
}
|
||||
|
||||
return closest.AliveOrNull()?.GetComponent<CyclopsNoiseManager>();
|
||||
}
|
||||
|
||||
public static bool IsTargetAValidInhabitedCyclops(IEcoTarget target)
|
||||
{
|
||||
return IsTargetAValidInhabitedCyclops(target.GetGameObject());
|
||||
}
|
||||
|
||||
public static bool IsTargetAValidInhabitedCyclops(GameObject targetObject)
|
||||
{
|
||||
return targetObject.TryGetComponent(out NitroxCyclops nitroxCyclops) && nitroxCyclops.Pawns.Count > 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.PlayerLogic;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Allows creatures to choose remote players as targets only if they can be attacked (<see cref="RemotePlayer.CanBeAttacked"/>)
|
||||
/// </summary>
|
||||
public sealed partial class AttackLastTarget_CanAttackTarget_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((AttackLastTarget t) => t.CanAttackTarget(default));
|
||||
|
||||
public static bool Prefix(GameObject target, ref bool __result)
|
||||
{
|
||||
if (!target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// We only want to cancel if the target is a remote player which can't be attacked
|
||||
if (target.TryGetComponent(out RemotePlayerIdentifier remotePlayerIdentifier) &&
|
||||
(!remotePlayerIdentifier.RemotePlayer.LiveMixin.IsAlive() || !remotePlayerIdentifier.RemotePlayer.CanBeAttacked()))
|
||||
{
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
19
NitroxPatcher/Patches/Dynamic/AuroraWarnings_Update_Patch.cs
Normal file
19
NitroxPatcher/Patches/Dynamic/AuroraWarnings_Update_Patch.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <remarks>
|
||||
/// Prevents <see cref="AuroraWarnings.Update"/> from occurring before initial sync has completed.
|
||||
/// It lets us avoid a very weird edge case in which warnings are triggered way too early. Linked to <see cref="CrashedShipExploder_Update_Patch"/>
|
||||
/// </remarks>
|
||||
public sealed partial class AuroraWarnings_Update_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((AuroraWarnings t) => t.Update());
|
||||
|
||||
public static bool Prefix()
|
||||
{
|
||||
return Multiplayer.Main && Multiplayer.Main.InitialSyncCompleted;
|
||||
}
|
||||
}
|
@@ -0,0 +1,201 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.GameLogic.Spawning.Bases;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic.Bases;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Bases;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxPatcher.PatternMatching;
|
||||
using UnityEngine;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
using static NitroxClient.GameLogic.Bases.BuildingHandler;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class BaseDeconstructable_Deconstruct_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((BaseDeconstructable t) => t.Deconstruct());
|
||||
|
||||
private static TemporaryBuildData Temp => BuildingHandler.Main.Temp;
|
||||
private static BuildPieceIdentifier cachedPieceIdentifier;
|
||||
|
||||
public static readonly InstructionsPattern BaseDeconstructInstructionPattern1 = new()
|
||||
{
|
||||
Callvirt,
|
||||
Call,
|
||||
Ldloc_3,
|
||||
{ new() { OpCode = Callvirt, Operand = new(nameof(BaseGhost), nameof(BaseGhost.ClearTargetBase)) }, "Insert1" }
|
||||
};
|
||||
public static readonly InstructionsPattern BaseDeconstructInstructionPattern2 = new()
|
||||
{
|
||||
Ldloc_0,
|
||||
new() { OpCode = Callvirt, Operand = new(nameof(Base), nameof(Base.FixCorridorLinks)) },
|
||||
Ldloc_0,
|
||||
{ new() { OpCode = Callvirt, Operand = new(nameof(Base), nameof(Base.RebuildGeometry)) }, "Insert2" },
|
||||
};
|
||||
|
||||
public static IEnumerable<CodeInstruction> InstructionsToAdd(bool destroyed)
|
||||
{
|
||||
yield return new(Ldarg_0);
|
||||
yield return new(Ldloc_2);
|
||||
yield return new(Ldloc_0);
|
||||
yield return new(destroyed ? Ldc_I4_1 : Ldc_I4_0);
|
||||
yield return new(Call, Reflect.Method(() => PieceDeconstructed(default, default, default, default)));
|
||||
}
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions) =>
|
||||
instructions.Transform(BaseDeconstructInstructionPattern1, (label, instruction) =>
|
||||
{
|
||||
if (label.Equals("Insert1"))
|
||||
{
|
||||
return InstructionsToAdd(true);
|
||||
}
|
||||
return null;
|
||||
}).Transform(BaseDeconstructInstructionPattern2, (label, instruction) =>
|
||||
{
|
||||
if (label.Equals("Insert2"))
|
||||
{
|
||||
return InstructionsToAdd(false);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
public static void Prefix(BaseDeconstructable __instance)
|
||||
{
|
||||
BuildUtils.TryGetIdentifier(__instance, out cachedPieceIdentifier, null, __instance.face);
|
||||
}
|
||||
|
||||
public static void PieceDeconstructed(BaseDeconstructable baseDeconstructable, ConstructableBase constructableBase, Base @base, bool destroyed)
|
||||
{
|
||||
if (!@base.TryGetNitroxId(out NitroxId baseId))
|
||||
{
|
||||
Log.Error("Couldn't find NitroxEntity on a deconstructed base, which is really problematic");
|
||||
return;
|
||||
}
|
||||
|
||||
GhostEntity ghostEntity = GhostEntitySpawner.From(constructableBase);
|
||||
ghostEntity.Id = baseId;
|
||||
if (destroyed)
|
||||
{
|
||||
// Base was destroyed and replaced with a simple ghost
|
||||
Log.Verbose("Transferring id from base to the new ghost");
|
||||
NitroxEntity.SetNewId(constructableBase.gameObject, baseId);
|
||||
|
||||
Log.Verbose("Base destroyed and replaced by a simple ghost");
|
||||
Resolve<IPacketSender>().Send(new BaseDeconstructed(baseId, ghostEntity));
|
||||
return;
|
||||
}
|
||||
if (!baseDeconstructable.GetComponentInParent<BaseCell>())
|
||||
{
|
||||
Log.Error("Couldn't find a BaseCell parent to the BaseDeconstructable");
|
||||
return;
|
||||
}
|
||||
|
||||
// If deconstruction was ordered by BuildingHandler, then we simply take the provided id
|
||||
if (Temp.Id != null)
|
||||
{
|
||||
// If it had an attached module, we'll also delete the NitroxEntity from the said module similarly to the code below
|
||||
if (NitroxEntity.TryGetObjectFrom(Temp.Id, out GameObject moduleObject) &&
|
||||
moduleObject.TryGetComponent(out IBaseModule baseModule) &&
|
||||
constructableBase.moduleFace.HasValue && constructableBase.moduleFace.Value.Equals(baseModule.moduleFace))
|
||||
{
|
||||
Object.Destroy(moduleObject.GetComponent<NitroxEntity>());
|
||||
}
|
||||
else if (constructableBase.techType.Equals(TechType.BaseMoonpool) && @base.TryGetComponent(out MoonpoolManager moonpoolManager))
|
||||
{
|
||||
moonpoolManager.DeregisterMoonpool(constructableBase.transform);
|
||||
}
|
||||
|
||||
NitroxEntity.SetNewId(constructableBase.gameObject, Temp.Id);
|
||||
// We don't need to go any further
|
||||
return;
|
||||
}
|
||||
|
||||
NitroxId pieceId = null;
|
||||
// If the destructed piece has an attached module, we'll transfer the NitroxEntity from it
|
||||
if (constructableBase.moduleFace.HasValue)
|
||||
{
|
||||
Base.Face moduleFace = constructableBase.moduleFace.Value;
|
||||
moduleFace.cell += @base.GetAnchor();
|
||||
Component geometryObject = @base.GetModule(moduleFace).AliveOrNull();
|
||||
if (geometryObject && geometryObject.TryGetNitroxEntity(out NitroxEntity moduleEntity))
|
||||
{
|
||||
pieceId = moduleEntity.Id;
|
||||
Object.Destroy(moduleEntity);
|
||||
Log.Verbose($"Successfully transferred NitroxEntity from module geometry {moduleEntity.Id}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (constructableBase.techType)
|
||||
{
|
||||
case TechType.BaseMoonpool:
|
||||
if (@base.TryGetComponent(out MoonpoolManager moonpoolManager))
|
||||
{
|
||||
pieceId = moonpoolManager.DeregisterMoonpool(constructableBase.transform); // pieceId can still be null
|
||||
}
|
||||
break;
|
||||
case TechType.BaseMapRoom:
|
||||
Int3 mapRoomFunctionalityCell = BuildUtils.GetMapRoomFunctionalityCell(constructableBase.model.GetComponent<BaseGhost>());
|
||||
MapRoomFunctionality mapRoomFunctionality = @base.GetMapRoomFunctionalityForCell(mapRoomFunctionalityCell);
|
||||
if (mapRoomFunctionality && mapRoomFunctionality.TryGetNitroxId(out NitroxId mapRoomId))
|
||||
{
|
||||
pieceId = mapRoomId;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("Either couldn't find a MapRoomFunctionality associated with destroyed piece or couldn't find a NitroxEntity onto it.");
|
||||
}
|
||||
break;
|
||||
case TechType.BaseWaterPark:
|
||||
// When a BaseWaterPark doesn't have a moduleFace, it means that there's still another WaterPark so we don't need to destroy its id and it won't be an error
|
||||
break;
|
||||
default:
|
||||
if (baseDeconstructable.GetComponent<IBaseModuleGeometry>() != null)
|
||||
{
|
||||
Log.Error("Couldn't find the module's GameObject of IBaseModuleGeometry when transferring the NitroxEntity");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Else, if it's a local client deconstruction, we generate a new one
|
||||
pieceId ??= new();
|
||||
NitroxEntity.SetNewId(constructableBase.gameObject, pieceId);
|
||||
ghostEntity.Id = pieceId;
|
||||
ghostEntity.ParentId = baseId;
|
||||
|
||||
if (cachedPieceIdentifier == default)
|
||||
{
|
||||
BuildingHandler.Main.EnsureTracker(baseId).FailedOperations++;
|
||||
Log.Error($"[{nameof(PieceDeconstructed)}] Couldn't find a CachedPieceIdentifier for deconstructed object {constructableBase.gameObject}");
|
||||
return;
|
||||
}
|
||||
|
||||
BuildingHandler.Main.EnsureTracker(baseId).LocalOperations++;
|
||||
int operationId = BuildingHandler.Main.GetCurrentOperationIdOrDefault(baseId);
|
||||
|
||||
PieceDeconstructed pieceDeconstructed;
|
||||
if (Temp.MovedChildrenIdsByNewHostId != null)
|
||||
{
|
||||
pieceDeconstructed = new LargeWaterParkDeconstructed(baseId, pieceId, cachedPieceIdentifier, ghostEntity, BuildEntitySpawner.GetBaseData(@base), Temp.MovedChildrenIdsByNewHostId, operationId);
|
||||
}
|
||||
else
|
||||
{
|
||||
pieceDeconstructed = Temp.NewWaterPark == null ?
|
||||
new PieceDeconstructed(baseId, pieceId, cachedPieceIdentifier, ghostEntity, BuildEntitySpawner.GetBaseData(@base), operationId) :
|
||||
new WaterParkDeconstructed(baseId, pieceId, cachedPieceIdentifier, ghostEntity, BuildEntitySpawner.GetBaseData(@base), Temp.NewWaterPark, Temp.MovedChildrenIds, Temp.Transfer, operationId);
|
||||
}
|
||||
|
||||
Log.Verbose($"Base is not empty, sending packet {pieceDeconstructed}");
|
||||
|
||||
Resolve<IPacketSender>().Send(pieceDeconstructed);
|
||||
|
||||
Temp.Dispose();
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Helper;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class BaseDeconstructable_DeconstructionAllowed_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((BaseDeconstructable t) => t.DeconstructionAllowed(out Reflect.Ref<string>.Field));
|
||||
|
||||
public static void Postfix(BaseDeconstructable __instance, ref bool __result, ref string reason)
|
||||
{
|
||||
if (!__result || !BuildingHandler.Main || !__instance.deconstructedBase.TryGetComponent(out NitroxEntity parentEntity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Constructable_DeconstructionAllowed_Patch.DeconstructionAllowed(parentEntity.Id, ref __result, ref reason);
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Bases;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class BaseHullStrength_CrushDamageUpdate_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((BaseHullStrength t) => t.CrushDamageUpdate());
|
||||
|
||||
public static bool Prefix(BaseHullStrength __instance)
|
||||
{
|
||||
return __instance.TryGetNitroxId(out NitroxId baseId) && Resolve<SimulationOwnership>().HasAnyLockType(baseId);
|
||||
}
|
||||
|
||||
/*
|
||||
* }
|
||||
* ErrorMessage.AddMessage(Language.main.GetFormat<float>("BaseHullStrDamageDetected", this.totalStrength));
|
||||
* BroadcastChange(this, random); <------ Inserted line
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
// We add instructions right before the ret which is equivalent to inserting at last offset
|
||||
return new CodeMatcher(instructions).End()
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldarg_0))
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_0))
|
||||
.Insert(new CodeInstruction(OpCodes.Call, Reflect.Method(() => BroadcastChange(default, default))))
|
||||
.InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void BroadcastChange(BaseHullStrength baseHullStrength, LiveMixin liveMixin)
|
||||
{
|
||||
if (!baseHullStrength.TryGetNitroxId(out NitroxId baseId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
BaseCell baseCell = liveMixin.GetComponent<BaseCell>();
|
||||
Int3 relativeCell = baseCell.cell - baseHullStrength.baseComp.anchor;
|
||||
BaseLeakManager baseLeakManager = baseHullStrength.gameObject.EnsureComponent<BaseLeakManager>();
|
||||
if (!baseLeakManager.TryGetAbsoluteCellId(baseCell.cell, out NitroxId leakId))
|
||||
{
|
||||
leakId = new();
|
||||
}
|
||||
baseLeakManager.EnsureLeak(relativeCell, leakId, liveMixin.health);
|
||||
|
||||
if (!Resolve<LiveMixinManager>().IsRemoteHealthChanging)
|
||||
{
|
||||
BaseLeakEntity baseLeakEntity = new(liveMixin.health, relativeCell.ToDto(), leakId, baseId);
|
||||
Resolve<Entities>().BroadcastEntitySpawnedByClient(baseLeakEntity, true);
|
||||
}
|
||||
}
|
||||
}
|
21
NitroxPatcher/Patches/Dynamic/Base_OnPreDestroy_Patch.cs
Normal file
21
NitroxPatcher/Patches/Dynamic/Base_OnPreDestroy_Patch.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic.PlayerLogic;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Before a base gets destroyed, we eventually detach/exit any remote player's object that would be inside so that their GameObjects don't get destroyed
|
||||
/// </summary>
|
||||
public sealed partial class Base_OnPreDestroy_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Base t) => t.OnPreDestroy());
|
||||
|
||||
public static void Prefix(Base __instance)
|
||||
{
|
||||
foreach (RemotePlayerIdentifier remotePlayerIdentifier in __instance.GetComponentsInChildren<RemotePlayerIdentifier>(true))
|
||||
{
|
||||
remotePlayerIdentifier.RemotePlayer.ResetStates();
|
||||
}
|
||||
}
|
||||
}
|
25
NitroxPatcher/Patches/Dynamic/BeaconLabel_SetLabel_Patch.cs
Normal file
25
NitroxPatcher/Patches/Dynamic/BeaconLabel_SetLabel_Patch.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a callback to broadcast beacon label change when edited.
|
||||
/// </summary>
|
||||
public sealed partial class BeaconLabel_OnHandClick_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((BeaconLabel t) => t.OnHandClick(default));
|
||||
|
||||
public static void Postfix(BeaconLabel __instance)
|
||||
{
|
||||
uGUI.main.userInput.callback += _ =>
|
||||
{
|
||||
if (__instance.transform.parent && __instance.transform.parent.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Entities>().EntityMetadataChanged(__instance.transform.parent.GetComponent<Beacon>(), id);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
17
NitroxPatcher/Patches/Dynamic/Bed_EnterInUseMode_Patch.cs
Normal file
17
NitroxPatcher/Patches/Dynamic/Bed_EnterInUseMode_Patch.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class Bed_EnterInUseMode_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Bed t) => t.EnterInUseMode(default(Player)));
|
||||
|
||||
public static void Postfix()
|
||||
{
|
||||
Resolve<IPacketSender>().Send(new BedEnter());
|
||||
}
|
||||
}
|
43
NitroxPatcher/Patches/Dynamic/Bench_ExitSittingMode_Patch.cs
Normal file
43
NitroxPatcher/Patches/Dynamic/Bench_ExitSittingMode_Patch.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.ChatUI;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class Bench_ExitSittingMode_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Bench t) => t.ExitSittingMode(default, default));
|
||||
|
||||
public static void Prefix(ref bool __runOriginal)
|
||||
{
|
||||
__runOriginal = !Resolve<PlayerChatManager>().IsChatSelected && !DevConsole.instance.selected;
|
||||
}
|
||||
|
||||
public static void Postfix(Bench __instance, bool __runOriginal)
|
||||
{
|
||||
if (!__runOriginal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (__instance.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
// Request to be downgraded to a transient lock so we can still simulate the positioning.
|
||||
Resolve<SimulationOwnership>().RequestSimulationLock(id, SimulationLockType.TRANSIENT);
|
||||
|
||||
Resolve<LocalPlayer>().AnimationChange(AnimChangeType.BENCH, AnimChangeState.OFF);
|
||||
__instance.StartCoroutine(ResetAnimationDelayed(__instance.standUpCinematicController.interpolationTimeOut));
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerator ResetAnimationDelayed(float delay)
|
||||
{
|
||||
yield return new WaitForSeconds(delay);
|
||||
Resolve<LocalPlayer>().AnimationChange(AnimChangeType.BENCH, AnimChangeState.UNSET);
|
||||
}
|
||||
}
|
59
NitroxPatcher/Patches/Dynamic/Bench_OnHandClick_Patch.cs
Normal file
59
NitroxPatcher/Patches/Dynamic/Bench_OnHandClick_Patch.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.Simulation;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.MonoBehaviours.Gui.HUD;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class Bench_OnHandClick_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Bench t) => t.OnHandClick(default(GUIHand)));
|
||||
private static bool skipPrefix;
|
||||
|
||||
public static bool Prefix(Bench __instance, GUIHand hand)
|
||||
{
|
||||
if (skipPrefix)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!__instance.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Resolve<SimulationOwnership>().HasExclusiveLock(id))
|
||||
{
|
||||
Log.Debug($"Already have an exclusive lock on the bench/chair: {id}");
|
||||
return true;
|
||||
}
|
||||
|
||||
HandInteraction<Bench> context = new(__instance, hand);
|
||||
LockRequest<HandInteraction<Bench>> lockRequest = new(id, SimulationLockType.EXCLUSIVE, ReceivedSimulationLockResponse, context);
|
||||
|
||||
Resolve<SimulationOwnership>().RequestSimulationLock(lockRequest);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ReceivedSimulationLockResponse(NitroxId id, bool lockAcquired, HandInteraction<Bench> context)
|
||||
{
|
||||
Bench bench = context.Target;
|
||||
|
||||
if (lockAcquired)
|
||||
{
|
||||
skipPrefix = true;
|
||||
bench.OnHandClick(context.GuiHand);
|
||||
Resolve<LocalPlayer>().AnimationChange(AnimChangeType.BENCH, AnimChangeState.ON);
|
||||
skipPrefix = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bench.gameObject.AddComponent<DenyOwnershipHand>();
|
||||
bench.isValidHandTarget = false;
|
||||
}
|
||||
}
|
||||
}
|
23
NitroxPatcher/Patches/Dynamic/Bench_OnPlayerDeath_Patch.cs
Normal file
23
NitroxPatcher/Patches/Dynamic/Bench_OnPlayerDeath_Patch.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class Bench_OnPlayerDeath_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Bench t) => t.OnPlayerDeath(default(Player)));
|
||||
|
||||
public static void Postfix(Bench __instance)
|
||||
{
|
||||
if (__instance.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
// Request to be downgraded to a transient lock so we can still simulate the positioning.
|
||||
Resolve<SimulationOwnership>().RequestSimulationLock(id, SimulationLockType.TRANSIENT);
|
||||
}
|
||||
|
||||
Resolve<LocalPlayer>().AnimationChange(AnimChangeType.BENCH, AnimChangeState.UNSET);
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class BreakableResource_BreakIntoResources_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static MethodInfo TARGET_METHOD = Reflect.Method((BreakableResource t) => t.BreakIntoResources());
|
||||
|
||||
public static void Prefix(BreakableResource __instance)
|
||||
{
|
||||
if (!__instance.TryGetNitroxId(out NitroxId destroyedId))
|
||||
{
|
||||
Log.Warn($"[{nameof(BreakableResource_BreakIntoResources_Patch)}] Could not find {nameof(NitroxEntity)} for breakable entity {__instance.gameObject.GetFullHierarchyPath()}.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Case by case handling
|
||||
|
||||
// Sea Treaders spawn resource chunks but we don't register them on server-side as they're auto destroyed after 60s
|
||||
// So we need to broadcast their deletion differently
|
||||
if (__instance.GetComponent<SinkingGroundChunk>())
|
||||
{
|
||||
Resolve<IPacketSender>().Send(new SeaTreaderChunkPickedUp(destroyedId));
|
||||
}
|
||||
// Generic case
|
||||
else
|
||||
{
|
||||
Resolve<IPacketSender>().Send(new EntityDestroyed(destroyedId));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxPatcher.PatternMatching;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes entities that can be broken and that will drop material, such as limestones...
|
||||
/// </summary>
|
||||
public sealed partial class BreakableResource_SpawnResourceFromPrefab_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method(() => BreakableResource.SpawnResourceFromPrefab(default, default, default)));
|
||||
|
||||
private static readonly InstructionsPattern SpawnResFromPrefPattern = new()
|
||||
{
|
||||
{ Reflect.Method((Rigidbody b) => b.AddForce(default(Vector3))), "DropItemInstance" },
|
||||
Ldc_I4_0
|
||||
};
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return instructions.InsertAfterMarker(SpawnResFromPrefPattern, "DropItemInstance", new CodeInstruction[]
|
||||
{
|
||||
new(Ldloc_1),
|
||||
new(Call, ((Action<GameObject>)Callback).Method)
|
||||
});
|
||||
}
|
||||
|
||||
private static void Callback(GameObject __instance)
|
||||
{
|
||||
NitroxEntity.SetNewId(__instance, new());
|
||||
Resolve<Items>().Dropped(__instance);
|
||||
}
|
||||
}
|
32
NitroxPatcher/Patches/Dynamic/BuilderTool_Construct_Patch.cs
Normal file
32
NitroxPatcher/Patches/Dynamic/BuilderTool_Construct_Patch.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.Helpers;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class BuilderTool_Construct_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((BuilderTool t) => t.Construct(default, default, default));
|
||||
|
||||
public static bool Prefix(Constructable c)
|
||||
{
|
||||
if (!BuildingHandler.Main || !c.tr.parent || !c.tr.parent.TryGetNitroxId(out NitroxId parentId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isAllowed = true;
|
||||
string message = string.Empty;
|
||||
|
||||
Constructable_DeconstructionAllowed_Patch.DeconstructionAllowed(parentId, ref isAllowed, ref message);
|
||||
if (!isAllowed)
|
||||
{
|
||||
Log.InGame(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
106
NitroxPatcher/Patches/Dynamic/Builder_TryPlace_Patch.cs
Normal file
106
NitroxPatcher/Patches/Dynamic/Builder_TryPlace_Patch.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic.Spawning.Bases;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.MonoBehaviours.Cyclops;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Bases;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxPatcher.PatternMatching;
|
||||
using UnityEngine;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class Builder_TryPlace_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => Builder.TryPlace());
|
||||
|
||||
public static readonly InstructionsPattern AddInstructionPattern1 = new()
|
||||
{
|
||||
Ldloc_0,
|
||||
Ldc_I4_0,
|
||||
Ldc_I4_1,
|
||||
new() { OpCode = Callvirt, Operand = new(nameof(Constructable), nameof(Constructable.SetState)) },
|
||||
{ Pop, "Insert1" }
|
||||
};
|
||||
|
||||
public static readonly List<CodeInstruction> InstructionsToAdd1 = new()
|
||||
{
|
||||
new(Ldloc_0),
|
||||
new(Call, Reflect.Method(() => GhostCreated(default)))
|
||||
};
|
||||
|
||||
public static readonly InstructionsPattern AddInstructionPattern2 = new()
|
||||
{
|
||||
Ldloc_S,
|
||||
Ldloc_3,
|
||||
Ldloc_S,
|
||||
Or,
|
||||
{ new() { OpCode = Callvirt, Operand = new(nameof(Constructable), nameof(Constructable.SetIsInside)) }, "Insert2" }
|
||||
};
|
||||
|
||||
public static readonly List<CodeInstruction> InstructionsToAdd2 = new()
|
||||
{
|
||||
TARGET_METHOD.Ldloc<Constructable>(),
|
||||
new(Call, Reflect.Method(() => GhostCreated(default)))
|
||||
};
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions) =>
|
||||
instructions.Transform(AddInstructionPattern1, (label, instruction) =>
|
||||
{
|
||||
if (label.Equals("Insert1"))
|
||||
{
|
||||
return InstructionsToAdd1;
|
||||
}
|
||||
return null;
|
||||
}).Transform(AddInstructionPattern2, (label, instruction) =>
|
||||
{
|
||||
if (label.Equals("Insert2"))
|
||||
{
|
||||
return InstructionsToAdd2;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
public static void GhostCreated(Constructable constructable)
|
||||
{
|
||||
GameObject ghostObject = constructable.gameObject;
|
||||
|
||||
NitroxId parentId = null;
|
||||
if (ghostObject.TryGetComponentInParent(out SubRoot subRoot, true) && (subRoot.isBase || subRoot.isCyclops) &&
|
||||
subRoot.TryGetNitroxId(out NitroxId entityId))
|
||||
{
|
||||
parentId = entityId;
|
||||
}
|
||||
|
||||
// Assign a NitroxId to the ghost now
|
||||
NitroxId ghostId = new();
|
||||
NitroxEntity.SetNewId(ghostObject, ghostId);
|
||||
if (constructable is ConstructableBase constructableBase)
|
||||
{
|
||||
GhostEntity ghost = GhostEntitySpawner.From(constructableBase);
|
||||
ghost.Id = ghostId;
|
||||
ghost.ParentId = parentId;
|
||||
Resolve<IPacketSender>().Send(new PlaceGhost(ghost));
|
||||
}
|
||||
else
|
||||
{
|
||||
ModuleEntitySpawner.MoveToGlobalRoot(ghostObject);
|
||||
|
||||
ModuleEntity module = ModuleEntitySpawner.From(constructable);
|
||||
module.Id = ghostId;
|
||||
module.ParentId = parentId;
|
||||
Resolve<IPacketSender>().Send(new PlaceModule(module));
|
||||
|
||||
if (constructable.transform.parent && constructable.transform.parent.TryGetComponent(out NitroxCyclops nitroxCyclops) && nitroxCyclops.Virtual)
|
||||
{
|
||||
nitroxCyclops.Virtual.ReplicateConstructable(constructable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
NitroxPatcher/Patches/Dynamic/Builder_Update_Patch.cs
Normal file
44
NitroxPatcher/Patches/Dynamic/Builder_Update_Patch.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.GameLogic.Settings;
|
||||
using NitroxClient.Helpers;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Changes the piece color during the placing process if the current base is desynced.
|
||||
/// </summary>
|
||||
public sealed partial class Builder_Update_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => Builder.Update());
|
||||
private static readonly Color DESYNCED_COLOR = Color.magenta;
|
||||
|
||||
public static void Postfix()
|
||||
{
|
||||
if (!Builder.canPlace || !BuildingHandler.Main || !NitroxPrefs.SafeBuilding.Value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
BaseGhost baseGhost = Builder.ghostModel.GetComponent<BaseGhost>();
|
||||
GameObject parentBase;
|
||||
if (baseGhost && baseGhost.targetBase)
|
||||
{
|
||||
parentBase = baseGhost.targetBase.gameObject;
|
||||
}
|
||||
// In case it's a simple Constructable
|
||||
else
|
||||
{
|
||||
parentBase = Builder.placementTarget;
|
||||
}
|
||||
|
||||
if (parentBase && parentBase.TryGetNitroxId(out NitroxId parentId) &&
|
||||
BuildingHandler.Main.EnsureTracker(parentId).IsDesynced())
|
||||
{
|
||||
Builder.canPlace = false;
|
||||
Builder.ghostStructureMaterial.SetColor(ShaderPropertyID._Tint, DESYNCED_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
65
NitroxPatcher/Patches/Dynamic/Bullet_Update_Patch.cs
Normal file
65
NitroxPatcher/Patches/Dynamic/Bullet_Update_Patch.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
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"/> and prevents remote bullets from detecting collisions
|
||||
/// </summary>
|
||||
public sealed partial class Bullet_Update_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((Bullet t) => t.Update());
|
||||
|
||||
/*
|
||||
* RaycastHit raycastHit;
|
||||
* REPLACE:
|
||||
* if (Physics.SphereCast(this.tr.position, this.shellRadius, this.tr.forward, out raycastHit, num, this.layerMask.value))
|
||||
* {
|
||||
* num = raycastHit.distance;
|
||||
* BY:
|
||||
* if (!Bullet_Update_Patch.IsRemoteObject(this) && Physics.SphereCast(this.tr.position, this.shellRadius, this.tr.forward, out raycastHit, num, this.layerMask.value))
|
||||
* {
|
||||
* num = raycastHit.distance;
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
|
||||
{
|
||||
Label label = generator.DefineLabel();
|
||||
|
||||
// Replace the two occurences of Time.deltaTime
|
||||
return new CodeMatcher(instructions).ReplaceDeltaTime()
|
||||
.ReplaceDeltaTime()
|
||||
.MatchStartForward([
|
||||
new CodeMatch(OpCodes.Ldarg_0),
|
||||
new CodeMatch(OpCodes.Call, Reflect.Property((Bullet t) => t.tr).GetGetMethod()),
|
||||
new CodeMatch(OpCodes.Callvirt, Reflect.Property((Transform t) => t.position).GetGetMethod()),
|
||||
new CodeMatch(OpCodes.Ldarg_0),
|
||||
])
|
||||
// Skip the Ldarg_0 because it is the previous ifs' jump target
|
||||
.Advance(1)
|
||||
// Insert if (!Bullet_Update_Patch.IsRemoteObject(this)) before the condition
|
||||
.InsertAndAdvance([
|
||||
new CodeInstruction(OpCodes.Call, Reflect.Method(() => IsRemoteObject(default))),
|
||||
new CodeInstruction(OpCodes.Brtrue_S, label),
|
||||
new CodeInstruction(OpCodes.Ldarg_0),
|
||||
])
|
||||
// Find the destination of the position to go to
|
||||
.MatchStartForward([
|
||||
new CodeMatch(OpCodes.Ldarg_0),
|
||||
new CodeMatch(OpCodes.Call, Reflect.Property((Bullet t) => t.tr).GetGetMethod()),
|
||||
new CodeMatch(OpCodes.Dup),
|
||||
new CodeMatch(OpCodes.Callvirt, Reflect.Property((Transform t) => t.position).GetGetMethod())
|
||||
])
|
||||
.AddLabels([label])
|
||||
.InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static bool IsRemoteObject(Bullet bullet)
|
||||
{
|
||||
return bullet.GetComponent<BulletManager.RemotePlayerBullet>();
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxPatcher.PatternMatching;
|
||||
using UnityEngine;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class ConstructableBase_SetState_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((ConstructableBase t) => t.SetState(default, default));
|
||||
|
||||
/*
|
||||
* Make it become
|
||||
* if (Builder.CanDestroyObject(gameObject))
|
||||
* {
|
||||
* ConstructableBase_SetState_Patch.BeforeDestroy(gameObject); <==========
|
||||
* UnityEngine.Object.Destroy(gameObject);
|
||||
* }
|
||||
*/
|
||||
public static readonly InstructionsPattern InstructionPattern = new()
|
||||
{
|
||||
new() { OpCode = Call, Operand = new(nameof(Builder), nameof(Builder.CanDestroyObject)) },
|
||||
{ Brfalse, "Insert" }
|
||||
};
|
||||
|
||||
public static readonly List<CodeInstruction> InstructionsToAdd = new()
|
||||
{
|
||||
TARGET_METHOD.Ldloc<GameObject>(),
|
||||
new(Call, Reflect.Method(() => BeforeDestroy(default)))
|
||||
};
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions) =>
|
||||
instructions.Transform(InstructionPattern, (label, instruction) =>
|
||||
{
|
||||
if (label.Equals("Insert"))
|
||||
{
|
||||
return InstructionsToAdd;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
public static void BeforeDestroy(GameObject gameObject)
|
||||
{
|
||||
if (gameObject && gameObject.TryGetNitroxId(out NitroxId nitroxId))
|
||||
{
|
||||
Resolve<IPacketSender>().Send(new EntityDestroyed(nitroxId));
|
||||
}
|
||||
}
|
||||
}
|
203
NitroxPatcher/Patches/Dynamic/Constructable_Construct_Patch.cs
Normal file
203
NitroxPatcher/Patches/Dynamic/Constructable_Construct_Patch.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.GameLogic.Spawning.Bases;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxClient.Helpers;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Bases;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Bases;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxPatcher.PatternMatching;
|
||||
using UnityEngine;
|
||||
using UWE;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
using static NitroxClient.GameLogic.Bases.BuildingHandler;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class Constructable_Construct_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Constructable t) => t.Construct());
|
||||
|
||||
private static TemporaryBuildData Temp => BuildingHandler.Main.Temp;
|
||||
|
||||
public static readonly InstructionsPattern InstructionsPattern = new()
|
||||
{
|
||||
Div,
|
||||
Stfld,
|
||||
Ldc_I4_0,
|
||||
Ret,
|
||||
Ldarg_0,
|
||||
{ InstructionPattern.Call(nameof(Constructable), nameof(Constructable.UpdateMaterial)), "Insert" }
|
||||
};
|
||||
|
||||
public static readonly List<CodeInstruction> InstructionsToAdd = new()
|
||||
{
|
||||
new(Ldarg_0),
|
||||
new(Ldc_I4_1), // True for "constructing"
|
||||
new(Call, Reflect.Method(() => ConstructionAmountModified(default, default)))
|
||||
};
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions) =>
|
||||
instructions.Transform(InstructionsPattern, (label, instruction) =>
|
||||
{
|
||||
if (label.Equals("Insert"))
|
||||
{
|
||||
return InstructionsToAdd;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
public static void ConstructionAmountModified(Constructable constructable, bool constructing)
|
||||
{
|
||||
// We only manage the amount change, not the deconstruction/construction action
|
||||
if (!constructable.TryGetNitroxId(out NitroxId entityId))
|
||||
{
|
||||
Log.ErrorOnce($"[{nameof(ConstructionAmountModified)}] Couldn't find a NitroxEntity on {constructable.name}");
|
||||
return;
|
||||
}
|
||||
float amount = NitroxModel.Helper.Mathf.Clamp01(constructable.constructedAmount);
|
||||
|
||||
// An object is destroyed when amount = 0 AND if we are destructing
|
||||
// so we don't need the broadcast if we are trying to construct with not enough resources (amount = 0)
|
||||
if (amount == 0f && constructing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Different cases:
|
||||
* - Normal module (only Constructable), just let it go to 1.0f normally
|
||||
* - ConstructableBase:
|
||||
* - if it's already in a base, simply update the current base
|
||||
* - else, create a new base
|
||||
*/
|
||||
if (amount == 1f)
|
||||
{
|
||||
Resolve<ThrottledPacketSender>().RemovePendingPackets(entityId);
|
||||
if (constructable is ConstructableBase constructableBase)
|
||||
{
|
||||
CoroutineHost.StartCoroutine(BroadcastObjectBuilt(constructableBase, entityId));
|
||||
return;
|
||||
}
|
||||
IEnumerator postSpawner = BuildingPostSpawner.ApplyPostSpawner(constructable.gameObject, entityId);
|
||||
|
||||
// Can be null if no post spawner is set for the constructable's techtype
|
||||
if (postSpawner != null)
|
||||
{
|
||||
CoroutineHost.StartCoroutine(postSpawner);
|
||||
}
|
||||
// To avoid any unrequired throttled packet to be sent we clean the pending throttled packets for this object
|
||||
Resolve<IPacketSender>().Send(new ModifyConstructedAmount(entityId, 1f));
|
||||
return;
|
||||
}
|
||||
|
||||
// update as a normal module
|
||||
Resolve<ThrottledPacketSender>().SendThrottled(new ModifyConstructedAmount(entityId, amount),
|
||||
(packet) => { return packet.GhostId; }, 0.1f);
|
||||
}
|
||||
|
||||
public static IEnumerator BroadcastObjectBuilt(ConstructableBase constructableBase, NitroxId entityId)
|
||||
{
|
||||
BaseGhost baseGhost = constructableBase.model.GetComponent<BaseGhost>();
|
||||
constructableBase.SetState(true, true);
|
||||
if (!baseGhost.targetBase)
|
||||
{
|
||||
Log.Error("Something wrong happened, couldn't find base after finishing building ghost");
|
||||
yield break;
|
||||
}
|
||||
Base targetBase = baseGhost.targetBase;
|
||||
Base parentBase = null;
|
||||
if (constructableBase.tr.parent)
|
||||
{
|
||||
parentBase = constructableBase.GetComponentInParent<Base>(true);
|
||||
}
|
||||
|
||||
// If a module was spawned we need to transfer the ghost id to it for further recognition
|
||||
BuildUtils.TryTransferIdFromGhostToModule(baseGhost, entityId, constructableBase, out GameObject moduleObject);
|
||||
|
||||
// Have a delay for baseGhost to be actually destroyed
|
||||
yield return null;
|
||||
|
||||
if (parentBase)
|
||||
{
|
||||
// update existing base
|
||||
if (!parentBase.TryGetNitroxId(out NitroxId parentId))
|
||||
{
|
||||
BuildingHandler.Main.FailedOperations++;
|
||||
Log.Error($"[{nameof(BroadcastObjectBuilt)}] Parent base doesn't have a NitroxEntity, which is not normal");
|
||||
yield break;
|
||||
}
|
||||
MoonpoolManager moonpoolManager = parentBase.gameObject.EnsureComponent<MoonpoolManager>();
|
||||
|
||||
GlobalRootEntity builtPiece = null;
|
||||
if (moduleObject)
|
||||
{
|
||||
if (moduleObject.TryGetComponent(out IBaseModule builtModule))
|
||||
{
|
||||
builtPiece = InteriorPieceEntitySpawner.From(builtModule, Resolve<EntityMetadataManager>());
|
||||
}
|
||||
else if (moduleObject.GetComponent<VehicleDockingBay>())
|
||||
{
|
||||
builtPiece = moonpoolManager.LatestRegisteredMoonpool;
|
||||
}
|
||||
else if (moduleObject.TryGetComponent(out MapRoomFunctionality mapRoomFunctionality))
|
||||
{
|
||||
builtPiece = BuildUtils.CreateMapRoomEntityFrom(mapRoomFunctionality, parentBase, entityId, parentId);
|
||||
}
|
||||
}
|
||||
|
||||
SendUpdateBase(parentBase, parentId, entityId, builtPiece, moonpoolManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Must happen before NitroxEntity.SetNewId because else, if a moonpool was marked with the same id, this id be will unlinked from the base object
|
||||
if (baseGhost.targetBase.TryGetComponent(out MoonpoolManager moonpoolManager))
|
||||
{
|
||||
moonpoolManager.LateAssignNitroxEntity(entityId);
|
||||
moonpoolManager.OnPostRebuildGeometry(baseGhost.targetBase);
|
||||
}
|
||||
// create a new base
|
||||
NitroxEntity.SetNewId(baseGhost.targetBase.gameObject, entityId);
|
||||
BuildingHandler.Main.EnsureTracker(entityId).ResetToId();
|
||||
|
||||
Resolve<IPacketSender>().Send(new PlaceBase(entityId, BuildEntitySpawner.From(targetBase, Resolve<EntityMetadataManager>())));
|
||||
}
|
||||
|
||||
if (moduleObject)
|
||||
{
|
||||
yield return BuildingPostSpawner.ApplyPostSpawner(moduleObject, entityId);
|
||||
}
|
||||
|
||||
Temp.Dispose();
|
||||
}
|
||||
|
||||
private static void SendUpdateBase(Base @base, NitroxId baseId, NitroxId pieceId, GlobalRootEntity builtPieceEntity, MoonpoolManager moonpoolManager)
|
||||
{
|
||||
List<Entity> childEntities = BuildUtils.GetChildEntities(@base, baseId, Resolve<EntityMetadataManager>());
|
||||
|
||||
// We get InteriorPieceEntity children from the base and make up a dictionary with their updated data (their BaseFace)
|
||||
Dictionary<NitroxId, NitroxBaseFace> updatedChildren = childEntities.OfType<InteriorPieceEntity>()
|
||||
.ToDictionary(entity => entity.Id, entity => entity.BaseFace);
|
||||
// Same for MapRooms
|
||||
Dictionary<NitroxId, NitroxInt3> updatedMapRooms = childEntities.OfType<MapRoomEntity>()
|
||||
.ToDictionary(entity => entity.Id, entity => entity.Cell);
|
||||
|
||||
BuildingHandler.Main.EnsureTracker(baseId).LocalOperations++;
|
||||
int operationId = BuildingHandler.Main.GetCurrentOperationIdOrDefault(baseId);
|
||||
|
||||
UpdateBase updateBase = new(baseId, pieceId, BuildEntitySpawner.GetBaseData(@base), builtPieceEntity, updatedChildren, moonpoolManager.GetMoonpoolsUpdate(), updatedMapRooms, Temp.ChildrenTransfer, operationId);
|
||||
|
||||
// TODO: (for server-side) Find a way to optimize this (maybe by copying BaseGhost.Finish() => Base.CopyFrom)
|
||||
Resolve<IPacketSender>().Send(updateBase);
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxPatcher.PatternMatching;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class Constructable_DeconstructAsync_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((Constructable t) => t.DeconstructAsync(default, default)));
|
||||
|
||||
public static readonly InstructionsPattern InstructionsPattern = new()
|
||||
{
|
||||
Ldc_I4_0,
|
||||
Ret,
|
||||
Ldloc_1,
|
||||
{ InstructionPattern.Call(nameof(Constructable), nameof(Constructable.UpdateMaterial)), "InsertDestruction" }
|
||||
};
|
||||
|
||||
public static readonly List<CodeInstruction> InstructionsToAdd = new()
|
||||
{
|
||||
new(Ldloc_1),
|
||||
new(Ldc_I4_0), // False for "constructing"
|
||||
new(Call, Reflect.Method(() => Constructable_Construct_Patch.ConstructionAmountModified(default, default)))
|
||||
};
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions) =>
|
||||
instructions.Transform(InstructionsPattern, (label, instruction) =>
|
||||
{
|
||||
if (label.Equals("InsertDestruction"))
|
||||
{
|
||||
return InstructionsToAdd;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.GameLogic.Settings;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents deconstruction if the target base is desynced.
|
||||
/// </summary>
|
||||
public sealed partial class Constructable_DeconstructionAllowed_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Constructable t) => t.DeconstructionAllowed(out Reflect.Ref<string>.Field));
|
||||
|
||||
public static void Postfix(Constructable __instance, ref bool __result, ref string reason)
|
||||
{
|
||||
if (!__result || !BuildingHandler.Main || !__instance.TryGetComponentInParent(out NitroxEntity parentEntity, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
DeconstructionAllowed(parentEntity.Id, ref __result, ref reason);
|
||||
}
|
||||
|
||||
public static void DeconstructionAllowed(NitroxId baseId, ref bool __result, ref string reason)
|
||||
{
|
||||
if (BuildingHandler.Main.BasesCooldown.ContainsKey(baseId))
|
||||
{
|
||||
__result = false;
|
||||
reason = Language.main.Get("Nitrox_ErrorRecentBuildUpdate");
|
||||
}
|
||||
else if (BuildingHandler.Main.EnsureTracker(baseId).IsDesynced() && NitroxPrefs.SafeBuilding.Value)
|
||||
{
|
||||
__result = false;
|
||||
reason = Language.main.Get("Nitrox_ErrorDesyncDetected");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.MonoBehaviours.Cyclops;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters constructables from virtual cyclops when they're fully deconstructed.
|
||||
/// </summary>
|
||||
public sealed partial class Constructable_ProgressDeconstruction_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Constructable t) => t.ProgressDeconstruction());
|
||||
|
||||
public static void Prefix(Constructable __instance)
|
||||
{
|
||||
if (__instance.constructedAmount <= 0f && __instance.transform.parent &&
|
||||
__instance.transform.parent.TryGetComponent(out NitroxCyclops nitroxCyclops) && nitroxCyclops.Virtual)
|
||||
{
|
||||
nitroxCyclops.Virtual.UnregisterConstructable(__instance.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
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
|
||||
{
|
||||
public sealed partial class ConstructorInput_OnCraftingBegin_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((ConstructorInput t) => t.OnCraftingBeginAsync(default(TechType), default(float))));
|
||||
|
||||
public static readonly OpCode INJECTION_OPCODE = OpCodes.Call;
|
||||
public static readonly object INJECTION_OPERAND = Reflect.Method(() => CrafterLogic.NotifyCraftEnd(default(GameObject), default(TechType)));
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
Validate.NotNull(INJECTION_OPERAND);
|
||||
|
||||
foreach (CodeInstruction instruction in instructions)
|
||||
{
|
||||
yield return instruction;
|
||||
|
||||
if (instruction.opcode.Equals(INJECTION_OPCODE) && instruction.operand.Equals(INJECTION_OPERAND))
|
||||
{
|
||||
/*
|
||||
* Callback(constructor, gameObject, techType, duration);
|
||||
*/
|
||||
yield return original.Ldloc<ConstructorInput>(0);
|
||||
yield return original.Ldloc<GameObject>(0);
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_0);
|
||||
yield return new CodeInstruction(OpCodes.Ldfld, TARGET_METHOD.DeclaringType.GetField("techType", BindingFlags.Instance | BindingFlags.Public));
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_0);
|
||||
yield return new CodeInstruction(OpCodes.Ldfld, TARGET_METHOD.DeclaringType.GetField("duration", BindingFlags.Instance | BindingFlags.Public));
|
||||
yield return new CodeInstruction(OpCodes.Call, ((Action<ConstructorInput, GameObject, TechType, float>)Callback).Method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Callback(ConstructorInput constructor, GameObject constructedObject, TechType techType, float duration)
|
||||
{
|
||||
Resolve<MobileVehicleBay>().BeginCrafting(constructor, constructedObject, techType, duration);
|
||||
}
|
||||
}
|
||||
}
|
25
NitroxPatcher/Patches/Dynamic/Constructor_Deploy_Patch.cs
Normal file
25
NitroxPatcher/Patches/Dynamic/Constructor_Deploy_Patch.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class Constructor_Deploy_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Constructor t) => t.Deploy(default(bool)));
|
||||
|
||||
public static void Prefix(Constructor __instance, bool value)
|
||||
{
|
||||
// only trigger updates when there is a valid state change.
|
||||
if (value != __instance.deployed)
|
||||
{
|
||||
// We need to set this early so that the extracted metadata has the right value for "deployed"
|
||||
__instance.deployed = value;
|
||||
if (__instance.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Entities>().EntityMetadataChanged(__instance, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/**
|
||||
* When a player is finished crafting an item, we need to let the server know we spawned the items. We also
|
||||
* let other players know to close out the crafter and consider it empty.
|
||||
*/
|
||||
public sealed partial class CrafterLogic_TryPickupSingleAsync_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((CrafterLogic t) => t.TryPickupSingleAsync(default(TechType), default(IOut<bool>))));
|
||||
|
||||
public static readonly OpCode INJECTION_OPCODE = OpCodes.Call;
|
||||
public static readonly object INJECTION_OPERAND = Reflect.Method(() => CrafterLogic.NotifyCraftEnd(default(GameObject), default(TechType)));
|
||||
|
||||
private static readonly MethodInfo COMPONENT_GAMEOBJECT_GETTER = Reflect.Property((Component t) => t.gameObject).GetMethod;
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
foreach (CodeInstruction instruction in instructions)
|
||||
{
|
||||
yield return instruction;
|
||||
|
||||
if (instruction.opcode.Equals(INJECTION_OPCODE) && instruction.operand.Equals(INJECTION_OPERAND))
|
||||
{
|
||||
/*
|
||||
* Injects: Callback(this.gameObject, gameObject);
|
||||
*/
|
||||
yield return original.Ldloc<CrafterLogic>();
|
||||
yield return new CodeInstruction(OpCodes.Callvirt, COMPONENT_GAMEOBJECT_GETTER);
|
||||
yield return new CodeInstruction(OpCodes.Ldloc_S, (byte)5);
|
||||
yield return new CodeInstruction(OpCodes.Call, ((Action<GameObject, GameObject>)Callback).Method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Callback(GameObject crafter, GameObject item)
|
||||
{
|
||||
if (crafter.TryGetIdOrWarn(out NitroxId crafterId))
|
||||
{
|
||||
// Tell the other players to consider this crafter to no longer contain a tech type.
|
||||
Resolve<Entities>().BroadcastMetadataUpdate(crafterId, new CrafterMetadata(null, DayNightCycle.main.timePassedAsFloat, 0));
|
||||
}
|
||||
|
||||
// The Pickup() item codepath will inform the server that the item was added to the inventory.
|
||||
}
|
||||
}
|
25
NitroxPatcher/Patches/Dynamic/CrashHome_OnDestroy_Patch.cs
Normal file
25
NitroxPatcher/Patches/Dynamic/CrashHome_OnDestroy_Patch.cs
Normal file
@@ -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;
|
||||
|
||||
public sealed partial class CrashHome_OnDestroy_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((CrashHome t) => t.OnDestroy());
|
||||
|
||||
public static void Prefix(CrashHome __instance)
|
||||
{
|
||||
if (!__instance.TryGetNitroxId(out NitroxId crashHomeId) ||
|
||||
!Resolve<SimulationOwnership>().HasAnyLockType(crashHomeId) ||
|
||||
!__instance.crash ||
|
||||
!__instance.crash.TryGetNitroxId(out NitroxId crashId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Resolve<IPacketSender>().Send(new EntityDestroyed(crashId));
|
||||
}
|
||||
}
|
70
NitroxPatcher/Patches/Dynamic/CrashHome_Spawn_Patch.cs
Normal file
70
NitroxPatcher/Patches/Dynamic/CrashHome_Spawn_Patch.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CrashHome_Spawn_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((CrashHome t) => t.Spawn());
|
||||
|
||||
public static bool Prefix(CrashHome __instance)
|
||||
{
|
||||
if (__instance.TryGetNitroxId(out NitroxId crashHomeId) &&
|
||||
Resolve<SimulationOwnership>().HasAnyLockType(crashHomeId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* this.spawnTime = -1f;
|
||||
* BroadcastFishCreated(gameObject); [INSERTED LINE]
|
||||
* if (LargeWorldStreamer.main != null)
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions).MatchStartForward(new CodeMatch(OpCodes.Stfld, Reflect.Field((CrashHome t) => t.spawnTime)))
|
||||
.Advance(1)
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_0))
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Call, Reflect.Method(() => BroadcastFishCreated(default))))
|
||||
.InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void BroadcastFishCreated(GameObject crashFishObject)
|
||||
{
|
||||
if (!crashFishObject.TryGetComponentInParent(out CrashHome crashHome, true) ||
|
||||
!crashHome.TryGetNitroxId(out NitroxId crashHomeId) || !DayNightCycle.main)
|
||||
{
|
||||
return;
|
||||
}
|
||||
NitroxId crashFishId = NitroxEntity.GenerateNewId(crashFishObject);
|
||||
LargeWorldEntity largeWorldEntity = crashFishObject.GetComponent<LargeWorldEntity>();
|
||||
UniqueIdentifier uniqueIdentifier = crashFishObject.GetComponent<UniqueIdentifier>();
|
||||
|
||||
// Broadcast the new CrashHome's metadata (spawnTime = -1)
|
||||
Optional<EntityMetadata> metadata = Resolve<EntityMetadataManager>().Extract(crashHome);
|
||||
if (metadata.HasValue)
|
||||
{
|
||||
Resolve<Entities>().BroadcastMetadataUpdate(crashHomeId, metadata.Value);
|
||||
}
|
||||
|
||||
// Create the entity
|
||||
WorldEntity crashFishEntity = new(crashFishObject.transform.ToWorldDto(), (int)largeWorldEntity.cellLevel, uniqueIdentifier.classId, false, crashFishId, TechType.Crash.ToDto(), null, crashHomeId, new List<Entity>());
|
||||
Resolve<Entities>().BroadcastEntitySpawnedByClient(crashFishEntity);
|
||||
}
|
||||
}
|
18
NitroxPatcher/Patches/Dynamic/CrashHome_Start_Patch.cs
Normal file
18
NitroxPatcher/Patches/Dynamic/CrashHome_Start_Patch.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// We don't want <see cref="CrashHome.Start"/> accidentally spawning a Crash so we prevent it from happening.
|
||||
/// Instead, the spawning functionality will happen in <see cref="CrashHome.Update"/>
|
||||
/// </summary>
|
||||
public sealed partial class CrashHome_Start_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((CrashHome t) => t.Start());
|
||||
|
||||
public static bool Prefix()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
57
NitroxPatcher/Patches/Dynamic/CrashHome_Update_Patch.cs
Normal file
57
NitroxPatcher/Patches/Dynamic/CrashHome_Update_Patch.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CrashHome_Update_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((CrashHome t) => t.Update());
|
||||
|
||||
/*
|
||||
* if (!this.crash && this.spawnTime < 0f)
|
||||
* {
|
||||
* this.spawnTime = (float)(main.timePassed + 1200.0); [REMOVED LINE]
|
||||
* UpdateSpawnTimeAndBroadcast(this); [INSERTED LINE]
|
||||
* }
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions).MatchEndForward([
|
||||
new CodeMatch(OpCodes.Ldfld),
|
||||
new CodeMatch(OpCodes.Ldc_R4),
|
||||
new CodeMatch(OpCodes.Bge_Un)
|
||||
])
|
||||
.Advance(1)
|
||||
.RemoveInstructions(7)
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldarg_0))
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Call, Reflect.Method(() => UpdateSpawnTimeAndBroadcast(default))))
|
||||
.InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void UpdateSpawnTimeAndBroadcast(CrashHome crashHome)
|
||||
{
|
||||
// We udpate and broadcast the spawn time only if we're simulating the home
|
||||
if (!crashHome.TryGetNitroxId(out NitroxId crashHomeId) ||
|
||||
!Resolve<SimulationOwnership>().HasAnyLockType(crashHomeId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
crashHome.spawnTime = DayNightCycle.main.timePassedAsFloat + (float)CrashHome.respawnDelay;
|
||||
|
||||
// Set spawn time before broadcast the new CrashHome's metadata
|
||||
Optional<EntityMetadata> metadata = Resolve<EntityMetadataManager>().Extract(crashHome);
|
||||
if (metadata.HasValue)
|
||||
{
|
||||
Resolve<Entities>().BroadcastMetadataUpdate(crashHomeId, metadata.Value);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <remarks>
|
||||
/// We just want to disable all these commands on client-side and redirect them as ConsoleCommand
|
||||
/// TODO: Remove this file when we'll have the command system
|
||||
/// </remarks>
|
||||
public sealed class CrashedShipExploder_OnConsoleCommand_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD_COUNTDOWNSHIP = Reflect.Method((CrashedShipExploder t) => t.OnConsoleCommand_countdownship());
|
||||
private static readonly MethodInfo TARGET_METHOD_EXPLODEFORCE = Reflect.Method((CrashedShipExploder t) => t.OnConsoleCommand_explodeforce());
|
||||
private static readonly MethodInfo TARGET_METHOD_EXPLODESHIP = Reflect.Method((CrashedShipExploder t) => t.OnConsoleCommand_explodeship());
|
||||
private static readonly MethodInfo TARGET_METHOD_RESTORESHIP = Reflect.Method((CrashedShipExploder t) => t.OnConsoleCommand_restoreship());
|
||||
|
||||
public static bool PrefixCountdownShip()
|
||||
{
|
||||
Resolve<IPacketSender>().Send(new ServerCommand("aurora countdown"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// This command's purpose is just to show FX, we don't need to sync it
|
||||
public static bool PrefixExplodeForce()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool PrefixExplodeShip()
|
||||
{
|
||||
Resolve<IPacketSender>().Send(new ServerCommand("aurora explode"));
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool PrefixRestoreShip()
|
||||
{
|
||||
Resolve<IPacketSender>().Send(new ServerCommand("aurora restore"));
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Patch(Harmony harmony)
|
||||
{
|
||||
PatchPrefix(harmony, TARGET_METHOD_COUNTDOWNSHIP, ((Func<bool>)PrefixCountdownShip).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_EXPLODEFORCE, ((Func<bool>)PrefixExplodeForce).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_EXPLODESHIP, ((Func<bool>)PrefixExplodeShip).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_RESTORESHIP, ((Func<bool>)PrefixRestoreShip).Method);
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <remarks>
|
||||
/// Prevents <see cref="CrashedShipExploder.Update"/> from occurring before initial sync has completed.
|
||||
/// It lets us avoid a very weird edge case in which SetExplodeTime happens before server time is set on the client,
|
||||
/// after what some event in this Update method might be triggered because there's a dead frame before the StoryGoalInitialSyncProcessor step
|
||||
/// which sets up all the aurora story-related stuff locally.
|
||||
/// </remarks>
|
||||
public sealed partial class CrashedShipExploder_Update_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((CrashedShipExploder t) => t.Update());
|
||||
|
||||
public static bool Prefix()
|
||||
{
|
||||
return Multiplayer.Main && Multiplayer.Main.InitialSyncCompleted;
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents <see cref="CreatureAction.Perform"/> from happening if local player doesn't have lock on creature
|
||||
/// </summary>
|
||||
public sealed partial class CreatureAction_Perform_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CreatureAction t) => t.Perform(default, default, default));
|
||||
|
||||
public static bool Prefix(CreatureAction __instance)
|
||||
{
|
||||
if (!__instance.TryGetNitroxId(out NitroxId id) || Resolve<SimulationOwnership>().HasAnyLockType(id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Perform is too specific for each action so it should always be synced case by case (and never run directly on remote players)
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents <see cref="CreatureAction.StartPerform"/> from happening if local player doesn't have lock on creature or if the action is not whitelisted
|
||||
/// </summary>
|
||||
public sealed partial class CreatureAction_StartPerform_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CreatureAction t) => t.StartPerform(default, default));
|
||||
|
||||
public static bool Prefix(CreatureAction __instance)
|
||||
{
|
||||
if (!__instance.TryGetNitroxId(out NitroxId id) || Resolve<SimulationOwnership>().HasAnyLockType(id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Resolve<AI>().IsCreatureActionWhitelisted(__instance);
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents <see cref="CreatureAction.StopPerform"/> from happening if local player doesn't have lock on creature or if the action is not whitelisted
|
||||
/// </summary>
|
||||
public sealed partial class CreatureAction_StopPerform_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CreatureAction t) => t.StopPerform(default, default));
|
||||
|
||||
public static bool Prefix(CreatureAction __instance) => CreatureAction_StartPerform_Patch.Prefix(__instance);
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents <see cref="CreatureDeath.OnAttackByCreature"/> from happening on non-simulated entities
|
||||
/// </summary>
|
||||
public sealed partial class CreatureDeath_OnAttackByCreature_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((CreatureDeath t) => t.OnAttackByCreature());
|
||||
|
||||
public static bool Prefix(CreatureDeath __instance)
|
||||
{
|
||||
if (__instance.TryGetNitroxId(out NitroxId creatureId) &&
|
||||
Resolve<SimulationOwnership>().HasAnyLockType(creatureId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
114
NitroxPatcher/Patches/Dynamic/CreatureDeath_OnKillAsync_Patch.cs
Normal file
114
NitroxPatcher/Patches/Dynamic/CreatureDeath_OnKillAsync_Patch.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts creature death consequences:<br/>
|
||||
/// - Converted to a cooked item<br/>
|
||||
/// - Dead but still has its corpse floating in the water<br/>
|
||||
/// - Eatable decomposition metadata
|
||||
/// </summary>
|
||||
public sealed partial class CreatureDeath_OnKillAsync_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
internal static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((CreatureDeath t) => t.OnKillAsync()));
|
||||
|
||||
/*
|
||||
* 1st injection:
|
||||
* gameObject.GetComponent<Rigidbody>().angularDrag = base.gameObject.GetComponent<Rigidbody>().angularDrag * 3f;
|
||||
* UnityEngine.Object.Destroy(base.gameObject);
|
||||
* result = null;
|
||||
* CreatureDeath_OnKillAsync_Patch.BroadcastCookedSpawned(this, gameObject, cookedData); <---- INSERTED LINE
|
||||
*
|
||||
* 2nd injection:
|
||||
* base.Invoke("RemoveCorpse", this.removeCorpseAfterSeconds);
|
||||
* CreatureDeath_OnKillAsync_Patch.BroadcastRemoveCorpse(this); <---- INSERTED LINE
|
||||
*
|
||||
* 3rd injection:
|
||||
* this.eatable.SetDecomposes(true);
|
||||
* CreatureDeath_OnKillAsync_Patch.BroadcastCookedSpawned(this.eatable); <---- INSERTED LINE
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
// First injection
|
||||
return new CodeMatcher(instructions).MatchEndForward([
|
||||
new CodeMatch(OpCodes.Ldarg_0),
|
||||
new CodeMatch(OpCodes.Ldnull),
|
||||
new CodeMatch(OpCodes.Stfld),
|
||||
new CodeMatch(OpCodes.Br),
|
||||
])
|
||||
.Advance(1)
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldarg_0))
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_2))
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_3))
|
||||
.Insert(new CodeInstruction(OpCodes.Call, Reflect.Method(() => BroadcastCookedSpawned(default, default, default))))
|
||||
// Second injection
|
||||
.MatchEndForward([
|
||||
new CodeMatch(OpCodes.Ldloc_1),
|
||||
new CodeMatch(OpCodes.Ldfld, Reflect.Field((CreatureDeath t) => t.removeCorpseAfterSeconds)),
|
||||
new CodeMatch(OpCodes.Call),
|
||||
])
|
||||
.Advance(1)
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldarg_0))
|
||||
.Insert(new CodeInstruction(OpCodes.Call, Reflect.Method(() => BroadcastRemoveCorpse(default))))
|
||||
// Third injection
|
||||
.MatchEndForward([
|
||||
new CodeMatch(OpCodes.Ldloc_1),
|
||||
new CodeMatch(OpCodes.Ldfld, Reflect.Field((CreatureDeath t) => t.eatable)),
|
||||
new CodeMatch(OpCodes.Ldc_I4_1),
|
||||
new CodeMatch(OpCodes.Callvirt),
|
||||
])
|
||||
.Advance(1)
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_1))
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldfld, Reflect.Field((CreatureDeath t) => t.eatable)))
|
||||
.Insert(new CodeInstruction(OpCodes.Call, Reflect.Method(() => BroadcastEatableMetadata(default))))
|
||||
.InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void BroadcastCookedSpawned(CreatureDeath creatureDeath, GameObject gameObject, TechType cookedTechType)
|
||||
{
|
||||
if (creatureDeath.TryGetNitroxId(out NitroxId creatureId))
|
||||
{
|
||||
NitroxEntity.SetNewId(gameObject, creatureId);
|
||||
}
|
||||
|
||||
Resolve<Items>().Dropped(gameObject, cookedTechType);
|
||||
}
|
||||
|
||||
public static void BroadcastRemoveCorpse(CreatureDeath creatureDeath)
|
||||
{
|
||||
if (creatureDeath.TryGetNitroxId(out NitroxId creatureId))
|
||||
{
|
||||
Resolve<SimulationOwnership>().StopSimulatingEntity(creatureId);
|
||||
EntityPositionBroadcaster.RemoveEntityMovementControl(creatureDeath.gameObject, creatureId);
|
||||
Resolve<IPacketSender>().Send(new RemoveCreatureCorpse(creatureId, creatureDeath.transform.localPosition.ToDto(), creatureDeath.transform.localRotation.ToDto()));
|
||||
}
|
||||
}
|
||||
|
||||
public static void BroadcastEatableMetadata(Eatable eatable)
|
||||
{
|
||||
if (!eatable.TryGetNitroxId(out NitroxId eatableId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<EntityMetadata> metadata = Resolve<EntityMetadataManager>().Extract(eatable);
|
||||
if (metadata.HasValue)
|
||||
{
|
||||
Resolve<Entities>().BroadcastMetadataUpdate(eatableId, metadata.Value);
|
||||
}
|
||||
}
|
||||
}
|
24
NitroxPatcher/Patches/Dynamic/CreatureDeath_OnKill_Patch.cs
Normal file
24
NitroxPatcher/Patches/Dynamic/CreatureDeath_OnKill_Patch.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents <see cref="CreatureDeath.OnKill"/> from happening on non-simulated entities
|
||||
/// </summary>
|
||||
public sealed partial class CreatureDeath_OnKill_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((CreatureDeath t) => t.OnKill());
|
||||
|
||||
public static bool Prefix(CreatureDeath __instance)
|
||||
{
|
||||
if (__instance.TryGetNitroxId(out NitroxId creatureId) &&
|
||||
Resolve<SimulationOwnership>().HasAnyLockType(creatureId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents <see cref="CreatureDeath.OnPickedUp"/> from happening on non-simulated entities
|
||||
/// </summary>
|
||||
public sealed partial class CreatureDeath_OnPickedUp_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((CreatureDeath t) => t.OnPickedUp(default));
|
||||
|
||||
public static bool Prefix(CreatureDeath __instance)
|
||||
{
|
||||
if (__instance.TryGetNitroxId(out NitroxId creatureId) &&
|
||||
Resolve<SimulationOwnership>().HasAnyLockType(creatureId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CreatureDeath_SpawnRespawner_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((CreatureDeath t) => t.SpawnRespawner());
|
||||
|
||||
public static bool Prefix(CreatureDeath __instance)
|
||||
{
|
||||
if (__instance.TryGetNitroxId(out NitroxId creatureId) &&
|
||||
Resolve<SimulationOwnership>().HasAnyLockType(creatureId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* this.hasSpawnedRespawner = true;
|
||||
* CreatureDeath_SpawnRespawner_Patch.BroadcastRespawnSpawned(this); [INSERTED LINE]
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase methodBase, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
// We add instructions right before the ret which is equivalent to inserting at last offset
|
||||
return new CodeMatcher(instructions).End()
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_3))
|
||||
.Insert(new CodeInstruction(OpCodes.Call, Reflect.Method(() => BroadcastSpawnedRespawner(default))))
|
||||
.InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void BroadcastSpawnedRespawner(Respawn respawn)
|
||||
{
|
||||
int cellLevel = respawn.TryGetComponent(out LargeWorldEntity largeWorldEntity) ? (int)largeWorldEntity.cellLevel : 0;
|
||||
string classId = respawn.GetComponent<UniqueIdentifier>().ClassId;
|
||||
NitroxId respawnId = NitroxEntity.GenerateNewId(respawn.gameObject);
|
||||
|
||||
NitroxId parentId = null;
|
||||
if (respawn.transform.parent)
|
||||
{
|
||||
respawn.transform.parent.TryGetNitroxId(out parentId);
|
||||
}
|
||||
|
||||
CreatureRespawnEntity creatureSpawner = new(respawn.transform.ToWorldDto(), cellLevel, classId, false, respawnId, NitroxTechType.None, null, parentId, [],
|
||||
respawn.spawnTime, respawn.techType.ToDto(), respawn.addComponents);
|
||||
|
||||
Resolve<Entities>().BroadcastEntitySpawnedByClient(creatureSpawner, true);
|
||||
|
||||
// We don't need this object as the respawner only works when we load its cell
|
||||
// and it won't activate right now so we'll just delete the entity locally
|
||||
GameObject.Destroy(respawn.gameObject);
|
||||
}
|
||||
}
|
39
NitroxPatcher/Patches/Dynamic/CreatureEgg_Hatch_Patch.cs
Normal file
39
NitroxPatcher/Patches/Dynamic/CreatureEgg_Hatch_Patch.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Syncs egg deletion when hatching.
|
||||
/// </summary>
|
||||
public sealed partial class CreatureEgg_Hatch_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CreatureEgg t) => t.Hatch());
|
||||
|
||||
public static void Prefix(CreatureEgg __instance)
|
||||
{
|
||||
// This case always destroys the creature egg (see original code)
|
||||
if (!__instance.TryGetComponent(out WaterParkItem waterParkItem))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't manage eggs with no id here
|
||||
if (!__instance.TryGetNitroxId(out NitroxId eggId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// It is VERY IMPORTANT to check for simulation ownership on the water park and not on the egg
|
||||
// since that's the convention we chose
|
||||
if (waterParkItem.currentWaterPark.TryGetNitroxId(out NitroxId waterParkId) &&
|
||||
Resolve<SimulationOwnership>().HasAnyLockType(waterParkId))
|
||||
{
|
||||
Resolve<IPacketSender>().Send(new EntityDestroyed(eggId));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// For players without lock: apply remote creature actions and prevent the original call.
|
||||
/// For players with lock: broadcast new creature actions
|
||||
/// </summary>
|
||||
public sealed partial class Creature_ChooseBestAction_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Creature t) => t.ChooseBestAction(default));
|
||||
|
||||
public static bool Prefix(Creature __instance, out NitroxId __state, ref CreatureAction __result)
|
||||
{
|
||||
if (!__instance.TryGetIdOrWarn(out __state) || Resolve<SimulationOwnership>().HasAnyLockType(__state))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we have received any order
|
||||
if (Resolve<AI>().TryGetActionForCreature(__instance, out CreatureAction action))
|
||||
{
|
||||
__result = action;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Postfix(Creature __instance, bool __runOriginal, NitroxId __state, ref CreatureAction __result)
|
||||
{
|
||||
if (!__runOriginal || __state == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Resolve<SimulationOwnership>().HasAnyLockType(__state))
|
||||
{
|
||||
Resolve<AI>().BroadcastNewAction(__state, __instance, __result);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CyclopsExternalDamageManager.RepairPoint(CyclopsDamagePoint)"/> would seem like the correct method to patch, but adding to its postfix will
|
||||
/// execute before <see cref="CyclopsDamagePoint.OnRepair"/> is finished working. Both owners and non-owners will be able to repair damage points on a ship.
|
||||
/// </summary>
|
||||
public sealed partial class CyclopsDamagePoint_OnRepair_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsDamagePoint t) => t.OnRepair());
|
||||
|
||||
public static void Postfix(CyclopsDamagePoint __instance)
|
||||
{
|
||||
// If the amount is high enough, it'll heal full
|
||||
Resolve<Cyclops>().OnDamagePointRepaired(__instance.GetComponentInParent<SubRoot>(), __instance, 999);
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsDecoyLaunchButton_OnClick_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsDecoyLaunchButton t) => t.OnClick());
|
||||
|
||||
public static void Postfix(CyclopsHornButton __instance)
|
||||
{
|
||||
if (__instance.subRoot.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
NitroxServiceLocator.LocateService<Cyclops>().BroadcastLaunchDecoy(id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours.Cyclops;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts the cyclops destruction, and safely removes every player from it. Also broadcasts the creation of the beacon.
|
||||
/// </summary>
|
||||
public sealed partial class CyclopsDestructionEvent_DestroyCyclops_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsDestructionEvent t) => t.DestroyCyclops());
|
||||
|
||||
public static void Prefix(CyclopsDestructionEvent __instance)
|
||||
{
|
||||
bool wasInCyclops = Player.main.currentSub == __instance.subRoot;
|
||||
|
||||
// Before the cyclops destruction, we move out the remote players so that they aren't stuck in its hierarchy
|
||||
if (__instance.subRoot && __instance.subRoot.TryGetComponent(out NitroxCyclops nitroxCyclops))
|
||||
{
|
||||
nitroxCyclops.RemoveAllPlayers();
|
||||
}
|
||||
|
||||
if (wasInCyclops)
|
||||
{
|
||||
// Particular case here, this is not broadcasted and should not be, it's just there to have player be really inside the cyclops while not being registered by NitroxCyclops
|
||||
Player.main._currentSub = __instance.subRoot;
|
||||
}
|
||||
|
||||
__instance.subLiveMixin.Kill();
|
||||
}
|
||||
|
||||
public static void Postfix(CyclopsDestructionEvent __instance)
|
||||
{
|
||||
if (__instance.TryGetNitroxId(out NitroxId nitroxId))
|
||||
{
|
||||
Resolve<Vehicles>().BroadcastDestroyedCyclops(__instance.gameObject, nitroxId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ADD at the end of the method:
|
||||
* CyclopsDestructionEvent_DestroyCyclops_Patch.ManageBeacon(component, this);
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions).End() // Move before Ret
|
||||
.InsertAndAdvance([
|
||||
new CodeInstruction(OpCodes.Ldloc_2),
|
||||
new CodeInstruction(OpCodes.Ldarg_0),
|
||||
new CodeInstruction(OpCodes.Call, Reflect.Method(() => ManageBeacon(default, default)))
|
||||
]).InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void ManageBeacon(Beacon beacon, CyclopsDestructionEvent cyclopsDestructionEvent)
|
||||
{
|
||||
if (!cyclopsDestructionEvent.TryGetNitroxId(out NitroxId nitroxId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We let the simulating player spawn it for everyone
|
||||
if (!Resolve<SimulationOwnership>().HasAnyLockType(nitroxId))
|
||||
{
|
||||
Object.Destroy(beacon.gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to force this state for beaconLabel to wear the correct tag
|
||||
beacon.Start();
|
||||
Resolve<Items>().Dropped(beacon.gameObject, TechType.Beacon);
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxModel.Helper;
|
||||
using static NitroxModel.Helper.Reflect;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed class CyclopsDestructionEvent_OnConsoleCommand_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD_RESTORE = Reflect.Method((CyclopsDestructionEvent t) => t.OnConsoleCommand_restorecyclops(default));
|
||||
private static readonly MethodInfo TARGET_METHOD_DESTROY = Reflect.Method((CyclopsDestructionEvent t) => t.OnConsoleCommand_destroycyclops(default));
|
||||
|
||||
public static bool PrefixRestore()
|
||||
{
|
||||
// TODO: add support for "restorecyclops" command
|
||||
Log.InGame(Language.main.Get("Nitrox_CommandNotAvailable"));
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool PrefixDestroy(CyclopsDestructionEvent __instance, out bool __state)
|
||||
{
|
||||
// We only apply the destroy to the current Cyclops
|
||||
__state = Player.main.currentSub == __instance.subRoot;
|
||||
return __state;
|
||||
}
|
||||
|
||||
public override void Patch(Harmony harmony)
|
||||
{
|
||||
MethodInfo destroyPrefixInfo = Method(() => PrefixDestroy(default, out Ref<bool>.Field));
|
||||
|
||||
PatchPrefix(harmony, TARGET_METHOD_RESTORE, ((Func<bool>)PrefixRestore).Method);
|
||||
PatchPrefix(harmony, TARGET_METHOD_DESTROY, destroyPrefixInfo);
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.Communication;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using NitroxPatcher.PatternMatching;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsDestructionEvent_SpawnLootAsync_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((CyclopsDestructionEvent t) => t.SpawnLootAsync()));
|
||||
|
||||
// Matches twice, once for scrap metal and once for computer chips
|
||||
public static readonly InstructionsPattern PATTERN = new(expectedMatches: 2)
|
||||
{
|
||||
{ Reflect.Method(() => UnityEngine.Object.Instantiate(default(GameObject), default(Vector3), default(Quaternion))), "SpawnObject" }
|
||||
};
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions)
|
||||
.MatchStartForward(new CodeMatch(OpCodes.Switch))
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Call, Reflect.Method(() => TrampolineCallback(default))))
|
||||
.InstructionEnumeration()
|
||||
.InsertAfterMarker(PATTERN, "SpawnObject", [
|
||||
new(OpCodes.Dup),
|
||||
new(OpCodes.Ldloc_1),
|
||||
new(OpCodes.Call, ((Action<GameObject, CyclopsDestructionEvent>)SpawnObjectCallback).Method)
|
||||
]);
|
||||
}
|
||||
|
||||
public static void SpawnObjectCallback(GameObject gameObject, CyclopsDestructionEvent __instance)
|
||||
{
|
||||
NitroxId lootId = NitroxEntity.GenerateNewId(gameObject);
|
||||
|
||||
LargeWorldEntity largeWorldEntity = gameObject.GetComponent<LargeWorldEntity>();
|
||||
PrefabIdentifier prefabIdentifier = gameObject.GetComponent<PrefabIdentifier>();
|
||||
Pickupable pickupable = gameObject.GetComponent<Pickupable>();
|
||||
|
||||
WorldEntity lootEntity = new(gameObject.transform.ToWorldDto(), (int)largeWorldEntity.cellLevel, prefabIdentifier.classId, false, lootId, pickupable.GetTechType().ToDto(), null, null, []);
|
||||
Resolve<Entities>().BroadcastEntitySpawnedByClient(lootEntity);
|
||||
}
|
||||
|
||||
public static int TrampolineCallback(int originalIndex)
|
||||
{
|
||||
// Immediately return from iterator block if called from within CyclopsMetadataProcessor
|
||||
return PacketSuppressor<EntitySpawnedByClient>.IsSuppressed ? int.MaxValue : originalIndex;
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsEngineChangeState_OnClick_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsEngineChangeState t) => t.OnClick());
|
||||
|
||||
public static void Postfix(CyclopsEngineChangeState __instance)
|
||||
{
|
||||
if (__instance.subRoot.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Cyclops>().BroadcastMetadataChange(id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsExternalDamageManager_CreatePoint_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsExternalDamageManager t) => t.CreatePoint());
|
||||
|
||||
public static bool Prefix(CyclopsExternalDamageManager __instance, out bool __state)
|
||||
{
|
||||
// Block from creating points if they aren't the owner of the sub
|
||||
__state = __instance.subRoot.TryGetNitroxId(out NitroxId id) && Resolve<SimulationOwnership>().HasAnyLockType(id);
|
||||
|
||||
return __state;
|
||||
}
|
||||
|
||||
public static void Postfix(CyclopsExternalDamageManager __instance, bool __state)
|
||||
{
|
||||
if (__state)
|
||||
{
|
||||
Resolve<Cyclops>().OnCreateDamagePoint(__instance.subRoot);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/*
|
||||
* Relays Cyclops FireSuppressionSystem to other players
|
||||
* This method was used instead of the OnClick to ensure, that the the suppression really started
|
||||
*/
|
||||
public sealed partial class CyclopsFireSuppressionButton_StartCooldown_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsFireSuppressionSystemButton t) => t.StartCooldown());
|
||||
|
||||
public static void Postfix(CyclopsFireSuppressionSystemButton __instance)
|
||||
{
|
||||
if (__instance.subRoot.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
NitroxServiceLocator.LocateService<Cyclops>().BroadcastActivateFireSuppression(id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsHelmHUDManager_StopPiloting_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsHelmHUDManager t) => t.StopPiloting());
|
||||
|
||||
public static void Postfix(CyclopsHelmHUDManager __instance)
|
||||
{
|
||||
__instance.hudActive = true;
|
||||
|
||||
if (__instance.subRoot.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Cyclops>().BroadcastMetadataChange(id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsHelmHUDManager_Update_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsHelmHUDManager t) => t.Update());
|
||||
|
||||
public static void Postfix(CyclopsHelmHUDManager __instance)
|
||||
{
|
||||
// To show the Cyclops HUD every time "hudActive" have to be true. "hornObject" is a good indicator to check if the player piloting the cyclops.
|
||||
if (!__instance.hornObject.activeSelf && __instance.hudActive)
|
||||
{
|
||||
__instance.canvasGroup.interactable = false;
|
||||
}
|
||||
else if (!__instance.hudActive)
|
||||
{
|
||||
__instance.hudActive = true;
|
||||
}
|
||||
if (__instance.subLiveMixin.IsAlive())
|
||||
{
|
||||
if (__instance.motorMode.engineOn)
|
||||
{
|
||||
__instance.engineToggleAnimator.SetTrigger("EngineOn");
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance.engineToggleAnimator.SetTrigger("EngineOff");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsLightingPanel_ToggleFloodlights_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsLightingPanel t) => t.ToggleFloodlights());
|
||||
|
||||
public static bool Prefix(CyclopsLightingPanel __instance, out bool __state)
|
||||
{
|
||||
__state = __instance.floodlightsOn;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Postfix(CyclopsLightingPanel __instance, bool __state)
|
||||
{
|
||||
if (__state != __instance.floodlightsOn && __instance.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Entities>().EntityMetadataChanged(__instance, id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsLightingPanel_ToggleInternalLighting_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsLightingPanel t) => t.ToggleInternalLighting());
|
||||
|
||||
public static bool Prefix(CyclopsLightingPanel __instance, out bool __state)
|
||||
{
|
||||
__state = __instance.lightingOn;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Postfix(CyclopsLightingPanel __instance, bool __state)
|
||||
{
|
||||
if (__state != __instance.lightingOn && __instance.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Entities>().EntityMetadataChanged(__instance, id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsMotorModeButton_OnClick_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsMotorModeButton t) => t.OnClick());
|
||||
|
||||
public static bool Prefix(CyclopsMotorModeButton __instance, out bool __state)
|
||||
{
|
||||
SubRoot cyclops = __instance.subRoot;
|
||||
if (cyclops != null && cyclops == Player.main.currentSub)
|
||||
{
|
||||
CyclopsHelmHUDManager cyclops_HUD = cyclops.gameObject.RequireComponentInChildren<CyclopsHelmHUDManager>();
|
||||
// To show the Cyclops HUD every time "hudActive" have to be true. "hornObject" is a good indicator to check if the player piloting the cyclops.
|
||||
if (cyclops_HUD.hudActive)
|
||||
{
|
||||
__state = cyclops_HUD.hornObject.activeSelf;
|
||||
return cyclops_HUD.hornObject.activeSelf;
|
||||
}
|
||||
}
|
||||
|
||||
__state = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Postfix(CyclopsMotorModeButton __instance, bool __state)
|
||||
{
|
||||
if (__state && __instance.subRoot.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Cyclops>().BroadcastMetadataChange(id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsMotorMode_RestoreEngineState_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsMotorMode t) => t.RestoreEngineState());
|
||||
|
||||
public static bool Prefix()
|
||||
{
|
||||
// We don't want this to happen because it will prevent players that were far of the cyclops when spawning to see its actual engine state
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsMotorMode_SaveEngineStateAndPowerDown_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsMotorMode t) => t.SaveEngineStateAndPowerDown());
|
||||
|
||||
public static bool Prefix(CyclopsMotorMode __instance)
|
||||
{
|
||||
// SN disable the engine if the player leave the cyclops. So this must be avoided.
|
||||
__instance.engineOnOldState = __instance.engineOn;
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsShieldButton_OnClick_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsShieldButton t) => t.OnClick());
|
||||
public static readonly OpCode START_CUT_CODE = OpCodes.Ldsfld;
|
||||
public static readonly OpCode START_CUT_CODE_CALL = OpCodes.Callvirt;
|
||||
public static readonly FieldInfo PLAYER_MAIN_FIELD = Reflect.Field(() => Player.main);
|
||||
public static readonly OpCode END_CUT_CODE = OpCodes.Ret;
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
List<CodeInstruction> instructionList = instructions.ToList();
|
||||
int startCut = 0;
|
||||
int endCut = instructionList.Count;
|
||||
/* Cut out
|
||||
* if (Player.main.currentSub != this.subRoot)
|
||||
* {
|
||||
* return;
|
||||
* }
|
||||
*/
|
||||
for (int i = 1; i < instructionList.Count; i++)
|
||||
{
|
||||
if (instructionList[i - 1].opcode.Equals(START_CUT_CODE) && instructionList[i - 1].operand.Equals(PLAYER_MAIN_FIELD) && instructionList[i].opcode == START_CUT_CODE_CALL)
|
||||
{
|
||||
startCut = i - 1;
|
||||
}
|
||||
// Cut at the first return encountered
|
||||
if (endCut == instructionList.Count && instructionList[i].opcode.Equals(END_CUT_CODE))
|
||||
{
|
||||
endCut = i;
|
||||
}
|
||||
}
|
||||
instructionList.RemoveRange(startCut, endCut + 1);
|
||||
if (startCut == 0)
|
||||
{
|
||||
instructionList.Insert(0, new CodeInstruction(OpCodes.Nop));
|
||||
}
|
||||
return instructionList;
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsShieldButton_StartShield_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsShieldButton t) => t.StartShield());
|
||||
|
||||
public static void Postfix(CyclopsShieldButton __instance)
|
||||
{
|
||||
if (__instance.subRoot.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Cyclops>().BroadcastMetadataChange(id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsShieldButton_StopShield_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsShieldButton t) => t.StopShield());
|
||||
|
||||
public static void Postfix(CyclopsShieldButton __instance)
|
||||
{
|
||||
if (__instance.subRoot.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Cyclops>().BroadcastMetadataChange(id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsSilentRunningAbilityButton_TurnOffSilentRunning_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsSilentRunningAbilityButton t) => t.TurnOffSilentRunning());
|
||||
|
||||
public static void Postfix(CyclopsSilentRunningAbilityButton __instance)
|
||||
{
|
||||
if (__instance.subRoot.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Cyclops>().BroadcastMetadataChange(id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsSilentRunningAbilityButton_TurnOnSilentRunning_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsSilentRunningAbilityButton t) => t.TurnOnSilentRunning());
|
||||
|
||||
public static void Postfix(CyclopsSilentRunningAbilityButton __instance)
|
||||
{
|
||||
if (__instance.subRoot.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Cyclops>().BroadcastMetadataChange(id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsSonarButton_OnClick_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsSonarButton t) => t.OnClick());
|
||||
|
||||
public static void Postfix(CyclopsSonarButton __instance)
|
||||
{
|
||||
if (__instance.subRoot.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Cyclops>().BroadcastMetadataChange(id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// The sonar will stay on until the player leaves the vehicle and automatically turns on when they enter again (if sonar was on at that time).
|
||||
/// </summary>
|
||||
public sealed partial class CyclopsSonarButton_SonarPing_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsSonarButton t) => t.SonarPing());
|
||||
|
||||
public static bool Prefix(CyclopsSonarButton __instance)
|
||||
{
|
||||
SubRoot subRoot = __instance.subRoot;
|
||||
if (Player.main.currentSub != subRoot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (LocalPlayerHasLock(subRoot) && !subRoot.powerRelay.ConsumeEnergy(subRoot.sonarPowerCost, out float _))
|
||||
{
|
||||
__instance.TurnOffSonar();
|
||||
return false;
|
||||
}
|
||||
SNCameraRoot.main.SonarPing();
|
||||
__instance.soundFX.Play();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool LocalPlayerHasLock(SubRoot subRoot)
|
||||
{
|
||||
return subRoot.TryGetNitroxId(out NitroxId entityId) && Resolve<SimulationOwnership>().HasExclusiveLock(entityId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.MonoBehaviours.Cyclops;
|
||||
using NitroxClient.MonoBehaviours.Vehicles;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents sonar from turning off automatically for the player that isn't currently piloting the Cyclops.
|
||||
/// </summary>
|
||||
public sealed partial class CyclopsSonarButton_Update_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsSonarButton t) => t.Update());
|
||||
|
||||
internal static readonly OpCode INJECTION_OPCODE = OpCodes.Ldsfld;
|
||||
internal static readonly object INJECTION_OPERAND = Reflect.Field(() => Player.main);
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase methodBase, IEnumerable<CodeInstruction> instructions, ILGenerator il)
|
||||
{
|
||||
/* Normally in the Update()
|
||||
* if (Player.main.GetMode() == Player.Mode.Normal && this.sonarActive)
|
||||
* {
|
||||
* this.TurnOffSonar();
|
||||
* }
|
||||
* this part will be changed into:
|
||||
* if (CyclopsSonarButton_Update_Patch.ShouldTurnOff(this) && Player.main.GetMode() == Player.Mode.Normal && this.sonarActive)
|
||||
*/
|
||||
List<CodeInstruction> codeInstructions = new(instructions);
|
||||
Label brLabel = il.DefineLabel();
|
||||
CodeInstruction loadInstruction = new(OpCodes.Ldarg_0);
|
||||
CodeInstruction callInstruction = new(OpCodes.Call, Reflect.Method(() => ShouldTurnoff(default)));
|
||||
CodeInstruction brInstruction = new(OpCodes.Brfalse, brLabel);
|
||||
codeInstructions.Last().labels.Add(brLabel);
|
||||
|
||||
for (int i = 0; i < codeInstructions.Count; i++)
|
||||
{
|
||||
CodeInstruction instruction = codeInstructions[i];
|
||||
|
||||
if (instruction.opcode.Equals(INJECTION_OPCODE) && instruction.operand.Equals(INJECTION_OPERAND))
|
||||
{
|
||||
// The second line after the current instruction should be a Brtrue, we need its operand to have the same jump label for our brfalse
|
||||
CodeInstruction nextBr = codeInstructions[i + 2];
|
||||
|
||||
// The new instruction will be the first of the if statement, so it should take the jump labels that the former first part of the statement had
|
||||
instruction.MoveLabelsTo(loadInstruction);
|
||||
|
||||
yield return loadInstruction;
|
||||
yield return callInstruction;
|
||||
yield return brInstruction;
|
||||
}
|
||||
yield return instruction;
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>true (sonar should be turned off) if local player is simulating the cyclops (there's no replicator in this case)</returns>
|
||||
public static bool ShouldTurnoff(CyclopsSonarButton cyclopsSonarButton)
|
||||
{
|
||||
return !cyclopsSonarButton.subRoot.GetComponent<CyclopsMovementReplicator>();
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Persistent;
|
||||
|
||||
public sealed partial class CyclopsSonarCreatureDetector_CheckForCreaturesInRange_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsSonarCreatureDetector t) => t.CheckForCreaturesInRange());
|
||||
|
||||
public const CyclopsSonarDisplay.EntityType PLAYER_TYPE = (CyclopsSonarDisplay.EntityType)2;
|
||||
|
||||
public static void Postfix(CyclopsSonarCreatureDetector __instance)
|
||||
{
|
||||
__instance.ChekItemsOnHashSet(Resolve<PlayerManager>().GetAllPlayerObjects(), PLAYER_TYPE);
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.PlayerLogic;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using NitroxPatcher.Patches.Persistent;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class CyclopsSonarDisplay_NewEntityOnSonar_Patch : NitroxPatch, IPersistentPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((CyclopsSonarDisplay t) => t.NewEntityOnSonar(default));
|
||||
|
||||
/*
|
||||
* }
|
||||
* this.entitysOnSonar.Add(entityPing2);
|
||||
* SetupPing(component, entityData); <----- INSERTED LINE
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions).End()
|
||||
.InsertAndAdvance(TARGET_METHOD.Ldloc<CyclopsHUDSonarPing>())
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldarg_1))
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Call, Reflect.Method(() => SetupPing(default, default))))
|
||||
.InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void SetupPing(CyclopsHUDSonarPing ping, CyclopsSonarCreatureDetector.EntityData entityData)
|
||||
{
|
||||
if (entityData.entityType != CyclopsSonarCreatureDetector_CheckForCreaturesInRange_Patch.PLAYER_TYPE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Color color;
|
||||
if (entityData.gameObject == Player.mainObject)
|
||||
{
|
||||
color = Resolve<LocalPlayer>().PlayerSettings.PlayerColor.ToUnity();
|
||||
}
|
||||
else if (entityData.gameObject.TryGetComponent(out RemotePlayerIdentifier remotePlayerIdentifier))
|
||||
{
|
||||
color = remotePlayerIdentifier.RemotePlayer.PlayerSettings.PlayerColor.ToUnity();
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CyclopsHUDSonarPing sonarPing = ping.GetComponent<CyclopsHUDSonarPing>();
|
||||
// Set isCreaturePing to true so that CyclopsHUDSonarPing.Start runs the SetColor code
|
||||
sonarPing.isCreaturePing = true;
|
||||
sonarPing.passiveColor = color;
|
||||
sonarPing.Start();
|
||||
sonarPing.isCreaturePing = false;
|
||||
|
||||
// We remove the pulse to be able to differentiate those signals from the creatures and decoy ones
|
||||
GameObject.Destroy(sonarPing.transform.Find("Ping/PingPulse").gameObject);
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class DayNightCycle_OnConsoleCommand_day_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((DayNightCycle t) => t.OnConsoleCommand_day(default(NotificationCenter.Notification)));
|
||||
|
||||
public static bool Prefix()
|
||||
{
|
||||
IPacketSender packetSender = NitroxServiceLocator.LocateService<IPacketSender>();
|
||||
packetSender.Send(new ServerCommand("time day"));
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class DayNightCycle_OnConsoleCommand_daynightspeed_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((DayNightCycle t) => t.OnConsoleCommand_daynightspeed(default));
|
||||
|
||||
// The command is skipped because simulating speed reliable on the server is out of scope
|
||||
public static bool Prefix()
|
||||
{
|
||||
Log.InGame(Language.main.Get("Nitrox_CommandNotAvailable"));
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class DayNightCycle_OnConsoleCommand_night_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((DayNightCycle t) => t.OnConsoleCommand_night(default(NotificationCenter.Notification)));
|
||||
|
||||
public static bool Prefix()
|
||||
{
|
||||
Resolve<IPacketSender>().Send(new ServerCommand("time night"));
|
||||
return false;
|
||||
}
|
||||
}
|
22
NitroxPatcher/Patches/Dynamic/DayNightCycle_Update_Patch.cs
Normal file
22
NitroxPatcher/Patches/Dynamic/DayNightCycle_Update_Patch.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Replace the local time calculations by the real server time.
|
||||
/// </summary>
|
||||
public sealed partial class DayNightCycle_Update_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((DayNightCycle t) => t.Update());
|
||||
|
||||
public static bool Prefix(DayNightCycle __instance)
|
||||
{
|
||||
// Essential part of the Update() method to have it running all of the time and have the local time set to the real server time
|
||||
__instance.timePassedAsDouble = Resolve<TimeManager>().CalculateCurrentTime();
|
||||
__instance.UpdateAtmosphere();
|
||||
__instance.UpdateDayNightMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Replace the deltaTime calculations by one that is not capped by <see cref="Time.maximumDeltaTime"/>
|
||||
/// </summary>
|
||||
public sealed partial class DayNightCycle_deltaTime_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Property((DayNightCycle t) => t.deltaTime).GetGetMethod();
|
||||
|
||||
public static bool Prefix(DayNightCycle __instance, out float __result)
|
||||
{
|
||||
__result = Resolve<TimeManager>().DeltaTime;
|
||||
return false;
|
||||
}
|
||||
}
|
34
NitroxPatcher/Patches/Dynamic/DevConsole_Update_Patch.cs
Normal file
34
NitroxPatcher/Patches/Dynamic/DevConsole_Update_Patch.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxPatcher.PatternMatching;
|
||||
using UnityEngine;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps DevConsole disabled when enter is pressed.
|
||||
/// </summary>
|
||||
public sealed partial class DevConsole_Update_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly InstructionsPattern devConsoleSetStateTruePattern = new()
|
||||
{
|
||||
Reflect.Method(() => Input.GetKeyDown(default(KeyCode))),
|
||||
Brfalse,
|
||||
Ldarg_0,
|
||||
Ldfld,
|
||||
Brtrue,
|
||||
Ldarg_0,
|
||||
{ Ldc_I4_1, "ConsoleEnableFlag" },
|
||||
Reflect.Method((DevConsole t) => t.SetState(default(bool)))
|
||||
};
|
||||
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((DevConsole t) => t.Update());
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return instructions.ChangeAtMarker(devConsoleSetStateTruePattern, "ConsoleEnableFlag", i => i.opcode = Ldc_I4_0);
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.Simulation;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class DockedVehicleHandTarget_OnHandClick_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo targetMethod = Reflect.Method((DockedVehicleHandTarget t) => t.OnHandClick(default(GUIHand)));
|
||||
|
||||
private static bool skipPrefix;
|
||||
|
||||
public static bool Prefix(DockedVehicleHandTarget __instance, GUIHand hand)
|
||||
{
|
||||
Vehicle vehicle = __instance.dockingBay.GetDockedVehicle();
|
||||
|
||||
if (skipPrefix || !vehicle.TryGetIdOrWarn(out NitroxId vehicleId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Resolve<SimulationOwnership>().HasExclusiveLock(vehicleId))
|
||||
{
|
||||
Log.Debug($"Already have an exclusive lock on this vehicle: {vehicleId}");
|
||||
return true;
|
||||
}
|
||||
|
||||
HandInteraction<DockedVehicleHandTarget> context = new(__instance, hand);
|
||||
LockRequest<HandInteraction<DockedVehicleHandTarget>> lockRequest = new(vehicleId, SimulationLockType.EXCLUSIVE, ReceivedSimulationLockResponse, context);
|
||||
Resolve<SimulationOwnership>().RequestSimulationLock(lockRequest);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ReceivedSimulationLockResponse(NitroxId vehicleId, bool lockAcquired, HandInteraction<DockedVehicleHandTarget> context)
|
||||
{
|
||||
if (lockAcquired)
|
||||
{
|
||||
VehicleDockingBay dockingBay = context.Target.dockingBay;
|
||||
Vehicle vehicle = dockingBay.GetDockedVehicle();
|
||||
|
||||
if (!dockingBay.TryGetIdOrWarn(out NitroxId dockId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vehicles.EngagePlayerMovementSuppressor(vehicle);
|
||||
Resolve<IPacketSender>().Send(new VehicleUndocking(vehicleId, dockId, Resolve<IMultiplayerSession>().Reservation.PlayerId, true));
|
||||
|
||||
skipPrefix = true;
|
||||
context.Target.OnHandClick(context.GuiHand);
|
||||
skipPrefix = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO: Check if this should be Hand
|
||||
HandReticle.main.SetText(HandReticle.TextType.Hand, "Another player is using this vehicle!", false, GameInput.Button.None);
|
||||
HandReticle.main.SetText(HandReticle.TextType.HandSubscript, string.Empty, false, GameInput.Button.None);
|
||||
HandReticle.main.SetIcon(HandReticle.IconType.HandDeny, 1f);
|
||||
context.Target.isValidHandTarget = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents <see cref="Eatable.IterateDespawn"/> from happening on non-simulated entities and broadcast it for simulated entities
|
||||
/// </summary>
|
||||
public sealed partial class Eatable_IterateDespawn_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method((Eatable t) => t.IterateDespawn());
|
||||
|
||||
public static bool Prefix(CreatureDeath __instance)
|
||||
{
|
||||
if (__instance.TryGetNitroxId(out NitroxId creatureId) &&
|
||||
Resolve<SimulationOwnership>().HasAnyLockType(creatureId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* if (DayNightCycle.main.timePassedAsFloat - this.timeDespawnStart > this.despawnDelay)
|
||||
* {
|
||||
* base.CancelInvoke();
|
||||
* UnityEngine.Object.Destroy(base.gameObject);
|
||||
* Eatable_IterateDespawn_Patch.BroadcastEatableDestroyed(this); [INSERTED LINE]
|
||||
* }
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
// We add instructions right before the ret which is equivalent to inserting at last offset
|
||||
return new CodeMatcher(instructions).End()
|
||||
.InsertAndAdvance(new CodeInstruction(OpCodes.Ldarg_0))
|
||||
.Insert(new CodeInstruction(OpCodes.Call, Reflect.Method(() => BroadcastEatableDestroyed(default))))
|
||||
.InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void BroadcastEatableDestroyed(Eatable eatable)
|
||||
{
|
||||
if (eatable.TryGetNitroxId(out NitroxId objectId))
|
||||
{
|
||||
Resolve<IPacketSender>().Send(new EntityDestroyed(objectId));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// When items are spawned in Subnautica will automatically add batteries to them async. This is challenging to deal with because it is
|
||||
/// difficult to discriminate between these async/default additions and a user purposfully placing batteries into an object. Instead, we
|
||||
/// disable the default behavior and allow Nitrox to always spawn the batteries. This guarentees we can capture the ids correctly.
|
||||
/// </summary>
|
||||
public sealed partial class EnergyMixin_SpawnDefaultAsync_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((EnergyMixin t) => t.SpawnDefaultAsync(default(float), default(TaskResult<bool>))));
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
|
||||
// Blanks out the generated MoveNext() and replaces it with:
|
||||
//
|
||||
// result.set(false);
|
||||
// return false;
|
||||
//
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_0);
|
||||
yield return new CodeInstruction(OpCodes.Ldfld, TARGET_METHOD.DeclaringType.GetField("result", BindingFlags.Instance | BindingFlags.Public));
|
||||
yield return new CodeInstruction(OpCodes.Ldc_I4_0);
|
||||
yield return new CodeInstruction(OpCodes.Callvirt, Reflect.Method((TaskResult<bool> result) => result.Set(default)));
|
||||
yield return new CodeInstruction(OpCodes.Ldc_I4_0);
|
||||
yield return new CodeInstruction(OpCodes.Ret);
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class EnergyMixin_ModifyCharge_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((EnergyMixin t) => t.ModifyCharge(default(float)));
|
||||
|
||||
public static void Postfix(EnergyMixin __instance, float __result)
|
||||
{
|
||||
GameObject batteryGo = __instance.GetBatteryGameObject();
|
||||
|
||||
if (batteryGo && batteryGo.TryGetComponent(out Battery battery) &&
|
||||
Math.Abs(Math.Floor(__instance.charge) - Math.Floor(__instance.charge - __result)) > 0.0 && //Send package if power changed to next natural number
|
||||
batteryGo.TryGetIdOrWarn(out NitroxId id))
|
||||
{
|
||||
Resolve<Entities>().EntityMetadataChanged(battery, id);
|
||||
}
|
||||
}
|
||||
}
|
18
NitroxPatcher/Patches/Dynamic/EnergyMixin_OnAddItem_Patch.cs
Normal file
18
NitroxPatcher/Patches/Dynamic/EnergyMixin_OnAddItem_Patch.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class EnergyMixin_OnAddItem_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((EnergyMixin t) => t.OnAddItem(default(InventoryItem)));
|
||||
|
||||
public static void Postfix(EnergyMixin __instance, InventoryItem item)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
Resolve<ItemContainers>().BroadcastBatteryAdd(item.item.gameObject, __instance.gameObject, item.techType);
|
||||
}
|
||||
}
|
||||
}
|
45
NitroxPatcher/Patches/Dynamic/EntityCell_AwakeAsync_Patch.cs
Normal file
45
NitroxPatcher/Patches/Dynamic/EntityCell_AwakeAsync_Patch.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Registers the awakening of a cell for <see cref="Terrain"/>, and prevents the cell from loading serialized data of any sort.
|
||||
/// </summary>
|
||||
public sealed partial class EntityCell_AwakeAsync_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
internal static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((EntityCell t) => t.AwakeAsync(default)));
|
||||
|
||||
/*
|
||||
* this.state = EntityCell.State.InAwakeAsync;
|
||||
* EntityCell_AwakeAsync_Patch.Callback(this); <--- INSERTED LINE
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions).MatchEndForward([
|
||||
new CodeMatch(OpCodes.Ldloc_2),
|
||||
new CodeMatch(OpCodes.Ldc_I4_6),
|
||||
new CodeMatch(OpCodes.Stfld, Reflect.Field((EntityCell t) => t.state))
|
||||
])
|
||||
.Advance(1)
|
||||
.InsertAndAdvance([
|
||||
new CodeInstruction(OpCodes.Ldloc_2),
|
||||
new CodeInstruction(OpCodes.Call, ((Action<EntityCell>)Callback).Method)
|
||||
]).InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void Callback(EntityCell __instance)
|
||||
{
|
||||
Resolve<Terrain>().CellLoaded(__instance.BatchId, __instance.CellId, __instance.Level);
|
||||
|
||||
__instance.ClearWaiterQueue();
|
||||
__instance.serialData.Clear();
|
||||
__instance.legacyData.Clear();
|
||||
__instance.waiterData.Clear();
|
||||
}
|
||||
}
|
18
NitroxPatcher/Patches/Dynamic/EntityCell_Reset_Patch.cs
Normal file
18
NitroxPatcher/Patches/Dynamic/EntityCell_Reset_Patch.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// An important source of cell unload which is different than <see cref="EntityCell.SleepAsync"/> but must also be taken into account.
|
||||
/// </summary>
|
||||
public sealed partial class EntityCell_Reset_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((EntityCell t) => t.Reset());
|
||||
|
||||
public static void Prefix(EntityCell __instance)
|
||||
{
|
||||
Resolve<Terrain>().CellUnloaded(__instance.batchId, __instance.cellId, __instance.level);
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents caching cells GameObjects.
|
||||
/// </summary>
|
||||
public sealed partial class EntityCell_SerializeAsyncImpl_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((EntityCell t) => t.SerializeAsyncImpl(default, default)));
|
||||
|
||||
public static bool Prefix()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents caching cells GameObjects.
|
||||
/// </summary>
|
||||
public sealed partial class EntityCell_SerializeWaiterDataAsync_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((EntityCell t) => t.SerializeWaiterDataAsync(default)));
|
||||
|
||||
public static bool Prefix()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
40
NitroxPatcher/Patches/Dynamic/EntityCell_SleepAsync_Patch.cs
Normal file
40
NitroxPatcher/Patches/Dynamic/EntityCell_SleepAsync_Patch.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
/// <summary>
|
||||
/// Entity cells will go sleep when the player gets out of range. This needs to be reported to the server so they can lose simulation locks.
|
||||
/// </summary>
|
||||
public sealed partial class EntityCell_SleepAsync_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
internal static readonly MethodInfo TARGET_METHOD = AccessTools.EnumeratorMoveNext(Reflect.Method((EntityCell t) => t.SleepAsync(default)));
|
||||
|
||||
/*
|
||||
* this.state = EntityCell.State.InSleepAsync;
|
||||
* EntityCell_SleepAsync_Patch.Callback(this); <--- INSERTED LINE
|
||||
*/
|
||||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
return new CodeMatcher(instructions).MatchEndForward([
|
||||
new CodeMatch(OpCodes.Ldloc_1),
|
||||
new CodeMatch(OpCodes.Ldc_I4_7),
|
||||
new CodeMatch(OpCodes.Stfld, Reflect.Field((EntityCell t) => t.state))
|
||||
])
|
||||
.Advance(1)
|
||||
.InsertAndAdvance([
|
||||
new CodeInstruction(OpCodes.Ldloc_1),
|
||||
new CodeInstruction(OpCodes.Call, ((Action<EntityCell>)Callback).Method)
|
||||
]).InstructionEnumeration();
|
||||
}
|
||||
|
||||
public static void Callback(EntityCell entityCell)
|
||||
{
|
||||
Resolve<Terrain>().CellUnloaded(entityCell.BatchId, entityCell.CellId, entityCell.Level);
|
||||
}
|
||||
}
|
18
NitroxPatcher/Patches/Dynamic/Equipment_AddItem_Patch.cs
Normal file
18
NitroxPatcher/Patches/Dynamic/Equipment_AddItem_Patch.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class Equipment_AddItem_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Equipment t) => t.AddItem(default, default, default));
|
||||
|
||||
public static void Postfix(Equipment __instance, bool __result, string slot, InventoryItem newItem)
|
||||
{
|
||||
if (__result)
|
||||
{
|
||||
Resolve<EquipmentSlots>().BroadcastEquip(newItem.item, __instance.owner, slot);
|
||||
}
|
||||
}
|
||||
}
|
38
NitroxPatcher/Patches/Dynamic/Equipment_RemoveItem_Patch.cs
Normal file
38
NitroxPatcher/Patches/Dynamic/Equipment_RemoveItem_Patch.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
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;
|
||||
|
||||
public sealed partial class Equipment_RemoveItem_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((Equipment t) => t.RemoveItem(default(string), default(bool), default(bool)));
|
||||
|
||||
public static readonly OpCode INJECTION_OPCODE = OpCodes.Stloc_1;
|
||||
|
||||
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
foreach (CodeInstruction instruction in instructions)
|
||||
{
|
||||
yield return instruction;
|
||||
|
||||
if (instruction.opcode.Equals(INJECTION_OPCODE))
|
||||
{
|
||||
/*
|
||||
* Multiplayer.Logic.EquipmentSlots.Unequip(pickupable, this.owner, slot)
|
||||
*/
|
||||
yield return TranspilerHelper.LocateService<EquipmentSlots>();
|
||||
yield return new CodeInstruction(OpCodes.Ldloc_0);
|
||||
yield return new CodeInstruction(OpCodes.Callvirt, Reflect.Property((InventoryItem t) => t.item).GetMethod);
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_0);
|
||||
yield return new CodeInstruction(OpCodes.Call, Reflect.Property((Equipment t) => t.owner).GetMethod);
|
||||
yield return new CodeInstruction(OpCodes.Ldarg_1);
|
||||
yield return new CodeInstruction(OpCodes.Callvirt, Reflect.Method((EquipmentSlots t) => t.BroadcastUnequip(default(Pickupable), default(GameObject), default(string))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using System.Reflection;
|
||||
using NitroxModel.Helper;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class EscapePodFirstUseCinematicsController_ReleaseCreature_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((EscapePodFirstUseCinematicsController t) => t.ReleaseCreature(default));
|
||||
|
||||
/**
|
||||
* Avoid cinematics from spawning unsynced entites.
|
||||
* As soon as the cinematics are over, we'll kill them.
|
||||
*/
|
||||
public static bool Prefix(GameObject creatureGO)
|
||||
{
|
||||
if (creatureGO)
|
||||
{
|
||||
UnityEngine.Object.Destroy(creatureGO);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
15
NitroxPatcher/Patches/Dynamic/EscapePod_Awake_Patch.cs
Normal file
15
NitroxPatcher/Patches/Dynamic/EscapePod_Awake_Patch.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class EscapePod_Awake_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((EscapePod t) => t.Awake());
|
||||
|
||||
public static bool Prefix(EscapePod __instance)
|
||||
{
|
||||
return !EscapePodWorldEntitySpawner.SuppressEscapePodAwakeMethod;
|
||||
}
|
||||
}
|
22
NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs
Normal file
22
NitroxPatcher/Patches/Dynamic/EscapePod_OnRepair_Patch.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Reflection;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class EscapePod_OnRepair_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((EscapePod t) => t.OnRepair());
|
||||
|
||||
public static void Prefix(EscapePod __instance)
|
||||
{
|
||||
if (__instance.TryGetIdOrWarn(out NitroxId id) &&
|
||||
Resolve<EntityMetadataManager>().TryExtract(__instance, out EntityMetadata metadata))
|
||||
{
|
||||
Resolve<Entities>().BroadcastMetadataUpdate(id, metadata);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxPatcher.Patches.Dynamic;
|
||||
|
||||
public sealed partial class EscapePod_RespawnPlayer_Patch : NitroxPatch, IDynamicPatch
|
||||
{
|
||||
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((EscapePod t) => t.RespawnPlayer());
|
||||
|
||||
public static void Postfix(EscapePod __instance)
|
||||
{
|
||||
// EscapePod.RespawnPlayer() runs both for player respawn (Player.MovePlayerToRespawnPoint()) and for warpme command
|
||||
Optional<NitroxId> id = __instance.GetId();
|
||||
Resolve<LocalPlayer>().BroadcastEscapePodChange(id);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user