Add client-side terrain replacement when terrain add is rejected

This commit is contained in:
NGnius (Graham) 2021-08-11 17:50:14 -04:00
parent 7c76c27126
commit 476d6382da
14 changed files with 427 additions and 45 deletions

View file

@ -14,9 +14,6 @@ namespace CLre.API.App
if (gameEngineReady != null) gameEngineReady(this, new GameReady { });
}
public override IEntitiesDB entitiesDB { get; set; }
public override IEntityFactory entityFactory { get; set; }
public void OnFrameworkInitialized()
{
// TODO framework init event
@ -33,9 +30,6 @@ namespace CLre.API.App
{
}
public override IEntitiesDB entitiesDB { get; set; }
public override IEntityFactory entityFactory { get; set; }
public void OnFrameworkInitialized()
{
if (gameFrameworkReady != null) gameFrameworkReady(this, new GameReady { });
@ -55,8 +49,5 @@ namespace CLre.API.App
{
if (menuEngineReady != null) menuEngineReady(this, new MenuReady { });
}
public override IEntitiesDB entitiesDB { get; set; }
public override IEntityFactory entityFactory { get; set; }
}
}

View file

@ -54,9 +54,6 @@ namespace CLre.API.Characters
{
}
public override IEntitiesDB entitiesDB { get; set; }
public override IEntityFactory entityFactory { get; set; }
public bool GetGodModeRequested()
{
if (entitiesDB == null) return false;

View file

@ -21,8 +21,8 @@ namespace CLre.API.Engines
}
public abstract void Ready();
public abstract IEntitiesDB entitiesDB { get; set; }
public abstract IEntityFactory entityFactory { get; set; }
public IEntitiesDB entitiesDB { get; set; }
public IEntityFactory entityFactory { get; set; }
}
/// <summary>
@ -41,8 +41,8 @@ namespace CLre.API.Engines
}
public abstract void Ready();
public abstract IEntitiesDB entitiesDB { get; set; }
public abstract IEntityFactory entityFactory { get; set; }
public IEntitiesDB entitiesDB { get; set; }
public IEntityFactory entityFactory { get; set; }
}
/// <summary>
@ -60,8 +60,8 @@ namespace CLre.API.Engines
}
public abstract void Ready();
public abstract IEntitiesDB entitiesDB { get; set; }
public abstract IEntityFactory entityFactory { get; set; }
public IEntitiesDB entitiesDB { get; set; }
public IEntityFactory entityFactory { get; set; }
}
/// <summary>
@ -79,8 +79,8 @@ namespace CLre.API.Engines
}
public abstract void Ready();
public abstract IEntitiesDB entitiesDB { get; set; }
public abstract IEntityFactory entityFactory { get; set; }
public IEntitiesDB entitiesDB { get; set; }
public IEntityFactory entityFactory { get; set; }
}
[HarmonyPatch(typeof(FrontEnd.MainFrontEnd), "BuildEngines")]

View file

@ -12,8 +12,8 @@ namespace CLre.API.Engines
}
public abstract void Ready();
public abstract IEntitiesDB entitiesDB { get; set; }
public abstract IEntityFactory entityFactory { get; set; }
public IEntitiesDB entitiesDB { get; set; }
public IEntityFactory entityFactory { get; set; }
}
public abstract class GameObsoleteEnginePostBuild : ICLreEngine
@ -24,8 +24,8 @@ namespace CLre.API.Engines
}
public abstract void Ready();
public abstract IEntitiesDB entitiesDB { get; set; }
public abstract IEntityFactory entityFactory { get; set; }
public IEntitiesDB entitiesDB { get; set; }
public IEntityFactory entityFactory { get; set; }
}
[HarmonyPatch(typeof(GameFramework.MainLevel), "BuildDeprecatedEngines")]

View file

