265 lines
No EOL
12 KiB
C#
265 lines
No EOL
12 KiB
C#
using System;
|
|
using System.Collections;
|
|
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;
|
|
using NetworkFramework.Shared;
|
|
using Svelto.DataStructures;
|
|
using Svelto.ECS;
|
|
using UnityEngine;
|
|
using User.Server;
|
|
using voxelfarm;
|
|
|
|
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)
|
|
{
|
|
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 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;
|
|
_netMsgServerSender = ____netMsgServerSender;
|
|
if (_netMessageSend_CLre == null)
|
|
{
|
|
// cache reflection operations on first run
|
|
#if DEBUG
|
|
API.Utility.Logging.MetaLog("Building SendMessage delegate optimisation");
|
|
#endif
|
|
_netMessageSend_CLre = 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
|
|
// 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_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()
|
|
{
|
|
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")});
|
|
}
|
|
}
|
|
} |