Add optional ability to stop terrain modification in exclusion zones (for players not allowed to place structures in zone)
This commit is contained in:
parent
34a4c2844f
commit
7c76c27126
11 changed files with 338 additions and 35 deletions
|
@ -11,7 +11,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lib.Harmony" Version="2.0.4" />
|
||||
<PackageReference Include="Lib.Harmony" Version="2.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -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<CLreConfig>(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);
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(); };
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lib.Harmony" Version="2.0.4" />
|
||||
<PackageReference Include="Lib.Harmony" Version="2.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
238
CLre_server/Tweaks/TerrainModificationExclusionZone.cs
Normal file
238
CLre_server/Tweaks/TerrainModificationExclusionZone.cs
Normal file
|
@ -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<AccountIdServerNode> accounts =
|
||||
entitiesDB.QueryEntityViews<AccountIdServerNode>(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<WorldCellData>().WorldCellRadius * 0.25f;
|
||||
object structureCellId = GetCellIdFromPosition(ref digPosition, cellSize);
|
||||
// TODO optimize
|
||||
Traverse exclusionNodeData = Traverse.Create(exclusionZonesNode)
|
||||
.Field("serverStructureExclusionZoneDataComponent");
|
||||
Traverse exclusionZoneIdsByWorldCell = exclusionNodeData
|
||||
.Property("exclusionZoneIdsByWorldCell"); // Dictionary<StructureCellId, HashSet<uint>>
|
||||
Traverse exclusionZonesByUniqueId = exclusionNodeData
|
||||
.Property("exclusionZonesByUniqueId"); // Dictionary<uint, ServerStructureExclusionZone>
|
||||
bool exists = exclusionZoneIdsByWorldCell
|
||||
.Method("ContainsKey", new[] {structureCellId}).GetValue<bool>();
|
||||
if (exists)
|
||||
{
|
||||
#if DEBUG
|
||||
API.Utility.Logging.MetaLog("Exclusion zone cell found, iterating over zones...");
|
||||
#endif
|
||||
HashSet<uint> zoneIds = exclusionZoneIdsByWorldCell
|
||||
.Property<HashSet<uint>>("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<bool>();
|
||||
#if DEBUG
|
||||
API.Utility.Logging.MetaLog($"IsOwner? {isOwner}");
|
||||
#endif
|
||||
Game.Building.AABB aabb = structureExclusionZone.Field("_exclusionZoneAabb")
|
||||
.Field<Game.Building.AABB>("_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<SerializedCLreTerrainModifyRejection>
|
||||
_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<SerializedCLreTerrainModifyRejection>
|
||||
>(
|
||||
"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<Vector3>("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")});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue