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; /// /// Broadcasts creature death consequences:
/// - Converted to a cooked item
/// - Dead but still has its corpse floating in the water
/// - Eatable decomposition metadata ///
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().angularDrag = base.gameObject.GetComponent().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 Transpiler(IEnumerable 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().Dropped(gameObject, cookedTechType); } public static void BroadcastRemoveCorpse(CreatureDeath creatureDeath) { if (creatureDeath.TryGetNitroxId(out NitroxId creatureId)) { Resolve().StopSimulatingEntity(creatureId); EntityPositionBroadcaster.RemoveEntityMovementControl(creatureDeath.gameObject, creatureId); Resolve().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 metadata = Resolve().Extract(eatable); if (metadata.HasValue) { Resolve().BroadcastMetadataUpdate(eatableId, metadata.Value); } } }