Skip to content

Commit

Permalink
Some refactoring, replacement of prefix by transpiler, added a synced…
Browse files Browse the repository at this point in the history
… creature type whitelist
  • Loading branch information
tornac1234 committed Feb 19, 2024
1 parent 4839668 commit 1a987e9
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using HarmonyLib;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NitroxTest.Patcher;

namespace NitroxPatcher.Patches.Dynamic;

[TestClass]
public class Vehicle_TorpedoShot_PatchTest
{
[TestMethod]
public void Sanity()
{
IEnumerable<CodeInstruction> originalIl = PatchTestHelper.GetInstructionsFromMethod(Vehicle_TorpedoShot_Patch.TARGET_METHOD);
IEnumerable<CodeInstruction> transformedIl = Vehicle_TorpedoShot_Patch.Transpiler(originalIl);
transformedIl.Count().Should().Be(originalIl.Count() + 3);
}
}
16 changes: 15 additions & 1 deletion NitroxClient/GameLogic/AI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ public class AI
typeof(AttackLastTarget), typeof(AttackCyclops)
];

/// <summary>
/// In the future, ensure all creatures are synced. We want each of them to be individually
/// checked (that all their actions are synced) before marking them as synced.
/// </summary>
private readonly HashSet<Type> syncedCreatureWhitelist =
[
typeof(ReaperLeviathan), typeof(SeaDragon)
];