@ -3,6 +3,7 @@ using CLre.API.Engines;
using GameNetworkLayer.Shared;
using HarmonyLib;
using Svelto.ECS;
using VoxelFarm.GameServer;
namespace CLre.API.Synergy
{
@ -40,9 +41,6 @@ namespace CLre.API.Synergy
Utility.Logging.MetaLog($"Received CLre handshake! {p}");
}
public override IEntitiesDB entitiesDB { get; set; }
public override IEntityFactory entityFactory { get; set; }
public IEnumerator Sender(SerializedCLreHandshake payload)
{
yield return null;
@ -72,12 +70,15 @@ namespace CLre.API.Synergy
internal static object netMessageListener;
internal static object netMessageSender;
internal static TerrainModelClientServer tmcs;
[HarmonyPostfix]
public static void AfterMethodCall(object ____netMessageListener, object ____netMessageSender)
public static void AfterMethodCall(object ____netMessageListener, object ____netMessageSender, TerrainModelClientServer ____terrainModelServerPrediction)
{
netMessageListener = ____netMessageListener;
netMessageSender = ____netMessageSender;
tmcs = ____terrainModelServerPrediction;
}
}
}

View file

@ -54,9 +54,6 @@ namespace CLre.API.Synergy
});
}
public override IEntitiesDB entitiesDB { get; set; }
public override IEntityFactory entityFactory { get; set; }
public ClientMessagingEngine(): base()
{
App.Client.GameJoin += (_, __) => { MessageSender().Run(); };

View file

@ -0,0 +1,75 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using Game.Handhelds;
using NetworkFramework.Shared;
using UnityEngine;
namespace CLre.API.Synergy.Tweaks
{
public struct SerializedCLreTerrainModifyRejection: ISerializedNetData
{
public RejectionFlag Flags;
public uint resourceId;
public Vector3 hit;
public string toolKey;
public ToolModeType toolMode;
public byte[] Serialize()
{
using (MemoryStream stream = new MemoryStream())
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write((byte)Flags);
writer.Write(resourceId);
writer.Write(hit.x);
writer.Write(hit.y);
writer.Write(hit.z);
writer.Write(toolKey);
writer.Write((byte)toolMode);
return stream.ToArray();
}
}
}
public void Deserialize(byte[] data)
{
using (MemoryStream stream = new MemoryStream(data))
{
using (BinaryReader reader = new BinaryReader(stream))
{
Flags = (RejectionFlag)reader.ReadByte();
resourceId = reader.ReadUInt32();
float x = reader.ReadSingle();
float y = reader.ReadSingle();
float z = reader.ReadSingle();
hit = new Vector3(x, y, z);
toolKey = reader.ReadString();
toolMode = (ToolModeType)reader.ReadByte();
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Ok()
{
return (Flags & RejectionFlag.Rejection) == RejectionFlag.None;
}
}
[Flags]
public enum RejectionFlag : byte
{
None = 0,
Rejection = 1,
Proximity = 1 << 1,
Permission = 1 << 2,
AccountNotFound = 1 << 3,
InitError = 1 << 4,
}
}

View file

@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Game.DataLoader;
using GameNetworkLayer.Client;
using GameNetworkLayer.Shared;
using HarmonyLib;
using NetworkFramework.Shared;
using OpenCVForUnity;
using Svelto.DataStructures;
using Svelto.ECS;
@ -34,6 +37,10 @@ namespace CLre.API.Utility
public delegate void INetMsgClientListener_RegisterListener<T>(NetworkDispatcherCode code, NetCBClient<T> proc) where T: struct, ISerializedNetData;
public delegate void DeprecatedDispatcher_Dispatch<T>(ref T value);
public delegate Dictionary<string, BaseData> IDataDB_GetValues();
// useful reflection functions
public static TFuncProto BuildDelegate<TFuncProto>(MethodInfo method) where TFuncProto : Delegate
{

View file

@ -51,6 +51,7 @@ namespace CLre
Fixes.InitLogSooner.Init();
Fixes.MiniScreenHelper.Init();
Fixes.UnderStructureCollider.Init();
Fixes.TerrainModifyReset.Init();
// API init
API.Synergy.ClientHandshakeEngine.Init();
@ -106,7 +107,11 @@ namespace CLre
API.App.Client.MenuReady += (_, __) => { API.Utility.Logging.MetaLog("Menu engine ready event fired!"); };
API.App.Client.GameReady += (_, __) => { API.Utility.Logging.MetaLog("Game engine ready event fired!"); };
API.App.Client.GameFrameworkReady += (_, __) => { API.Utility.Logging.MetaLog("Game framework ready event fired!"); };
API.App.Client.GameFrameworkReady += (_, __) =>
{
API.Utility.Logging.MetaLog("Game framework ready event fired!");
API.Utility.Logging.MetaLog($"PhotonChat Connection Protocol: {PhotonChatUI.Chat.Instance.ConnectionProtocol}");
};
API.App.Client.GameFrameworkExit += (_, __) => { API.Utility.Logging.MetaLog("Game framework exit event fired!"); };
Character c = Character.Local();

View file

@ -0,0 +1,244 @@
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<API.Synergy.Tweaks.SerializedCLreTerrainModifyRejection> _registerListener;
public override void Ready()
{
_registerListener =
Reflection.MethodAsDelegate<Reflection.INetMsgClientListener_RegisterListener<API.Synergy.Tweaks.SerializedCLreTerrainModifyRejection>>(
"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");
}
}*/
}

View file

@ -1,7 +1,9 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using Game.Handhelds;
using NetworkFramework.Shared;
using UnityEngine;
namespace CLre_server.API.Synergy.Tweaks
{
@ -9,8 +11,14 @@ namespace CLre_server.API.Synergy.Tweaks
{
public RejectionFlag Flags;
public uint Cell;
public uint resourceId;
public Vector3 hit;
public string toolKey;
public ToolModeType toolMode;
public byte[] Serialize()
{
using (MemoryStream stream = new MemoryStream())
@ -18,7 +26,12 @@ namespace CLre_server.API.Synergy.Tweaks
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write((byte)Flags);
writer.Write(Cell);
writer.Write(resourceId);
writer.Write(hit.x);
writer.Write(hit.y);
writer.Write(hit.z);
writer.Write(toolKey);
writer.Write((byte)toolMode);
return stream.ToArray();
}
}
@ -31,7 +44,13 @@ namespace CLre_server.API.Synergy.Tweaks
using (BinaryReader reader = new BinaryReader(stream))
{
Flags = (RejectionFlag)reader.ReadByte();
Cell = reader.ReadUInt32();
resourceId = reader.ReadUInt32();
float x = reader.ReadSingle();
float y = reader.ReadSingle();
float z = reader.ReadSingle();
hit = new Vector3(x, y, z);
toolKey = reader.ReadString();
toolMode = (ToolModeType)reader.ReadByte();
}
}
}

