using System; using System.Reflection; using System.Runtime.CompilerServices; using CLre.API.Synergy; using CLre.API.Utility; using Game.DataLoader; using Game.Handhelds; using GameNetworkLayer.Shared; using HarmonyLib; using NetworkFramework.Shared; using Svelto.ECS; using UnityEngine; using VoxelFarm.GameServer; namespace CLre.Fixes { [Bugfix(name = "TerrainModificationFailedHandler", description = "Actually handle TerrainModificationFailed network messages", more = "https://trello.com/c/Pq5lcB1p/23-moderation-tools", component = BugfixType.Initialiser, id = 8)] public class TerrainModifyReset { private static TerrainModifyResetEngine _tmrEngine = null; public static void Init() { _tmrEngine = new TerrainModifyResetEngine(); } } [Bugfix(name = "TerrainModificationFailedHandler", description = "Actually handle TerrainModificationFailed network messages", more = "https://trello.com/c/Pq5lcB1p/23-moderation-tools", component = BugfixType.Workaround, id = 8)] public class TerrainModifyResetEngine : API.Engines.GameObsoleteEnginePostBuild, IDataAccess { private Reflection.INetMsgClientListener_RegisterListener _registerListener; public override void Ready() { _registerListener = Reflection.MethodAsDelegate>( "GameNetworkLayer.Client.NetMessageClientListener:RegisterListener", generics: new [] {typeof(API.Synergy.Tweaks.SerializedCLreTerrainModifyRejection)}, instance: MainLevel_BuildClasses_Patch.netMessageListener); _registerListener(NetworkDispatcherCode.TerrainModificationFailed, OnMessageReceived); } private void OnMessageReceived(ref API.Synergy.Tweaks.SerializedCLreTerrainModifyRejection data) { if (!data.Ok()) { // reset terrain visuals // TODO optimise #if DEBUG API.Utility.Logging.MetaLog($"data.resourceId: {data.resourceId}"); #endif AddTerrain(ref data); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void GetTerrainRelativePosition(ref Vector3 pos) { // This uses decompiled code from VoxelFarm.Shared.VoxelFarmGameUtils:GetTerrainRelativePosition // there's no point in calling that simple function when I have to jump through hoops with reflection // // there's also nothing particularly unique (ie copyrightable) about this code, // so the lawyers can suck it (also suing a benevolent project is a shitty move) pos.x /= 0.083333336f; pos.y /= 0.041666668f; pos.z /= 0.083333336f; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void AddTerrain(ref API.Synergy.Tweaks.SerializedCLreTerrainModifyRejection data) { // This uses decompiled code from VoxelFarm.GameServer.TerrainModelClientServer:AddTerrain // there's no point in calling that simple function when I have to jump through hoops with reflection // to supply the parameters to it // // Also this is not unique functionality, and comes from the logic of how the placement modes work switch (data.toolMode) { case ToolModeType.Block: MainLevel_BuildClasses_Patch.tmcs.AddDisc(0, data.hit, (int)data.resourceId, 5, 5); break; case ToolModeType.Disc: MainLevel_BuildClasses_Patch.tmcs.AddDisc(0, data.hit, (int)data.resourceId, 1, 5); break; case ToolModeType.Voxel: MainLevel_BuildClasses_Patch.tmcs.AddSingleVoxel(0, data.hit, (int)data.resourceId); break; } } public IDataDB dataDB { get; set; } } [Bugfix(name = "TerrainModificationFailedHandler", description = "Actually handle TerrainModificationFailed network messages", more = "https://trello.com/c/Pq5lcB1p/23-moderation-tools", component = BugfixType.HarmonyPatch, id = 8)] [HarmonyPatch] class TerrainPendingModificationEngineClient_Add_Patch { internal static object _terrainModifyInputNode = null; [HarmonyPrefix] public static void BeforeMethodCall(object node) { #if DEBUG API.Utility.Logging.MetaLog("Intercepting VoxelFarm.Client.TerrainPendingModificationEngineClient:Add()"); #endif _terrainModifyInputNode = node; } [HarmonyTargetMethod] public static MethodBase Target() { return AccessTools.Method("VoxelFarm.Client.TerrainPendingModificationEngineClient:Add", new []{ AccessTools.TypeByName("VoxelFarm.Shared.TerrainModifyInputNode")}); } } /*[Bugfix(name = "TerrainModificationFailedHandler", description = "Actually handle TerrainModificationFailed network messages", more = "https://trello.com/c/Pq5lcB1p/23-moderation-tools", component = BugfixType.HarmonyPatch, id = 8)] [HarmonyPatch] class TerrainPendingModificationEngineClient_OnBlockRemoved_Patch { [HarmonyPrefix] public static bool BeforeMethodCall(int sender, ref ISerializedNetData terrainModifyInputData) { #if DEBUG API.Utility.Logging.MetaLog($"Intercepting VoxelFarm.Client.TerrainPendingModificationEngineClient:OnBlockRemoved({sender}, {terrainModifyInputData})"); #endif return false; } [HarmonyTargetMethod] public static MethodBase Target() { return AccessTools.Method("VoxelFarm.Client.TerrainPendingModificationEngineClient:OnBlockRemoved"); } }*/ [Bugfix(name = "TerrainModificationFailedHandler", description = "Actually handle TerrainModificationFailed network messages", more = "https://trello.com/c/Pq5lcB1p/23-moderation-tools", component = BugfixType.Debug, id = 8)] [HarmonyPatch] class SpadeEngine_FinishDigging_Patch { internal static int CurrentMaterialId = 0; [HarmonyPrefix] public static void BeforeMethodCall(object toolNode, int ____currentMaterialId) { #if DEBUG API.Utility.Logging.MetaLog($"Intercepting Game.Handhelds.SpadeEngine:FinishDigging:GetTerrainMaterial(...) material:{____currentMaterialId}"); #endif CurrentMaterialId = ____currentMaterialId; } [HarmonyTargetMethod] public static MethodBase Target() { return AccessTools.Method("Game.Handhelds.SpadeEngine:FinishDigging"); } } [Bugfix(name = "TerrainModificationFailedHandler", description = "Actually handle TerrainModificationFailed network messages", more = "https://trello.com/c/Pq5lcB1p/23-moderation-tools", component = BugfixType.Debug, id = 8)] [HarmonyPatch] class TerrainModifyInputData_InjectValues_Patch { [HarmonyPrefix] public static void BeforeMethodCall(ref uint resourceId) { #if DEBUG API.Utility.Logging.MetaLog($"VoxelFarm.Shared.TerrainModifyInputData:InjectValues({resourceId}, ...)"); #endif if (resourceId == 0) resourceId = (uint) SpadeEngine_FinishDigging_Patch.CurrentMaterialId; } [HarmonyTargetMethod] public static MethodBase Target() { return AccessTools.Method("VoxelFarm.Shared.TerrainModifyInputData:InjectValues"); } } [Bugfix(name = "TerrainModificationFailedHandler", description = "Actually handle TerrainModificationFailed network messages", more = "https://trello.com/c/Pq5lcB1p/23-moderation-tools", component = BugfixType.HarmonyPatch, id = 8)] [HarmonyPatch] class NetMessageClientListener_RegisterListener_Patch { [HarmonyPrefix] public static bool BeforeMethodCall(NetworkDispatcherCode code) { #if DEBUG API.Utility.Logging.MetaLog($"Intercepting GameNetworkLayer.Client.NetMessageClientListener:RegisterListener({code}, ...)"); #endif // don't allow for standard TerrainModificationFailed listener to be registered // because it's a different type and that's illegal return code != NetworkDispatcherCode.TerrainModificationFailed; } [HarmonyTargetMethod] public static MethodBase Target() { return AccessTools.Method("GameNetworkLayer.Client.NetMessageClientListener:RegisterListener", generics: new []{ typeof(SerializedEmptyNetData)}); } } // this disables terrain destruction /*[Bugfix(name = "TerrainModificationFailedHandler", description = "Actually handle TerrainModificationFailed network messages", more = "https://trello.com/c/Pq5lcB1p/23-moderation-tools", component = BugfixType.Debug, id = 8)] [HarmonyPatch] class TerrainModificationEngineServer_RemoveTerrainInput_Patch { [HarmonyPrefix] public static bool BeforeMethodCall(int senderPlayerId, ref ISerializedNetData data) { #if DEBUG API.Utility.Logging.MetaLog($"Intercepting client-side GameServer.VoxelFarm.Server.TerrainModificationEngineServer:RemoveTerrainInput({senderPlayerId}, {data})"); #endif return false; } [HarmonyTargetMethod] public static MethodBase Target() { return AccessTools.Method("GameServer.VoxelFarm.Server.TerrainModificationEngineServer:RemoveTerrainInput"); } }*/ }