public AI(IPacketSender packetSender)
{
this.packetSender = packetSender;
Expand All @@ -36,7 +45,7 @@ public AI(IPacketSender packetSender)

public void BroadcastNewAction(NitroxId creatureId, Creature creature, CreatureAction newAction)
{
if (creature is not ReaperLeviathan)
if (!syncedCreatureWhitelist.Contains(creature.GetType()))
{
return;
}
Expand Down Expand Up @@ -119,4 +128,9 @@ public bool IsCreatureActionWhitelisted(CreatureAction creatureAction)
{
return creatureActionWhitelist.Contains(creatureAction.GetType());
}

public bool IsCreatureWhitelisted(Creature creature)
{
return syncedCreatureWhitelist.Contains(creature.GetType());
}
}
13 changes: 7 additions & 6 deletions NitroxClient/GameLogic/BulletManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

namespace NitroxClient.GameLogic;

/// <summary>
/// Registers one stasis sphere per connected remote player, and syncs their behaviour.<br/>
/// Also syncs remote torpedo (of all types) shots and hits.
/// </summary>
public class BulletManager
{
private readonly PlayerManager playerManager;
Expand Down Expand Up @@ -77,10 +81,7 @@ public void TorpedoTargetAcquired(NitroxId bulletId, NitroxId targetId, Vector3

public void ShootStasisSphere(ushort playerId, Vector3 position, Quaternion rotation, float speed, float lifeTime, float chargeNormalized)
{
if (!stasisSpherePerPlayerId.TryGetValue(playerId, out StasisSphere cloneSphere) || !cloneSphere)
{
cloneSphere = EnsurePlayerHasSphere(playerId);
}
StasisSphere cloneSphere = EnsurePlayerHasSphere(playerId);

cloneSphere.Shoot(position, rotation, speed, lifeTime, chargeNormalized);
}
Expand All @@ -89,15 +90,15 @@ public void StasisSphereHit(ushort playerId, Vector3 position, Quaternion rotati
{
StasisSphere cloneSphere = EnsurePlayerHasSphere(playerId);

// Setup the sphere in case it the shot was sent earlier
// Setup the sphere in case the shot was sent earlier
cloneSphere.Shoot(position, rotation, 0, 0, chargeNormalized);
// We override this field (set by .Shoot) with the right data
cloneSphere._consumption = consumption;

// Code from Bullet.Update when finding an object to hit
cloneSphere._visible = true;
cloneSphere.OnMadeVisible();
cloneSphere.OnHit(default);
cloneSphere.EnableField();
cloneSphere.Deactivate();
}

Expand Down
4 changes: 2 additions & 2 deletions NitroxClient/GameLogic/RemotePlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ static void CopyEmitter(FMOD_CustomEmitter src, FMOD_CustomEmitter dst)
private void SetupMixins()
{
InfectedMixin = Body.AddComponent<InfectedMixin>();
InfectedMixin.shaderKeyWord = "UWE_PLAYERINFECTION";
InfectedMixin.shaderKeyWord = InfectedMixin.uwe_playerinfection;
Renderer renderer = PlayerModel.transform.Find("male_geo/diveSuit/diveSuit_hands_geo").GetComponent<Renderer>();
InfectedMixin.renderers = [renderer];

Expand All @@ -432,7 +432,7 @@ private void SetupMixins()
broadcastKillOnDeath = false
};
LiveMixin.health = 100;
// We set the remote player to invincible because we only want this component to detectable but not to work
// We set the remote player to invincible because we only want this component to be detectable but not to work
LiveMixin.invincible = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public static bool Prefix(AggressiveWhenSeeTarget __instance)
return true;
}


return false;
}

Expand All @@ -47,6 +46,11 @@ public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructio

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public sealed partial class AttackCyclops_OnCollisionEnter_Patch : NitroxPatch,
{
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)
Expand All @@ -41,10 +47,4 @@ public static bool ShouldCollisionAnnoyCreature(Collision collision)
{
return AttackCyclops_UpdateAggression_Patch.IsTargetAValidInhabitedCyclops(collision.gameObject);
}

public static bool Prefix(AttackCyclops __instance)
{
return !__instance.TryGetNitroxId(out NitroxId creatureId) ||
Resolve<SimulationOwnership>().HasAnyLockType(creatureId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ public static bool IsTargetAValidInhabitedCyclops(IEcoTarget target)
public static bool IsTargetAValidInhabitedCyclops(GameObject targetObject)
{
// Is a Cyclops
if (!targetObject.GetComponent<CyclopsNoiseManager>())
if (!targetObject.TryGetComponent(out SubRoot subRoot) || !subRoot.isCyclops)
{
return false;
}

// Has the local player inside
if (Player.main && Player.main.currentSub && Player.main.currentSub.gameObject == targetObject)
if (Player.main && Player.main.currentSub == subRoot)
{
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public static bool Prefix(Creature __instance, out NitroxId __state, ref Creatur
{
if (!__instance.TryGetIdOrWarn(out __state, true))
{
Log.WarnOnce($"[{nameof(Creature_ChooseBestAction_Patch)}] Couldn't find an id on {__instance.GetFullHierarchyPath()}");
return true;
}
if (Resolve<SimulationOwnership>().HasAnyLockType(__state))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public sealed partial class SeamothTorpedoWhirlpool_Register_Patch : NitroxPatch
{
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SeamothTorpedoWhirlpool t) => t.Register(default, ref Reflect.Ref<Rigidbody>.Field));

public static bool Prefix(Collider other, ref bool __result)
public static bool Prefix(Collider other)
{
return !other.GetComponentInParent<RemotePlayerIdentifier>(true);
}
Expand Down
14 changes: 7 additions & 7 deletions NitroxPatcher/Patches/Dynamic/StasisSphere_OnHit_Patch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@
namespace NitroxPatcher.Patches.Dynamic;

/// <summary>
/// Prevents remote stasis sphere from triggering hit effets (they're triggered with packets)
/// Prevents remote stasis sphere from triggering hit effets (they're triggered with packets).
/// Broadcasts local player's stasis sphere hits
/// </summary>
public sealed partial class StasisSphere_OnHit_Patch : NitroxPatch, IDynamicPatch
{
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((StasisSphere t) => t.OnHit(default));

private static ushort LocalPlayerId => Resolve<LocalPlayer>().PlayerId;

public static void Prefix(StasisSphere __instance)
public static bool Prefix(StasisSphere __instance)
{
if (__instance.GetComponent<BulletManager.RemotePlayerBullet>())
{
return;
return false;
}

ushort localPlayerId = Resolve<LocalPlayer>().PlayerId;
NitroxVector3 position = __instance.tr.position.ToDto();
NitroxQuaternion rotation = __instance.tr.rotation.ToDto();
// Calculate the chargeNormalized value which was passed to StasisSphere.Shoot
float chargeNormalized = Mathf.Unlerp(__instance.minRadius, __instance.maxRadius, __instance.radius);

Resolve<IPacketSender>().Send(new StasisSphereHit(LocalPlayerId, position, rotation, chargeNormalized, __instance._consumption));
return;
Resolve<IPacketSender>().Send(new StasisSphereHit(localPlayerId, position, rotation, chargeNormalized, __instance._consumption));
return true;
}
}
68 changes: 47 additions & 21 deletions NitroxPatcher/Patches/Dynamic/Vehicle_TorpedoShot_Patch.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using NitroxClient.Communication.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.Unity;
using NitroxModel.Helper;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
Expand All @@ -11,31 +15,53 @@ namespace NitroxPatcher.Patches.Dynamic;

public sealed partial class Vehicle_TorpedoShot_Patch : NitroxPatch, IDynamicPatch
{
private static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => Vehicle.TorpedoShot(default, default, default));
internal static readonly MethodInfo TARGET_METHOD = Reflect.Method(() => Vehicle.TorpedoShot(default, default, default));

public static bool Prefix(Vehicle __instance, ref bool __result, ItemsContainer container, TorpedoType torpedoType, Transform muzzle)
/*
* Inserts a DUP instruction after the SeamothTorpedo object is pushed onto the stack so we can use it later on
* in the inserted callback.
* NB: Ldarg_0 refers to the 1st parameter (ItemsContainer) and Ldarg_1 refers to the 2nd parameter (TorpedoType) as this method is static
*
* MODIFICATION:
* component.Shoot(muzzle.position, aimingTransform.rotation, num, -1f);
* Vehicle_TorpedoShot_Patch.TorpedoShotCallback(dupped object, torpedoType); <--- INSERTED LINE
* return true;
*
* "dupped object" is the SeamothTorpedo object from the line:
* gameObject.GetComponent<SeamothTorpedo>();
*/
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
// (almost) Exact code copy from Vehicle.TorpedoShot because it's impossible to make a transpiler for it without modifying most of the instructions
// (the transpiler wouldn't be readable at all) so the best possibility is to copy the exact and out the created torpedo GameObject
if (torpedoType == null || !container.DestroyItem(torpedoType.techType))
{
return false;
}
GameObject gameObject = GameObject.Instantiate(torpedoType.prefab);
Bullet component = gameObject.GetComponent<SeamothTorpedo>();
Transform aimingTransform = Player.main.camRoot.GetAimingTransform();
Rigidbody componentInParent = muzzle.GetComponentInParent<Rigidbody>();
Vector3 vector = componentInParent ? componentInParent.velocity : Vector3.zero;
float num = Vector3.Dot(aimingTransform.forward, vector);
component.Shoot(muzzle.position, aimingTransform.rotation, num, -1f);
__result = true;
// Always match for one more instruction after the searched one because the cursor will be moved right before it
return new CodeMatcher(instructions).MatchEndForward([
new CodeMatch(OpCodes.Pop),
new CodeMatch(OpCodes.Callvirt, Reflect.Method((GameObject t) => t.GetComponent<SeamothTorpedo>())),
new CodeMatch(OpCodes.Call),
])
.InsertAndAdvance([
new CodeInstruction(OpCodes.Dup),
])
.MatchEndForward([
new CodeMatch(OpCodes.Callvirt, Reflect.Method((Bullet t) => t.Shoot(default, default, default, default))),
new CodeMatch(OpCodes.Ldc_I4_1),
])
.InsertAndAdvance([
new CodeInstruction(OpCodes.Ldarg_1),
new CodeInstruction(OpCodes.Call, Reflect.Method(() => TorpedoShotCallback(default, default)))
])
.InstructionEnumeration();
}

public static void TorpedoShotCallback(SeamothTorpedo seamothTorpedo, TorpedoType torpedoType)
{
NitroxId bulletId = NitroxEntity.GenerateNewId(seamothTorpedo.gameObject);

// Broadcast code
NitroxVector3 position = seamothTorpedo.transform.position.ToDto();
NitroxQuaternion rotation = seamothTorpedo.transform.rotation.ToDto();

NitroxId bulletId = new();
NitroxEntity.SetNewId(gameObject, bulletId);
// In Bullet.Shoot, _consumption = f(lifeTime), lifeTime = g(_consumption), this is g:
float lifeTime = seamothTorpedo._consumption > 0 ? 1f / seamothTorpedo._consumption : 0f;

Resolve<IPacketSender>().Send(new TorpedoShot(bulletId, torpedoType.techType.ToDto(), muzzle.position.ToDto(), aimingTransform.rotation.ToDto(), num, -1));
return false;
Resolve<IPacketSender>().Send(new TorpedoShot(bulletId, torpedoType.techType.ToDto(), position, rotation, seamothTorpedo.speed, lifeTime));
}
}

0 comments on commit 1a987e9

Please sign in to comment.