View file

@ -51,5 +51,21 @@ namespace CLre_server.API.Tools
}
}
}
[HarmonyPatch(typeof(AccessTools), "Method",
new Type[] {typeof(Type), typeof(string), typeof(Type[]), typeof(Type[])})]
class AccessTools_Method2_Patch
{
[HarmonyPostfix]
public static void AfterMethodCall(MethodInfo __result, Type type, string name)
{
if (!AccessToolsWarnings.isEnabled) return;
if (__result == null)
{
var method = (new StackTrace()).GetFrame(2).GetMethod();
Utility.Logging.LogWarning($"[{method.DeclaringType.FullName}.{method.Name}] AccessTools.Method(\"{type}\", \"{name}\") returned null result");
}
}
}
}
#endif

View file

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Game.DataLoader;
using GameNetworkLayer.Shared;
using HarmonyLib;
using NetworkFramework.Shared;
@ -33,7 +35,8 @@ namespace CLre_server.API.Utility
public delegate void INetMsgServerListener_RegisterListener<T>(NetworkDispatcherCode code, NetCBServer<T> proc) where T: struct, ISerializedNetData;
public delegate Dictionary<string, BaseData> IDataDB_GetValues();
// useful reflection functions
public static TFuncProto BuildDelegate<TFuncProto>(MethodInfo method) where TFuncProto : Delegate
{

View file

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using CLre_server.API.Synergy.Tweaks;
using CLre_server.API.Utility;
using Game.DataLoader;
using GameNetworkLayer.Shared;
using HarmonyLib;
@ -12,6 +13,7 @@ using Svelto.DataStructures;
using Svelto.ECS;
using UnityEngine;
using User.Server;
using voxelfarm;
namespace CLre_server.Tweaks
{
@ -104,8 +106,6 @@ namespace CLre_server.Tweaks
#endif
if (isPointInAABB)
{
result.Cell = item;
if (!isOwner)
{
result.Flags = RejectionFlag.Proximity
@ -154,19 +154,24 @@ namespace CLre_server.Tweaks
[HarmonyPatch]
class TerrainModificationEngineServer_RemoveTerrainInput_Patch
{
private static API.Utility.Reflection.INetMsgServerSender_SendMessage<SerializedCLreTerrainModifyRejection>
_netMessageSend = null;
private static object _netMsgServerSender;
// reflection caching
private static API.Utility.Reflection.INetMsgServerSender_SendMessage<SerializedCLreTerrainModifyRejection>
_netMessageSend_CLre = null;
[HarmonyPrefix]
public static bool BeforeMethodCall(int senderPlayerId, ref ISerializedNetData data, object ____netMsgServerSender)
{
if (!CLre.Config.terrain_exclusion_zone) return true;
if (_netMessageSend == null)
_netMsgServerSender = ____netMsgServerSender;
if (_netMessageSend_CLre == null)
{
// cache reflection operations on first run
#if DEBUG
API.Utility.Logging.MetaLog("Building SendMessage delegate optimisation");
#endif
_netMessageSend = API.Utility.Reflection
_netMessageSend_CLre = API.Utility.Reflection
.MethodAsDelegate<
API.Utility.Reflection.INetMsgServerSender_SendMessage<SerializedCLreTerrainModifyRejection>
>(
@ -185,12 +190,34 @@ namespace CLre_server.Tweaks
#if DEBUG
API.Utility.Logging.MetaLog("Rejecting terrain modification");
#endif
// TODO optimize
Traverse tmid = Traverse.Create(data);
modifyPayload.resourceId = tmid.Property<uint>("resourceId").Value;
modifyPayload.hit = location;
//modifyPayload.materialIndex = tmid.Property<int>("materialIndex").Value;
modifyPayload.toolKey = tmid.Property<string>("toolKey").Value;
modifyPayload.toolMode = tmid.Property<Game.Handhelds.ToolModeType>("toolMode").Value;
switch (modifyPayload.toolMode)
{
case Game.Handhelds.ToolModeType.Block:
modifyPayload.hit.y -= 0.125f * 2; // each layer is 0.125 thick, block is 3 layers
break;
case Game.Handhelds.ToolModeType.Disc:
break;
case Game.Handhelds.ToolModeType.Voxel:
modifyPayload.toolMode = Game.Handhelds.ToolModeType.Disc; // voxels aren't replaced properly
break;
}
// signal client that stuff failed
_netMessageSend(NetworkDispatcherCode.TerrainModificationFailed, ref modifyPayload, senderPlayerId);
_netMessageSend_CLre(NetworkDispatcherCode.TerrainModificationFailed, ref modifyPayload, senderPlayerId);
// build terrain data for terrain replacement
#if DEBUG
API.Utility.Logging.MetaLog("Filling hole left by terrain modification");
#endif
}
return modifyPayload.Ok();
}
[HarmonyTargetMethod]
public static MethodBase Target()
{