diff --git a/CLre/CLre.csproj b/CLre/CLre.csproj
index d6f2fad..314259a 100644
--- a/CLre/CLre.csproj
+++ b/CLre/CLre.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/CLre_server/API/Config/CLreConfig.cs b/CLre_server/API/Config/CLreConfig.cs
index 11f4a88..2922c71 100644
--- a/CLre_server/API/Config/CLreConfig.cs
+++ b/CLre_server/API/Config/CLreConfig.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
namespace CLre_server.API.Config
{
@@ -7,6 +8,7 @@ namespace CLre_server.API.Config
{
public bool clre_clients_only;
public bool web_server;
+ public bool terrain_exclusion_zone;
public static CLreConfig Default()
{
@@ -14,6 +16,7 @@ namespace CLre_server.API.Config
{
clre_clients_only = false,
web_server = false,
+ terrain_exclusion_zone = false,
};
}
@@ -22,6 +25,37 @@ namespace CLre_server.API.Config
return UnityEngine.JsonUtility.FromJson(s);
}
+ public static CLreConfig FromFile(string path)
+ {
+ string s = File.ReadAllText(path);
+ return FromString(s);
+ }
+
+ public static CLreConfig FromFileSafely(string path)
+ {
+ // try to load config file
+ CLreConfig conf = Default();
+ try
+ {
+ string s = File.ReadAllText(path);
+ conf = FromString(s);
+ }
+ catch (Exception e)
+ {
+ Utility.Logging.LogWarning($"Failed to load {path}: {e}\n{e.StackTrace}");
+ try
+ {
+ File.WriteAllText(path, conf.ToString());
+ }
+ catch (Exception e2)
+ {
+ Utility.Logging.LogWarning($"Failed to write {path}: {e2}\n{e2.StackTrace}");
+ }
+ }
+
+ return conf;
+ }
+
public override string ToString()
{
return UnityEngine.JsonUtility.ToJson(this, true);
diff --git a/CLre_server/API/Engines/ServerEngines.cs b/CLre_server/API/Engines/ServerEngines.cs
index 90f598f..fabc9ed 100644
--- a/CLre_server/API/Engines/ServerEngines.cs
+++ b/CLre_server/API/Engines/ServerEngines.cs
@@ -13,8 +13,8 @@ namespace CLre_server.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 IDataDB dataDB { get; set; }
}
@@ -26,8 +26,8 @@ namespace CLre_server.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 IDataDB dataDB { get; set; }
}
diff --git a/CLre_server/API/MainServer/ServerEngines.cs b/CLre_server/API/MainServer/ServerEngines.cs
index b55c0c3..85b11b3 100644
--- a/CLre_server/API/MainServer/ServerEngines.cs
+++ b/CLre_server/API/MainServer/ServerEngines.cs
@@ -28,9 +28,6 @@ namespace CLre_server.API.MainServer
});
}
- public override IEntitiesDB entitiesDB { get; set; }
- public override IEntityFactory entityFactory { get; set; }
-
public void OnFrameworkInitialized()
{
GameServerSettings gss = Server.Instance.GameServerSettings;
diff --git a/CLre_server/API/Synergy/ServerHandshakeEngine.cs b/CLre_server/API/Synergy/ServerHandshakeEngine.cs
index 9578a97..eccb155 100644
--- a/CLre_server/API/Synergy/ServerHandshakeEngine.cs
+++ b/CLre_server/API/Synergy/ServerHandshakeEngine.cs
@@ -50,9 +50,6 @@ namespace CLre_server.API.Synergy
Sender(payload, playerId).Run();
}
- public override IEntitiesDB entitiesDB { get; set; }
- public override IEntityFactory entityFactory { get; set; }
-
public IEnumerator Sender(SerializedCLreHandshake payload, int playerId)
{
yield return null;
diff --git a/CLre_server/API/Synergy/ServerMessagingEngine.cs b/CLre_server/API/Synergy/ServerMessagingEngine.cs
index 409dc08..8dd863d 100644
--- a/CLre_server/API/Synergy/ServerMessagingEngine.cs
+++ b/CLre_server/API/Synergy/ServerMessagingEngine.cs
@@ -56,9 +56,6 @@ namespace CLre_server.API.Synergy
});
}
- public override IEntitiesDB entitiesDB { get; set; }
- public override IEntityFactory entityFactory { get; set; }
-
public ServerMessagingEngine(): base()
{
MainServer.Server.Instance.Connected += (_, __) => { MessageSender().Run(); };
diff --git a/CLre_server/API/Synergy/Tweaks/SerializedCLreTerrainModifyRejection.cs b/CLre_server/API/Synergy/Tweaks/SerializedCLreTerrainModifyRejection.cs
new file mode 100644
index 0000000..e9ba82b
--- /dev/null
+++ b/CLre_server/API/Synergy/Tweaks/SerializedCLreTerrainModifyRejection.cs
@@ -0,0 +1,56 @@
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+using NetworkFramework.Shared;
+
+namespace CLre_server.API.Synergy.Tweaks
+{
+ public struct SerializedCLreTerrainModifyRejection: ISerializedNetData
+ {
+ public RejectionFlag Flags;
+
+ public uint Cell;
+
+ public byte[] Serialize()
+ {
+ using (MemoryStream stream = new MemoryStream())
+ {
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ writer.Write((byte)Flags);
+ writer.Write(Cell);
+ return stream.ToArray();
+ }
+ }
+ }
+
+ public void Deserialize(byte[] data)
+ {
+ using (MemoryStream stream = new MemoryStream(data))
+ {
+ using (BinaryReader reader = new BinaryReader(stream))
+ {
+ Flags = (RejectionFlag)reader.ReadByte();
+ Cell = reader.ReadUInt32();
+ }
+ }
+ }
+
+ [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,
+ }
+}
diff --git a/CLre_server/CLre_server.cs b/CLre_server/CLre_server.cs
index fb9ce2c..9944e19 100644
--- a/CLre_server/CLre_server.cs
+++ b/CLre_server/CLre_server.cs
@@ -88,26 +88,11 @@ namespace CLre_server
API.MainServer.Server.Instance.InitComplete += (_, __) => API.Utility.Logging.MetaLog("(!) Server successfully initialised");
#endif
// try to load config file
- try
- {
- string s = File.ReadAllText("CLre_server.json");
- Config = CLreConfig.FromString(s);
- }
- catch (Exception e)
- {
- API.Utility.Logging.LogWarning($"Failed to load CLre_server.json: {e}\n{e.StackTrace}");
- try
- {
- File.WriteAllText("CLre_server.json", Config.ToString());
- }
- catch (Exception e2)
- {
- API.Utility.Logging.LogWarning($"Failed to write CLre_server.json: {e2}\n{e2.StackTrace}");
- }
- }
+ Config = CLreConfig.FromFileSafely("CLre_server.json");
// init config-dependent functionality
WebServer.Init();
API.Synergy.CLreEnforcer.Init();
+ Tweaks.TerrainModificationExclusionZone.Init();
// Log info
API.Utility.Logging.MetaLog($"{Name} init complete.");
}
@@ -129,6 +114,7 @@ namespace CLre_server
API.Utility.Logging.Log(sb.ToString());
}
+#if DEBUG
public override void OnGUI()
{
if (GUI.Button(new Rect(10, 10, 50, 50), "QUIT"))
@@ -136,5 +122,6 @@ namespace CLre_server
Application.Quit(); // yeet
}
}
+#endif
}
}
diff --git a/CLre_server/CLre_server.csproj b/CLre_server/CLre_server.csproj
index 545b744..d5bc888 100644
--- a/CLre_server/CLre_server.csproj
+++ b/CLre_server/CLre_server.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/CLre_server/Tweaks/TerrainModificationExclusionZone.cs b/CLre_server/Tweaks/TerrainModificationExclusionZone.cs
new file mode 100644
index 0000000..107193f
--- /dev/null
+++ b/CLre_server/Tweaks/TerrainModificationExclusionZone.cs
@@ -0,0 +1,238 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using CLre_server.API.Synergy.Tweaks;
+using Game.DataLoader;
+using GameNetworkLayer.Shared;
+using HarmonyLib;
+using NetworkFramework.Shared;
+using Svelto.DataStructures;
+using Svelto.ECS;
+using UnityEngine;
+using User.Server;
+
+namespace CLre_server.Tweaks
+{
+ public class TerrainModificationExclusionZone
+ {
+ private static TerrainExclusionZoneEngine teze = null;
+
+ internal static object _serverStructureExclusionZoneNode = null;
+
+ internal static void Init()
+ {
+ if (!CLre.Config.terrain_exclusion_zone) return;
+ teze = new TerrainExclusionZoneEngine();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static SerializedCLreTerrainModifyRejection HasExclusionZoneAtLocationWithMember(ref Vector3 location, int playerId)
+ {
+ if (_serverStructureExclusionZoneNode == null)
+ {
+ SerializedCLreTerrainModifyRejection result = default;
+ result.Flags = RejectionFlag.InitError | RejectionFlag.Rejection;
+ return result; // this shouldn't happen (I hope...)
+ }
+ return teze.HasExclusionZoneAtLocationWithMember(ref location, playerId, _serverStructureExclusionZoneNode);
+ }
+ }
+
+ public class TerrainExclusionZoneEngine : API.Engines.ServerEnginePostBuild
+ {
+ public override void Ready()
+ {
+ }
+
+ public SerializedCLreTerrainModifyRejection HasExclusionZoneAtLocationWithMember(ref Vector3 digPosition, int playerId, object exclusionZonesNode)
+ {
+ SerializedCLreTerrainModifyRejection result = default;
+ // Similar to Game.Building.ExclusionZone.ServerStructureExclusionZoneEngine.FindPotentialMatchingExclusionZones
+ // Match player index to Guid
+ FieldInfo f = AccessTools.Field(AccessTools.TypeByName("User.Server.AccountExclusiveGroups"), "accountGroup");
+ ExclusiveGroup accountGroup = (ExclusiveGroup) f.GetValue(null);
+ ReadOnlyCollectionStruct accounts =
+ entitiesDB.QueryEntityViews(accountGroup);
+ if (playerId >= accounts.Count)
+ {
+ // playerId isn't in connected accounts
+ API.Utility.Logging.LogWarning("PlayerId isn't in connected accounts, denying terrain modification");
+ result.Flags = RejectionFlag.AccountNotFound | RejectionFlag.Rejection;
+ return result; // this shouldn't happen (I hope...)
+ }
+ Guid playerGuid = accounts[playerId].accountId.publicId;
+ // find exclusion zone where terrain modification is happening
+ float cellSize = dataDB.GetDefaultValue().WorldCellRadius * 0.25f;
+ object structureCellId = GetCellIdFromPosition(ref digPosition, cellSize);
+ // TODO optimize
+ Traverse exclusionNodeData = Traverse.Create(exclusionZonesNode)
+ .Field("serverStructureExclusionZoneDataComponent");
+ Traverse exclusionZoneIdsByWorldCell = exclusionNodeData
+ .Property("exclusionZoneIdsByWorldCell"); // Dictionary>
+ Traverse exclusionZonesByUniqueId = exclusionNodeData
+ .Property("exclusionZonesByUniqueId"); // Dictionary
+ bool exists = exclusionZoneIdsByWorldCell
+ .Method("ContainsKey", new[] {structureCellId}).GetValue();
+ if (exists)
+ {
+#if DEBUG
+ API.Utility.Logging.MetaLog("Exclusion zone cell found, iterating over zones...");
+#endif
+ HashSet zoneIds = exclusionZoneIdsByWorldCell
+ .Property>("Item", index: new[] {structureCellId})
+ .Value;
+ foreach (uint item in zoneIds)
+ {
+ Traverse serverStructureExclusionZone = exclusionZonesByUniqueId
+ .Property("Item", index: new object[] {item});
+ Traverse structureExclusionZone = serverStructureExclusionZone
+ .Property("structureExclusionZone");
+ bool isOwner = serverStructureExclusionZone
+ .Method("CheckIsOwner", playerGuid)
+ .GetValue();
+#if DEBUG
+ API.Utility.Logging.MetaLog($"IsOwner? {isOwner}");
+#endif
+ Game.Building.AABB aabb = structureExclusionZone.Field("_exclusionZoneAabb")
+ .Field("_aabb").Value;
+ bool isPointInAABB = IsWithin(ref digPosition, ref aabb);
+#if DEBUG
+ API.Utility.Logging.MetaLog($"IsPointInAABB? {isPointInAABB}");
+ API.Utility.Logging.MetaLog($"AABB max:{aabb.max}, min: {aabb.min} dig: {digPosition}");
+#endif
+ if (isPointInAABB)
+ {
+ result.Cell = item;
+
+ if (!isOwner)
+ {
+ result.Flags = RejectionFlag.Proximity
+ | RejectionFlag.Rejection
+ | RejectionFlag.Permission;
+ }
+ return result;
+ }
+ }
+ }
+#if DEBUG
+ API.Utility.Logging.MetaLog("Allowing player to modify terrain");
+#endif
+ result.Flags = RejectionFlag.None;
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static object GetCellIdFromPosition(ref Vector3 playerPosition, float cellSize)
+ {
+ // This uses decompiled code from Game.WorldGrid.StructureGridUtils:GetCellIdFromPosition
+ // 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)
+ float num = 1f / cellSize;
+ int x = Mathf.CeilToInt((playerPosition.x - cellSize * 0.5f) * num);
+ int y = Mathf.CeilToInt((playerPosition.y - cellSize * 0.5f) * num);
+ int z = Mathf.CeilToInt((playerPosition.z - cellSize * 0.5f) * num);
+ // TODO optimize
+ // Create StructureCellId by jumping through hoops
+ return AccessTools.TypeByName("Game.WorldGrid.StructureCellId")
+ .GetConstructor(AccessTools.all, null, new[] {typeof(int), typeof(int), typeof(int)}, null)
+ .Invoke(new object[] {x, y, z});
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsWithin(ref Vector3 point, ref Game.Building.AABB bounds)
+ {
+ return point.x > bounds.min.x && point.x < bounds.max.x
+ && point.y > bounds.min.y && point.y < bounds.max.y
+ && point.z > bounds.min.z && point.x < bounds.max.z;
+ }
+ }
+
+ [HarmonyPatch]
+ class TerrainModificationEngineServer_RemoveTerrainInput_Patch
+ {
+ private static API.Utility.Reflection.INetMsgServerSender_SendMessage
+ _netMessageSend = null;
+
+ [HarmonyPrefix]
+ public static bool BeforeMethodCall(int senderPlayerId, ref ISerializedNetData data, object ____netMsgServerSender)
+ {
+ if (!CLre.Config.terrain_exclusion_zone) return true;
+ if (_netMessageSend == null)
+ {
+#if DEBUG
+ API.Utility.Logging.MetaLog("Building SendMessage delegate optimisation");
+#endif
+ _netMessageSend = API.Utility.Reflection
+ .MethodAsDelegate<
+ API.Utility.Reflection.INetMsgServerSender_SendMessage
+ >(
+ "GameNetworkLayer.Server.INetMsgServerSender:SendMessage",
+ generics: new [] {typeof(SerializedCLreTerrainModifyRejection)},
+ instance: ____netMsgServerSender);
+ }
+#if DEBUG
+ API.Utility.Logging.MetaLog("Intercepting TerrainModificationEngineServer.RemoveTerrainInput()");
+#endif
+ Vector3 location = Traverse.Create(data).Property("hit").Value;
+ API.Utility.Logging.MetaLog($"location is null? {location == Vector3.zero}");
+ SerializedCLreTerrainModifyRejection modifyPayload = TerrainModificationExclusionZone.HasExclusionZoneAtLocationWithMember(ref location, senderPlayerId);
+ if (!modifyPayload.Ok())
+ {
+#if DEBUG
+ API.Utility.Logging.MetaLog("Rejecting terrain modification");
+#endif
+ // signal client that stuff failed
+ _netMessageSend(NetworkDispatcherCode.TerrainModificationFailed, ref modifyPayload, senderPlayerId);
+ }
+ return modifyPayload.Ok();
+ }
+
+ [HarmonyTargetMethod]
+ public static MethodBase Target()
+ {
+ return AccessTools.Method("GameServer.VoxelFarm.Server.TerrainModificationEngineServer:RemoveTerrainInput");
+ }
+ }
+
+ [HarmonyPatch]
+ class ServerStructureExclusionZoneEngine_Add_Patch
+ {
+ [HarmonyPostfix]
+ public static void AfterMethodCall(object entityView)
+ {
+ TerrainModificationExclusionZone._serverStructureExclusionZoneNode = entityView;
+#if DEBUG
+ API.Utility.Logging.MetaLog("Got TerrainModificationExclusionZone._serverStructureExclusionZoneNode");
+#endif
+ }
+
+ [HarmonyTargetMethod]
+ public static MethodBase Target()
+ {
+ return AccessTools.Method("Game.Building.ExclusionZone.ServerStructureExclusionZoneEngine:Add", new Type[] {AccessTools.TypeByName("Game.Building.ExclusionZone.ServerStructureExclusionZonesNode")});
+ }
+ }
+
+ [HarmonyPatch]
+ class ServerStructureExclusionZoneEngine_Remove_Patch
+ {
+ [HarmonyPostfix]
+ public static void AfterMethodCall()
+ {
+ TerrainModificationExclusionZone._serverStructureExclusionZoneNode = null;
+#if DEBUG
+ API.Utility.Logging.MetaLog("Yeeted TerrainModificationExclusionZone._serverStructureExclusionZoneNode");
+#endif
+ }
+
+ [HarmonyTargetMethod]
+ public static MethodBase Target()
+ {
+ return AccessTools.Method("Game.Building.ExclusionZone.ServerStructureExclusionZoneEngine:Remove", new Type[] {AccessTools.TypeByName("Game.Building.ExclusionZone.ServerStructureExclusionZonesNode")});
+ }
+ }
+}
\ No newline at end of file
diff --git a/CLre_server/WebStatus/StatusEndpoints.cs b/CLre_server/WebStatus/StatusEndpoints.cs
index 0ffb959..42fefdf 100644
--- a/CLre_server/WebStatus/StatusEndpoints.cs
+++ b/CLre_server/WebStatus/StatusEndpoints.cs
@@ -77,9 +77,6 @@ namespace CLre_server.WebStatus
pollLoop().Run();
}
- public override IEntitiesDB entitiesDB { get; set; }
- public override IEntityFactory entityFactory { get; set; }
-
private delegate void PlayerPositionFunc();
private PlayerPositionFunc _playerPositionFunc = null;