From 547655c326aefeb144c8454c6cd0fcb06650fb09 Mon Sep 17 00:00:00 2001 From: / Date: Sun, 29 Dec 2024 21:15:58 +0100 Subject: [PATCH] zz --- GlobalShared/API/GlobalAPI.cs | 174 ++++ GlobalShared/API/IBooleanFunction.cs | 17 + GlobalShared/API/IControllableEntityData.cs | 9 + GlobalShared/API/ICustomData.cs | 9 + GlobalShared/API/ICustomManager.cs | 7 + GlobalShared/API/IGridData.cs | 26 + GlobalShared/API/OcTreeVariables.cs | 34 + GlobalShared/Config/IPluginConfig.cs | 14 + GlobalShared/Events/BlockEvents.cs | 27 + GlobalShared/Events/GridEvents.cs | 13 + GlobalShared/GlobalShared.projitems | 49 + GlobalShared/GlobalShared.shproj | 13 + GlobalShared/Logging/IPluginLogger.cs | 28 + GlobalShared/Logging/LogFormatter.cs | 90 ++ GlobalShared/Logging/LoggingLevel.cs | 20 + GlobalShared/OcTree/Bag.cs | 173 +++ .../OcTree/Data/ControllableEntityData.cs | 51 + GlobalShared/OcTree/Data/GridData.cs | 334 ++++++ GlobalShared/OcTree/Data/IOcTreeData.cs | 17 + GlobalShared/OcTree/GenericOcTree.cs | 986 ++++++++++++++++++ GlobalShared/OcTree/IContainer.cs | 12 + GlobalShared/OcTree/OcTree.cs | 707 +++++++++++++ GlobalShared/OcTree/OcTreeHandler.cs | 140 +++ GlobalShared/OcTree/Pool.cs | 36 + GlobalShared/Patches/CubeGridPatch.cs | 66 ++ GlobalShared/Patches/EntityNamePatch.cs | 85 ++ GlobalShared/Patches/MyConveyorLinePatch.cs | 17 + .../Patches/MyGamePruningStructurePatch.cs | 55 + .../MyMechanicalConnectionBlockBasePatch.cs | 33 + GlobalShared/Patches/MyTerminalBlockPatch.cs | 24 + GlobalShared/Patches/TerminalSystemPatch.cs | 31 + GlobalShared/Patching/PatchHelpers.cs | 27 + GlobalShared/Plugin/GlobalInstance.cs | 24 + GlobalShared/Plugin/GlobalStatic.cs | 40 + GlobalShared/Plugin/IGlobalPlugin.cs | 15 + GlobalShared/Properties/AssemblyInfo.cs | 35 + GlobalShared/Util/KeenExtensions.cs | 19 + GlobalShared/Util/NexusUtils.cs | 42 + GlobalShared/Util/UintCache.cs | 93 ++ GlobalTorch/API/Discord/DiscordStructs.cs | 263 +++++ GlobalTorch/API/Discord/DiscordUtil.cs | 129 +++ GlobalTorch/API/Discord/DiscordWebhook.cs | 77 ++ GlobalTorch/API/EventResult.cs | 9 + GlobalTorch/API/GlobalModApi.cs | 28 + GlobalTorch/API/GlobalServerModApi.cs | 28 + .../API/Libraries/SENetworkAPI/Client.cs | 104 ++ .../API/Libraries/SENetworkAPI/Command.cs | 22 + .../API/Libraries/SENetworkAPI/NetSync.cs | 498 +++++++++ .../API/Libraries/SENetworkAPI/Network.cs | 323 ++++++ .../API/Libraries/SENetworkAPI/Server.cs | 158 +++ .../Libraries/SENetworkAPI/SessionTools.cs | 13 + GlobalTorch/API/Util/CollectionUtils.cs | 49 + GlobalTorch/API/Util/FacUtils.cs | 45 + GlobalTorch/API/Util/FileUtils.cs | 71 ++ GlobalTorch/API/Util/GlobalLogManager.cs | 74 ++ GlobalTorch/App.config | 23 + GlobalTorch/AudioSendManager.cs | 90 ++ GlobalTorch/Config/WpfConfig.cs | 183 ++++ GlobalTorch/DBManager.cs | 16 + GlobalTorch/GlobalCommands.cs | 38 + GlobalTorch/GlobalPlugin.cs | 220 ++++ GlobalTorch/GlobalTorch.csproj | 273 +++++ GlobalTorch/GlobalTorch.csproj.user | 9 + GlobalTorch/LegacyConfig.cs | 52 + GlobalTorch/NexusIntegration.cs | 28 + GlobalTorch/OpusWrapper.dll | Bin 0 -> 10240 bytes GlobalTorch/Overwrites/MyEntitiesButBetter.cs | 366 +++++++ GlobalTorch/Patches/MyScriptManagerPatch.cs | 71 ++ GlobalTorch/Patches/Patcher.cs | 131 +++ GlobalTorch/Program.cs | 44 + GlobalTorch/Properties/AssemblyInfo.cs | 35 + GlobalTorch/TorchLogger.cs | 114 ++ GlobalTorch/Util/MTObservableCollection.cs | 34 + GlobalTorch/Util/SEUtils.cs | 60 ++ GlobalTorch/Util/SendBuffer.cs | 36 + GlobalTorch/manifest.xml | 7 + GlobalTorch/opus.dll | Bin 0 -> 426640 bytes 77 files changed, 7313 insertions(+) create mode 100644 GlobalShared/API/GlobalAPI.cs create mode 100644 GlobalShared/API/IBooleanFunction.cs create mode 100644 GlobalShared/API/IControllableEntityData.cs create mode 100644 GlobalShared/API/ICustomData.cs create mode 100644 GlobalShared/API/ICustomManager.cs create mode 100644 GlobalShared/API/IGridData.cs create mode 100644 GlobalShared/API/OcTreeVariables.cs create mode 100644 GlobalShared/Config/IPluginConfig.cs create mode 100644 GlobalShared/Events/BlockEvents.cs create mode 100644 GlobalShared/Events/GridEvents.cs create mode 100644 GlobalShared/GlobalShared.projitems create mode 100644 GlobalShared/GlobalShared.shproj create mode 100644 GlobalShared/Logging/IPluginLogger.cs create mode 100644 GlobalShared/Logging/LogFormatter.cs create mode 100644 GlobalShared/Logging/LoggingLevel.cs create mode 100644 GlobalShared/OcTree/Bag.cs create mode 100644 GlobalShared/OcTree/Data/ControllableEntityData.cs create mode 100644 GlobalShared/OcTree/Data/GridData.cs create mode 100644 GlobalShared/OcTree/Data/IOcTreeData.cs create mode 100644 GlobalShared/OcTree/GenericOcTree.cs create mode 100644 GlobalShared/OcTree/IContainer.cs create mode 100644 GlobalShared/OcTree/OcTree.cs create mode 100644 GlobalShared/OcTree/OcTreeHandler.cs create mode 100644 GlobalShared/OcTree/Pool.cs create mode 100644 GlobalShared/Patches/CubeGridPatch.cs create mode 100644 GlobalShared/Patches/EntityNamePatch.cs create mode 100644 GlobalShared/Patches/MyConveyorLinePatch.cs create mode 100644 GlobalShared/Patches/MyGamePruningStructurePatch.cs create mode 100644 GlobalShared/Patches/MyMechanicalConnectionBlockBasePatch.cs create mode 100644 GlobalShared/Patches/MyTerminalBlockPatch.cs create mode 100644 GlobalShared/Patches/TerminalSystemPatch.cs create mode 100644 GlobalShared/Patching/PatchHelpers.cs create mode 100644 GlobalShared/Plugin/GlobalInstance.cs create mode 100644 GlobalShared/Plugin/GlobalStatic.cs create mode 100644 GlobalShared/Plugin/IGlobalPlugin.cs create mode 100644 GlobalShared/Properties/AssemblyInfo.cs create mode 100644 GlobalShared/Util/KeenExtensions.cs create mode 100644 GlobalShared/Util/NexusUtils.cs create mode 100644 GlobalShared/Util/UintCache.cs create mode 100644 GlobalTorch/API/Discord/DiscordStructs.cs create mode 100644 GlobalTorch/API/Discord/DiscordUtil.cs create mode 100644 GlobalTorch/API/Discord/DiscordWebhook.cs create mode 100644 GlobalTorch/API/EventResult.cs create mode 100644 GlobalTorch/API/GlobalModApi.cs create mode 100644 GlobalTorch/API/GlobalServerModApi.cs create mode 100644 GlobalTorch/API/Libraries/SENetworkAPI/Client.cs create mode 100644 GlobalTorch/API/Libraries/SENetworkAPI/Command.cs create mode 100644 GlobalTorch/API/Libraries/SENetworkAPI/NetSync.cs create mode 100644 GlobalTorch/API/Libraries/SENetworkAPI/Network.cs create mode 100644 GlobalTorch/API/Libraries/SENetworkAPI/Server.cs create mode 100644 GlobalTorch/API/Libraries/SENetworkAPI/SessionTools.cs create mode 100644 GlobalTorch/API/Util/CollectionUtils.cs create mode 100644 GlobalTorch/API/Util/FacUtils.cs create mode 100644 GlobalTorch/API/Util/FileUtils.cs create mode 100644 GlobalTorch/API/Util/GlobalLogManager.cs create mode 100644 GlobalTorch/App.config create mode 100644 GlobalTorch/AudioSendManager.cs create mode 100644 GlobalTorch/Config/WpfConfig.cs create mode 100644 GlobalTorch/DBManager.cs create mode 100644 GlobalTorch/GlobalCommands.cs create mode 100644 GlobalTorch/GlobalPlugin.cs create mode 100644 GlobalTorch/GlobalTorch.csproj create mode 100644 GlobalTorch/GlobalTorch.csproj.user create mode 100644 GlobalTorch/LegacyConfig.cs create mode 100644 GlobalTorch/NexusIntegration.cs create mode 100644 GlobalTorch/OpusWrapper.dll create mode 100644 GlobalTorch/Overwrites/MyEntitiesButBetter.cs create mode 100644 GlobalTorch/Patches/MyScriptManagerPatch.cs create mode 100644 GlobalTorch/Patches/Patcher.cs create mode 100644 GlobalTorch/Program.cs create mode 100644 GlobalTorch/Properties/AssemblyInfo.cs create mode 100644 GlobalTorch/TorchLogger.cs create mode 100644 GlobalTorch/Util/MTObservableCollection.cs create mode 100644 GlobalTorch/Util/SEUtils.cs create mode 100644 GlobalTorch/Util/SendBuffer.cs create mode 100644 GlobalTorch/manifest.xml create mode 100644 GlobalTorch/opus.dll diff --git a/GlobalShared/API/GlobalAPI.cs b/GlobalShared/API/GlobalAPI.cs new file mode 100644 index 0000000..bc7d71b --- /dev/null +++ b/GlobalShared/API/GlobalAPI.cs @@ -0,0 +1,174 @@ +using System.Collections.Generic; +using Global.Shared.OcTree; +using Global.Shared.OcTree.Data; +using Global.Shared.Plugin; +using Sandbox.Game.Entities; +using Sandbox.ModAPI; +using VRageMath; + +namespace Global.Shared.API +{ + public static class GlobalAPI + { + private static OcTreeHandler OcTreeHandler => GlobalStatic.OcTreeHandler; + + public static bool IsAwareOfGrid(long entityId) + { + return OcTreeHandler.GridTree.Contains(entityId); + } + + public static IGridData GetGridById(long entityId) + { + IGridData grid = OcTreeHandler.GridTree.Get(entityId); + if (grid != null) return grid; + var entity = MyAPIGateway.Entities.GetEntityById(entityId); + if (!(entity is MyCubeGrid g)) + { + var text = entity != null ? $"a {entity.GetType().Name}" : "non existent"; + GlobalInstance.Log.Error($"GetGridById: Entity is not a grid, it is a {text}"); + return null; + } + + grid = OcTreeHandler.AddGrid(g); + + return grid; + } + + public static IEnumerable GetAllGrids() + { + return OcTreeHandler.GridTree.GetAll(); + } + + #region Experimental Spatial Data API + + public static IEnumerable LocateGridsExperimental(BoundingBoxD box) + { + return OcTreeHandler.GridTree.GetEnumerable(box); + } + + public static IEnumerable LocateGridsExperimental(BoundingSphereD sphere) + { + return OcTreeHandler.GridTree.GetEnumerable(sphere); + } + + public static IEnumerable LocateGridsExperimental(MyOrientedBoundingBoxD box) + { + return OcTreeHandler.GridTree.GetEnumerable(box); + } + + public static IEnumerable LocateCharactersExperimental(BoundingBoxD box) + { + return OcTreeHandler.CharacterTree.GetEnumerable(box); + } + + public static IEnumerable LocateCharactersExperimental(BoundingSphereD sphere) + { + return OcTreeHandler.CharacterTree.GetEnumerable(sphere); + } + + public static IEnumerable LocateCharactersExperimental(MyOrientedBoundingBoxD box) + { + return OcTreeHandler.CharacterTree.GetEnumerable(box); + } + + #endregion + + #region Spatial Data API + + public static void LocateGrids(List list, BoundingBoxD box, long flags = 0L) + { + OcTreeHandler.GridTree.GetExact(list, box, flags); + } + + public static void LocateGrids(List list, BoundingSphereD sphere, long flags = 0L) + { + OcTreeHandler.GridTree.GetExact(list, sphere, flags); + } + + public static void LocateGrids(List list, MyOrientedBoundingBoxD box, long flags = 0L) + { + OcTreeHandler.GridTree.GetExact(list, box, flags); + } + + public static void LocateCharacters(List list, BoundingBoxD box, long flags = 0L) + { + OcTreeHandler.CharacterTree.GetExact(list, box, flags); + } + + public static void LocateCharacters(List list, BoundingSphereD sphere, + long flags = 0L) + { + OcTreeHandler.CharacterTree.GetExact(list, sphere, flags); + } + + public static void LocateCharacters(List list, MyOrientedBoundingBoxD box, + long flags = 0L) + { + OcTreeHandler.CharacterTree.GetExact(list, box, flags); + } + + #endregion + + #region Extensions + + private static readonly List _internalGridList = new List(); + + private static readonly List + _internalCharacterList = new List(); + + private static void GetExact(this GenericOcTree ocTree, List list, BoundingSphereD sphere, + long flags = 0L) + { + _internalGridList.Clear(); + ocTree.GetExact(_internalGridList, sphere, flags); + list.AddRange(_internalGridList); + } + + private static void GetExact(this GenericOcTree ocTree, List list, BoundingBoxD box, + long flags = 0L) + { + _internalGridList.Clear(); + ocTree.GetExact(_internalGridList, box, flags); + list.AddRange(_internalGridList); + } + + private static void GetExact(this GenericOcTree ocTree, List list, + MyOrientedBoundingBoxD box, + long flags = 0L) + { + _internalGridList.Clear(); + ocTree.GetExact(_internalGridList, box, flags); + list.AddRange(_internalGridList); + } + + private static void GetExact(this GenericOcTree ocTree, + List list, BoundingSphereD sphere, + long flags = 0L) + { + _internalCharacterList.Clear(); + ocTree.GetExact(_internalCharacterList, sphere, flags); + list.AddRange(_internalCharacterList); + } + + private static void GetExact(this GenericOcTree ocTree, + List list, BoundingBoxD box, + long flags = 0L) + { + _internalCharacterList.Clear(); + ocTree.GetExact(_internalCharacterList, box, flags); + list.AddRange(_internalCharacterList); + } + + private static void GetExact(this GenericOcTree ocTree, + List list, + MyOrientedBoundingBoxD box, + long flags = 0L) + { + _internalCharacterList.Clear(); + ocTree.GetExact(_internalCharacterList, box, flags); + list.AddRange(_internalCharacterList); + } + + #endregion + } +} \ No newline at end of file diff --git a/GlobalShared/API/IBooleanFunction.cs b/GlobalShared/API/IBooleanFunction.cs new file mode 100644 index 0000000..300c9b0 --- /dev/null +++ b/GlobalShared/API/IBooleanFunction.cs @@ -0,0 +1,17 @@ +namespace Global.Shared.API +{ + public static class BooleanFunctions + { + public delegate bool BooleanFunction(bool a, bool b); + + public static BooleanFunction True = (a, b) => true; + public static BooleanFunction False = (a, b) => false; + public static BooleanFunction And = (a, b) => a && b; + public static BooleanFunction Or = (a, b) => a || b; + public static BooleanFunction Xor = (a, b) => a ^ b; + public static BooleanFunction NotA = (a, b) => b && !a; + public static BooleanFunction NotB = (a, b) => a && !b; + public static BooleanFunction NotAAndB = (a, b) => !(a && b); + public static BooleanFunction NotAOrB = (a, b) => !(a || b); + } +} \ No newline at end of file diff --git a/GlobalShared/API/IControllableEntityData.cs b/GlobalShared/API/IControllableEntityData.cs new file mode 100644 index 0000000..cc74b5f --- /dev/null +++ b/GlobalShared/API/IControllableEntityData.cs @@ -0,0 +1,9 @@ +using Sandbox.Game.Entities; + +namespace Global.Shared.API +{ + public interface IControllableEntityData + { + IMyControllableEntity Entity { get; } + } +} \ No newline at end of file diff --git a/GlobalShared/API/ICustomData.cs b/GlobalShared/API/ICustomData.cs new file mode 100644 index 0000000..b00960a --- /dev/null +++ b/GlobalShared/API/ICustomData.cs @@ -0,0 +1,9 @@ +using System; + +namespace Global.Shared.API +{ + public interface ICustomData : IDisposable + { + IGridData GridData { get; } + } +} \ No newline at end of file diff --git a/GlobalShared/API/ICustomManager.cs b/GlobalShared/API/ICustomManager.cs new file mode 100644 index 0000000..39f6341 --- /dev/null +++ b/GlobalShared/API/ICustomManager.cs @@ -0,0 +1,7 @@ +namespace Global.Shared.API +{ + public interface ICustomManager where T : ICustomData + { + T CreateData(IGridData data); + } +} \ No newline at end of file diff --git a/GlobalShared/API/IGridData.cs b/GlobalShared/API/IGridData.cs new file mode 100644 index 0000000..e2b5703 --- /dev/null +++ b/GlobalShared/API/IGridData.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Global.Shared.OcTree.Data; +using Sandbox.Game.Entities; +using Sandbox.Game.Entities.Cube; +using VRage.ObjectBuilders; +using VRageMath; + +namespace Global.Shared.API +{ + public interface IGridData : IOcTreeData + { + long Id { get; } + BoundingBoxD BoundingBox { get; } + MyCubeGrid CubeGrid { get; } + + bool IsInVoxel { get; } + + int BlockCount { get; } + + IEnumerable GetBlocks(MyObjectBuilderType type); + + IEnumerable GetAllFunctionalBlocks(); + + bool Contains(MyObjectBuilderType type); + } +} \ No newline at end of file diff --git a/GlobalShared/API/OcTreeVariables.cs b/GlobalShared/API/OcTreeVariables.cs new file mode 100644 index 0000000..b5820af --- /dev/null +++ b/GlobalShared/API/OcTreeVariables.cs @@ -0,0 +1,34 @@ +using System; + +namespace Global.Shared.API +{ + public static class GridFlag + { + public const long None = 0; + private static int _taken; + public static long StaticGrid = CreateFlag(); + public static long DynamicGrid = CreateFlag(); + public static long SmallGrid = CreateFlag(); + public static long LargeGrid = CreateFlag(); + + public static long CreateFlag() + { + if (_taken == 31) throw new Exception("Too many grid flags created. Max is 30."); + return 1 << _taken++; + } + } + + public static class PlayerFlag + { + public const long None = 0; + private static int _taken; + public static long Character = CreateFlag(); + public static long Controlled = CreateFlag(); + + public static long CreateFlag() + { + if (_taken == 31) throw new Exception("Too many player flags created. Max is 30."); + return 1 << _taken++; + } + } +} \ No newline at end of file diff --git a/GlobalShared/Config/IPluginConfig.cs b/GlobalShared/Config/IPluginConfig.cs new file mode 100644 index 0000000..c8a705b --- /dev/null +++ b/GlobalShared/Config/IPluginConfig.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; +using VRageMath; + +namespace Global.Shared.Config +{ + public interface IPluginConfig : INotifyPropertyChanged + { + bool IsEnabled { get; set; } + int Size { get; set; } + Vector3I CenterPosition { get; set; } + int Capacity { get; set; } + int MaxDepth { get; set; } + } +} \ No newline at end of file diff --git a/GlobalShared/Events/BlockEvents.cs b/GlobalShared/Events/BlockEvents.cs new file mode 100644 index 0000000..9fde969 --- /dev/null +++ b/GlobalShared/Events/BlockEvents.cs @@ -0,0 +1,27 @@ +using System; +using Sandbox.Definitions; +using Sandbox.Game.Entities; +using VRageMath; + +namespace Global.Shared.Events +{ + public class BlockEvents + { + public delegate void CanPlaceBlockDelegate(MyCubeGrid grid, MyBlockOrientation? orientation, + MyCubeBlockDefinition blockDefinition, ulong placingPlayer, ref bool canPlace); + + public static Action OnPlayerControlAcquired; + public static Action OnPlayerControlReleased; + + public static Action OnBlockAdded; + public static Action OnBlockRemoved; + + public static event CanPlaceBlockDelegate CanPlaceBlockEvent; + + internal static void OnCanPlaceBlockEvent(MyCubeGrid grid, MyBlockOrientation? orientation, + MyCubeBlockDefinition def, ulong placingPlayer, ref bool canPlace) + { + CanPlaceBlockEvent?.Invoke(grid, orientation, def, placingPlayer, ref canPlace); + } + } +} \ No newline at end of file diff --git a/GlobalShared/Events/GridEvents.cs b/GlobalShared/Events/GridEvents.cs new file mode 100644 index 0000000..cc09cab --- /dev/null +++ b/GlobalShared/Events/GridEvents.cs @@ -0,0 +1,13 @@ +using System; +using Sandbox.Game.Entities; + +namespace Global.Shared.Events +{ + public class GridEvents + { + public static Action GridCreated = l => { }; + public static Action GridRemoved = l => { }; + public static Action SubGridAdded = (parent, child) => { }; + public static Action SubGridRemoved = (parent, child) => { }; + } +} \ No newline at end of file diff --git a/GlobalShared/GlobalShared.projitems b/GlobalShared/GlobalShared.projitems new file mode 100644 index 0000000..653fbcf --- /dev/null +++ b/GlobalShared/GlobalShared.projitems @@ -0,0 +1,49 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + B127E405-3C0F-48C3-9F8E-7437E8F4E106 + + + Global.Shared + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GlobalShared/GlobalShared.shproj b/GlobalShared/GlobalShared.shproj new file mode 100644 index 0000000..8df249f --- /dev/null +++ b/GlobalShared/GlobalShared.shproj @@ -0,0 +1,13 @@ + + + + {C5784FE0-CF0A-4870-9DEF-7BEA8B64C01A} + v4.8 + Shared + + + + + + + \ No newline at end of file diff --git a/GlobalShared/Logging/IPluginLogger.cs b/GlobalShared/Logging/IPluginLogger.cs new file mode 100644 index 0000000..22cff63 --- /dev/null +++ b/GlobalShared/Logging/IPluginLogger.cs @@ -0,0 +1,28 @@ +using System; + +namespace Global.Shared.Logging +{ + public interface IPluginLogger + { + bool IsTraceEnabled { get; } + bool IsDebugEnabled { get; } + bool IsInfoEnabled { get; } + bool IsWarningEnabled { get; } + bool IsErrorEnabled { get; } + bool IsCriticalEnabled { get; } + + void Trace(Exception ex, string message, params object[] data); + void Debug(Exception ex, string message, params object[] data); + void Info(Exception ex, string message, params object[] data); + void Warning(Exception ex, string message, params object[] data); + void Error(Exception ex, string message, params object[] data); + void Critical(Exception ex, string message, params object[] data); + + void Trace(string message, params object[] data); + void Debug(string message, params object[] data); + void Info(string message, params object[] data); + void Warning(string message, params object[] data); + void Error(string message, params object[] data); + void Critical(string message, params object[] data); + } +} \ No newline at end of file diff --git a/GlobalShared/Logging/LogFormatter.cs b/GlobalShared/Logging/LogFormatter.cs new file mode 100644 index 0000000..6859c1b --- /dev/null +++ b/GlobalShared/Logging/LogFormatter.cs @@ -0,0 +1,90 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; + +namespace Global.Shared.Logging +{ + public class LogFormatter + { + private const int MaxExceptionDepth = 100; + private readonly string _prefix; + private readonly ThreadLocal _stringBuilder = new ThreadLocal(); + + public LogFormatter(string prefix) + { + _prefix = prefix; + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected string Format(Exception ex, string message, object[] data) + { + // Allocate a single StringBuilder object per thread + var sb = _stringBuilder.Value; + if (sb == null) + { + sb = new StringBuilder(); + _stringBuilder.Value = sb; + } + + if (message == null) + message = ""; + + sb.Append(_prefix); + + sb.Append(data == null || data.Length == 0 ? message : string.Format(message, data)); + + FormatException(sb, ex); + + var text = sb.ToString(); + sb.Clear(); + + return text; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void FormatException(StringBuilder sb, Exception ex) + { + if (ex == null) + return; + + for (var i = 0; i < MaxExceptionDepth; i++) + { + sb.Append("\r\n["); + sb.Append(ex.GetType().Name); + sb.Append("] "); + sb.Append(ex.Message); + + if (ex.TargetSite != null) + { + sb.Append("\r\nMethod: "); + sb.Append(ex.TargetSite); + } + + if (ex.Data.Count > 0) + { + sb.Append("\r\nData:"); + foreach (var key in ex.Data.Keys) + { + sb.Append("\r\n"); + sb.Append(key); + sb.Append(" = "); + sb.Append(ex.Data[key]); + } + } + + sb.Append("\r\nTraceback:\r\n"); + sb.Append(ex.StackTrace); + + ex = ex.InnerException; + if (ex == null) + return; + + sb.Append("\r\nInner exception:\r\n"); + } + + sb.Append($"WARNING: Not logging more than {MaxExceptionDepth} inner exceptions"); + } + } +} \ No newline at end of file diff --git a/GlobalShared/Logging/LoggingLevel.cs b/GlobalShared/Logging/LoggingLevel.cs new file mode 100644 index 0000000..f42517f --- /dev/null +++ b/GlobalShared/Logging/LoggingLevel.cs @@ -0,0 +1,20 @@ +namespace Global.Shared.Logging +{ + public enum LoggingLevel + { + Trace, + Debug, + Info, + Warning, + Error, + Fatal + } + + public static class LevelExtensions + { + public static bool IsOfLevel(this LoggingLevel level, LoggingLevel levelToCheck) + { + return (int)level <= (int)levelToCheck; + } + } +} \ No newline at end of file diff --git a/GlobalShared/OcTree/Bag.cs b/GlobalShared/OcTree/Bag.cs new file mode 100644 index 0000000..2e32820 --- /dev/null +++ b/GlobalShared/OcTree/Bag.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Global.Shared.OcTree +{ + public interface IImmutableBag : IEnumerable + { + /// Returns the element at the specified position in Bag + /// the index of the element to return + /// the element at the specified position in bag + E Get(int index); + + /// Returns the number of elements in this Bag + /// the number of elements in this bag + int Size(); + + bool IsEmpty(); + + bool Contains(E element); + } + + public class Bag : IImmutableBag + { + public E[] data; + private BagEnumerator it; + protected int size; + + public Bag() : this(64) + { + } + + public Bag(int capacity) + { + data = new E[capacity]; + } + + public E this[int i] + { + get => Get(i); + set => data[i] = value; + } + + public E Get(int index) + { + return data[index]; + } + + /// Returns the number of elements in this Bag + /// the number of elements in this bag + public int Size() + { + return size; + } + + public bool IsEmpty() + { + return size == 0; + } + + public bool Contains(E e) + { + for (var i = 0; i < size; i++) + { + var e2 = data[i]; + + if (!e.Equals(e2)) return true; + } + + return false; + } + + public E Remove(int index) + { + var e = data[index]; + data[index] = data[--size]; + data[size] = default; + return e; + } + + public E RemoveLast() + { + if (IsEmpty()) return default; + + var e = data[--size]; + data[size] = default; + return e; + } + + public bool Remove(E e) + { + for (var i = 0; i < size; i++) + { + var e2 = data[i]; + + if (!e.Equals(e2)) continue; + + data[i] = data[--size]; + data[size] = default; + return true; + } + + return false; + } + + /// Returns the number of elements this bag can hold without growing. + /// the number of elements this bag can hold without growing + public int Capacity() + { + return data.Length; + } + + public void Add(E e) + { + if (size == data.Length) Grow(data.Length + 1); + + data[size++] = e; + } + + public void Grow(int newCapacity) + { + Array.Resize(ref data, newCapacity); + } + + #region Enumerator + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + if (it == null) it = new BagEnumerator(this); + + it.Cursor = -1; + + return it; + } + + public class BagEnumerator : IEnumerator + { + private readonly Bag bag; + internal int Cursor; + + public BagEnumerator(Bag bag) + { + this.bag = bag; + } + + public void Dispose() + { + } + + public bool MoveNext() + { + Cursor++; + return Cursor < bag.size; + } + + public void Reset() + { + Cursor = -1; + } + + object IEnumerator.Current => Current; + + public E Current => bag.data[Cursor]; + } + + #endregion + } +} \ No newline at end of file diff --git a/GlobalShared/OcTree/Data/ControllableEntityData.cs b/GlobalShared/OcTree/Data/ControllableEntityData.cs new file mode 100644 index 0000000..1e6543c --- /dev/null +++ b/GlobalShared/OcTree/Data/ControllableEntityData.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Global.Shared.API; +using Global.Shared.Plugin; +using Sandbox.Game.Entities; +using Sandbox.Game.Entities.Character; +using VRage.Game.Components; +using VRageMath; + +namespace Global.Shared.OcTree.Data +{ + public class ControllableEntityData : IControllableEntityData, IOcTreeData + { + public ControllableEntityData(IMyControllableEntity entity) + { + Entity = entity; + Id = entity.Entity.EntityId; + Recompute(); + entity.Entity.PositionComp.OnPositionChanged += PositionChanged; + } + + public IMyControllableEntity Entity { get; set; } + public long Id { get; } + public List ChildIds { get; } = new List(); + public long Flags { get; private set; } + public BoundingBoxD BoundingBox { get; private set; } + + public void Recompute() + { + Flags = PlayerFlag.None; + Flags |= Entity is MyCharacter ? PlayerFlag.Character : PlayerFlag.Controlled; + BoundingBox = Entity.Entity.PositionComp.WorldAABB; + } + + public void Update() + { + Recompute(); + } + + public void Dispose() + { + Entity.Entity.PositionComp.OnPositionChanged -= PositionChanged; + Entity = null; + BoundingBox = default; + } + + private void PositionChanged(MyPositionComponentBase obj) + { + GlobalStatic.OcTreeHandler.CharacterTree.Update(Id); + } + } +} \ No newline at end of file diff --git a/GlobalShared/OcTree/Data/GridData.cs b/GlobalShared/OcTree/Data/GridData.cs new file mode 100644 index 0000000..b3f312a --- /dev/null +++ b/GlobalShared/OcTree/Data/GridData.cs @@ -0,0 +1,334 @@ +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Global.Shared.API; +using Global.Shared.Events; +using Global.Shared.Plugin; +using Sandbox.Game.Entities; +using Sandbox.Game.Entities.Cube; +using Sandbox.Game.World; +using VRage.Collections; +using VRage.Game; +using VRage.Game.ModAPI; +using VRage.Game.ObjectBuilders.Definitions.SessionComponents; +using VRage.ObjectBuilders; +using VRageMath; + +namespace Global.Shared.OcTree.Data +{ + public class GridData : IOcTreeData, IGridData + { + private static readonly Stopwatch _stopwatch = new Stopwatch(); + public ConcurrentDictionary Blocks; + public MyConcurrentList FunctionalBlocks; + public List SubGrids; + + public GridData(MyCubeGrid grid) + { + CubeGrid = grid; + Id = grid.EntityId; + Init(); + } + + public bool IsInVoxel { get; set; } + public int BlockCount { get; set; } + + public MyCubeGrid CubeGrid { get; } + + public IEnumerable GetBlocks(MyObjectBuilderType type) + { + // TODO: Improve memory allocation of this. + return Blocks.TryGetValue(type, out var list) + ? list.Cast().ToArray() + : Enumerable.Empty(); + } + + + public IEnumerable GetAllFunctionalBlocks() + { + return FunctionalBlocks ?? Enumerable.Empty(); + } + + public bool Contains(MyObjectBuilderType type) + { + return Blocks.ContainsKey(type) && Blocks[type].Count > 0; + } + + public void Update() + { + Recompute(); + } + + public long Id { get; set; } + public List ChildIds => SubGrids?.Select(x => x.EntityId).ToList(); + public long Flags { get; private set; } + public BoundingBoxD BoundingBox { get; private set; } + + public void Recompute() + { + Flags = GridFlag.None; + BoundingBox = CubeGrid.GetPhysicalGroupAABB(); + Flags |= CubeGrid.IsStatic ? GridFlag.StaticGrid : GridFlag.DynamicGrid; + Flags |= CubeGrid.GridSizeEnum == MyCubeSize.Large ? GridFlag.LargeGrid : GridFlag.SmallGrid; + BlockCount = CubeGrid.BlocksCount + (SubGrids?.Sum(x => x.BlocksCount) ?? 0); + } + + private bool Closing; + + public void Dispose() + { + GlobalInstance.Log.Debug($"Disposing of grid data for {Id} {CubeGrid.DisplayName}"); + if (GlobalInstance.Log.IsTraceEnabled) GlobalInstance.Log.Trace(new StackTrace(true).ToString()); + CubeGrid.OnStaticChanged -= OnStaticChanged; + CubeGrid.OnConnectionChanged -= OnConnectionChanged; + CubeGrid.OnGridMerge -= OnGridMerged; + CubeGrid.OnGridSplit -= OnGridSplit; + CubeGrid.OnFatBlockAdded -= AddBlock; + CubeGrid.OnFatBlockRemoved -= OnFatBlockRemoved; + SubGrids?.ForEach(x => + { + x.OnStaticChanged -= OnStaticChanged; + x.OnConnectionChanged -= OnConnectionChanged; + x.OnGridMerge -= OnGridMerged; + x.OnGridSplit -= OnGridSplit; + x.OnFatBlockAdded -= AddBlock; + x.OnFatBlockRemoved -= OnFatBlockRemoved; + }); + Closing = true; + GlobalInstance.Run(() => + { + Blocks.Clear(); + + FunctionalBlocks.Clear(); + SubGrids.Clear(); + }); + } + + private void OnStaticChanged(MyCubeGrid arg1, bool arg2) + { + RecheckVoxelStatus(); + } + + public void Init() + { + Blocks = new ConcurrentDictionary(); + FunctionalBlocks = new MyConcurrentList(); + RecalculateSubgrids(); + Recompute(); + CubeGrid.OnStaticChanged += OnStaticChanged; + CubeGrid.OnConnectionChanged += OnConnectionChanged; + CubeGrid.OnGridMerge += OnGridMerged; + CubeGrid.OnGridSplit += OnGridSplit; + CubeGrid.OnFatBlockAdded += AddBlock; + CubeGrid.OnFatBlockRemoved += OnFatBlockRemoved; + + if (GlobalInstance.Log.IsDebugEnabled) + { + _stopwatch.Reset(); + _stopwatch.Start(); + } + + ScanGridBlocks(CubeGrid); + if (SubGrids.Count > 0) + SubGrids.ForEach(ScanGridBlocks); + RecheckVoxelStatus(); + + if (GlobalInstance.Log.IsDebugEnabled) + GlobalInstance.Log.Debug( + $"Data Init took {_stopwatch.ElapsedMilliseconds}ms for {CubeGrid.DisplayName} with {CubeGrid.BlocksCount + SubGrids.Sum(e => e.BlocksCount)} blocks and {SubGrids.Count} subgrids"); + } + + private void OnFatBlockRemoved(MyCubeBlock block) + { + if (Closing) return; + if (Blocks.TryGetValue(block.BlockDefinition.Id.TypeId, out var bag)) bag.Remove(block); + + GlobalInstance.Run(() => + { + if (block is IMyControllableEntity entity) + { + entity.ControllerInfo.ControlAcquired -= OnControlAcquired; + entity.ControllerInfo.ControlReleased -= OnControlReleased; + OnControlReleased(entity.ControllerInfo.Controller); + } + + BlockEvents.OnBlockRemoved?.Invoke(block); + }); + } + + public void RecheckVoxelStatus() + { + if (Closing) return; + var settings = new MyGridPlacementSettings + { + VoxelPlacement = new VoxelPlacementSettings + { + PlacementMode = VoxelPlacementMode.Volumetric + } + }; + IsInVoxel = false; + if (!CheckGridVoxelStatic(CubeGrid, settings)) + if (SubGrids.Count > 0 && SubGrids.All(e => !CheckGridVoxelStatic(e, settings))) + return; + IsInVoxel = true; + } + + public bool CheckGridVoxelStatic(MyCubeGrid grid, MyGridPlacementSettings settings) + { + if (!MyCubeGrid.IsAabbInsideVoxel(grid.WorldMatrix, grid.PositionComp.LocalAABB, settings)) return false; + var worldAabb = grid.PositionComp.WorldAABB; + return MyGamePruningStructure.AnyVoxelMapInBox(ref worldAabb) && + grid.GetBlocks().Any(block => MyCubeGrid.IsInVoxels(block, isVolumetric: false)); + } + + private bool RecalculateSubgrids() + { + var previous = SubGrids; + SubGrids = CubeGrid.GetConnectedGrids(GridLinkTypeEnum.Mechanical); + SubGrids.Remove(CubeGrid); + if (previous == null) + { + SubGrids.ForEach(OnSubGridAdded); + return SubGrids.Any(); + } + + var changed = false; + var newSubGrids = SubGrids.Except(previous).ToList(); + if (newSubGrids.Count > 0) + { + changed = true; + newSubGrids.ForEach(OnSubGridAdded); + } + + var removedSubGrids = previous.Except(SubGrids).ToList(); + if (removedSubGrids.Count > 0) + { + changed = true; + removedSubGrids.ForEach(OnSubGridRemoved); + } + + if (changed) GlobalStatic.OcTreeHandler.GridTree.Update(Id); + + return changed; + } + + private void OnGridSplit(MyCubeGrid arg1, MyCubeGrid arg2) + { + GlobalInstance.Log.Debug( + $"Grid split Invoke: {arg1.DisplayName} and {arg2.DisplayName} {Thread.CurrentThread.Name}"); + GlobalInstance.Run(() => + { + GlobalInstance.Log.Debug( + $"Grid split Run: {arg1.DisplayName} and {arg2.DisplayName} {Thread.CurrentThread.Name}"); + GlobalStatic.OcTreeHandler.GridTree.Remove(arg1.EntityId); + GlobalStatic.OcTreeHandler.GridTree.Remove(arg2.EntityId); + GlobalStatic.OcTreeHandler.AddGrid(arg1); + GlobalStatic.OcTreeHandler.AddGrid(arg2); + }); + } + + private void OnGridMerged(MyCubeGrid arg1, MyCubeGrid arg2) + { + //GlobalInstance.Log.Info($"Merging {arg1.DisplayName} and {arg2.DisplayName}"); + RecalculateSubgrids(); + Recompute(); + Blocks.Clear(); + + FunctionalBlocks.Clear(); + ScanGridBlocks(CubeGrid); + if (SubGrids.Count > 0) + SubGrids.ForEach(ScanGridBlocks); + } + + private void OnConnectionChanged(MyCubeGrid arg1, GridLinkTypeEnum arg2) + { + if (Closing) return; + if (arg2 != GridLinkTypeEnum.Mechanical) return; + + RecalculateSubgrids(); + Recompute(); + Blocks.Clear(); + FunctionalBlocks.Clear(); + ScanGridBlocks(CubeGrid); + if (SubGrids.Count > 0) + SubGrids.ForEach(ScanGridBlocks); + } + + private void ScanGridBlocks(MyCubeGrid obj) + { + Parallel.ForEach(obj.GetFatBlocks(), AddBlock); + } + + private void OnSubGridAdded(MyCubeGrid grid) + { + GlobalStatic.OcTreeHandler.RemoveGrid(grid); + grid.OnFatBlockAdded += AddBlock; + grid.OnFatBlockRemoved += OnFatBlockRemoved; + grid.OnStaticChanged += OnStaticChanged; + grid.OnConnectionChanged += OnConnectionChanged; + grid.OnGridMerge += OnGridMerged; + grid.OnGridSplit += OnGridSplit; + GridEvents.SubGridAdded?.Invoke(CubeGrid, grid); + RecheckVoxelStatus(); + } + + private void OnSubGridRemoved(MyCubeGrid grid) + { + grid.OnFatBlockAdded -= AddBlock; + grid.OnFatBlockRemoved -= OnFatBlockRemoved; + grid.OnStaticChanged -= OnStaticChanged; + grid.OnConnectionChanged -= OnConnectionChanged; + grid.OnGridMerge -= OnGridMerged; + grid.OnGridSplit -= OnGridSplit; + GridEvents.SubGridRemoved?.Invoke(CubeGrid, grid); + RecheckVoxelStatus(); + } + + private void AddBlock(MyCubeBlock block) + { + if (Closing) return; + Blocks.AddOrUpdate(block.BlockDefinition.Id.TypeId, + _ => ArrayList.Synchronized(new ArrayList { block }), + (_, list) => + { + list.Add(block); + + return list; + }); + + GlobalInstance.Run(() => + { + if (block is IMyControllableEntity entity) + { + entity.ControllerInfo.ControlAcquired += OnControlAcquired; + entity.ControllerInfo.ControlReleased += OnControlReleased; + if (entity.ControllerInfo.IsLocallyControlled() || entity.ControllerInfo.IsRemotelyControlled()) + OnControlAcquired(entity.ControllerInfo.Controller); + } + + if (block is MyFunctionalBlock functionalBlock) FunctionalBlocks.Add(functionalBlock); + + BlockEvents.OnBlockAdded?.Invoke(block); + }); + } + + private void OnControlAcquired(MyEntityController obj) + { + BlockEvents.OnPlayerControlAcquired?.Invoke(obj.ControlledEntity); + } + + private void OnControlReleased(MyEntityController obj) + { + if (obj != null) BlockEvents.OnPlayerControlReleased?.Invoke(obj.ControlledEntity); + } + + public void RaiseAttachedEntityChanged() + { + OnConnectionChanged(CubeGrid, GridLinkTypeEnum.Mechanical); + } + } +} \ No newline at end of file diff --git a/GlobalShared/OcTree/Data/IOcTreeData.cs b/GlobalShared/OcTree/Data/IOcTreeData.cs new file mode 100644 index 0000000..19ffc74 --- /dev/null +++ b/GlobalShared/OcTree/Data/IOcTreeData.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using VRageMath; + +namespace Global.Shared.OcTree.Data +{ + public interface IOcTreeData : IDisposable + { + long Id { get; } + List ChildIds { get; } + long Flags { get; } + BoundingBoxD BoundingBox { get; } + + void Update(); + void Recompute(); + } +} \ No newline at end of file diff --git a/GlobalShared/OcTree/GenericOcTree.cs b/GlobalShared/OcTree/GenericOcTree.cs new file mode 100644 index 0000000..a27d7ed --- /dev/null +++ b/GlobalShared/OcTree/GenericOcTree.cs @@ -0,0 +1,986 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Global.Shared.OcTree.Data; +using Global.Shared.Plugin; +using Global.Shared.Util; +using VRageMath; + +namespace Global.Shared.OcTree +{ + public class GenericOcTree : IPoolable where T : IOcTreeData + { + private readonly GenericContainer _bounds; + + private readonly List> _containersToAdd = new List>(); + + private readonly Pool> _cPool = + new Pool>(() => new GenericContainer()); + + private readonly GenericOcTree[] _nodes = new GenericOcTree[8]; + + private readonly Pool> _otPool = new Pool>(() => new GenericOcTree()); + + private int _capacity; + private Bag> _containers; + + private Dictionary> _idToContainer; + private int _maxDepth; + + private GenericOcTree _parent; + + private int treeDepth; + + private GenericOcTree() : this(0, 0, 0, 0, 0, 0, 0, 0) + { + } + + public GenericOcTree(float x, float y, float z, float width, float height, float depth, int capacity, + int maxDepth) + { + _bounds = new GenericContainer().Set(x, y, z, width, height, depth); + _capacity = capacity; + _maxDepth = maxDepth; + _containers = new Bag>(capacity); + _idToContainer = new Dictionary>(); + } + + public T this[long entityId] => Get(entityId); + + public void Reset() + { + for (var i = _containers.Size() - 1; i >= 0; i--) _cPool.Release(_containers.Remove(i)); + for (var i = 0; i < _nodes.Length; i++) + if (_nodes[i] != null) + { + _otPool.Release(_nodes[i]); + _nodes[i] = null; + } + } + + private GenericOcTree Init(int currentTreeDepth, float x, float y, float z, float width, float height, + float depth, + GenericOcTree parent) + { + treeDepth = currentTreeDepth; + _parent = parent; + _capacity = parent?._capacity ?? 1; + _maxDepth = parent?._maxDepth ?? 6; + _bounds.Set(x, y, z, width, height, depth); + _idToContainer = parent?._idToContainer ?? new Dictionary>(); + _containers = new Bag>(_capacity); + + return this; + } + + private int IndexOf(float x, float y, float z, float width, float height, float depth) + { + var midX = _bounds.X + _bounds.Width / 2; + var midY = _bounds.Y + _bounds.Height / 2; + var midZ = _bounds.Z + _bounds.Depth / 2; + + var res = 0; + if (x > midX) res |= E; + else if (x < midX && x + width < midX) res |= W; + else return OUTSIDE; + + if (y > midY) res |= U; + else if (y < midY && y + height < midY) res |= D; + else return OUTSIDE; + + if (z > midZ) res |= S; + else if (z < midZ && z + depth < midZ) res |= N; + else return OUTSIDE; + + return res; + } + + private bool ShouldMerge() + { + // If has no children, has nothing to merge + if (_nodes[0] == null) return false; + // If has any children with children, cannot merge + if (_nodes.Any(n => n._nodes[0] != null)) return false; + // If children combined contain less than capacity, can merge + return _containers.Size() + _nodes.Sum(n => n._containers.Size()) <= _capacity; + } + + private bool ShouldSplit() + { + var halfWidth = _bounds.Width / 2; + var halfHeight = _bounds.Height / 2; + var halfDepth = _bounds.Depth / 2; + var temporaryBounds = new GenericContainer(); + temporaryBounds.Set(_bounds.X, _bounds.Y, _bounds.Z, halfWidth, halfHeight, halfDepth); + if (_containers.All(e => temporaryBounds.Contains(e))) return false; + temporaryBounds.Set(_bounds.X + halfWidth, _bounds.Y, _bounds.Z, halfWidth, halfHeight, halfDepth); + if (_containers.All(e => temporaryBounds.Contains(e))) return false; + temporaryBounds.Set(_bounds.X, _bounds.Y + halfHeight, _bounds.Z, halfWidth, halfHeight, halfDepth); + if (_containers.All(e => temporaryBounds.Contains(e))) return false; + temporaryBounds.Set(_bounds.X + halfWidth, _bounds.Y + halfHeight, _bounds.Z, halfWidth, halfHeight, + halfDepth); + if (_containers.All(e => temporaryBounds.Contains(e))) return false; + temporaryBounds.Set(_bounds.X, _bounds.Y, _bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth); + if (_containers.All(e => temporaryBounds.Contains(e))) return false; + temporaryBounds.Set(_bounds.X + halfWidth, _bounds.Y, _bounds.Z + halfDepth, halfWidth, halfHeight, + halfDepth); + if (_containers.All(e => temporaryBounds.Contains(e))) return false; + temporaryBounds.Set(_bounds.X, _bounds.Y + halfHeight, _bounds.Z + halfDepth, halfWidth, halfHeight, + halfDepth); + if (_containers.All(e => temporaryBounds.Contains(e))) return false; + temporaryBounds.Set(_bounds.X + halfWidth, _bounds.Y + halfHeight, _bounds.Z + halfDepth, halfWidth, + halfHeight, halfDepth); + if (_containers.All(e => temporaryBounds.Contains(e))) return false; + return true; + } + + private void Split() + { + // if (!ShouldSplit()) return; TODO: See if necessary + + var halfWidth = _bounds.Width / 2; + var halfHeight = _bounds.Height / 2; + var halfDepth = _bounds.Depth / 2; + + _nodes[DNW] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y, _bounds.Z, halfWidth, halfHeight, + halfDepth, this); + _nodes[DNE] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y, _bounds.Z, halfWidth, + halfHeight, halfDepth, this); + _nodes[DSW] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y, _bounds.Z + halfDepth, halfWidth, + halfHeight, halfDepth, this); + _nodes[DSE] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y, + _bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this); + + _nodes[UNW] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y + halfHeight, _bounds.Z, + halfWidth, halfHeight, halfDepth, this); + _nodes[UNE] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y + halfHeight, + _bounds.Z, halfWidth, halfHeight, halfDepth, this); + _nodes[USW] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y + halfHeight, + _bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this); + _nodes[USE] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y + halfHeight, + _bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this); + } + + private void HandleMerge() + { + if (!ShouldMerge()) return; + + for (var index = _nodes.Length - 1; index >= 0; index--) + { + var ocTree = _nodes[index]; + for (var i = ocTree._containers.Size() - 1; i >= 0; i--) + { + var ocTreeContainer = ocTree._containers[i]; + _containersToAdd.Add(ocTreeContainer); + ocTree._containers.Remove(i); + } + } + + for (var index = _nodes.Length - 1; index >= 0; index--) + { + var ocTree = _nodes[index]; + _nodes[index] = null; + _otPool.Release(ocTree); + ocTree.Reset(); + } + + foreach (var container in _containersToAdd) Insert(container); + + _containersToAdd.Clear(); + } + + public IEnumerable GetAll() + { + if (_nodes[0] != null) + foreach (var data in _nodes.SelectMany(e => e.GetAll())) + yield return data; + foreach (var genericContainer in _containers) + if (genericContainer.Value != null) + yield return genericContainer.Value; + } + + #region Node Bitwise + + private const int OUTSIDE = -1; + private const int D = 0b000; + private const int U = 0b100; + private const int S = 0b000; + private const int N = 0b010; + private const int W = 0b000; + private const int E = 0b001; + private const int DSW = D | S | W; // 0 + private const int DSE = D | S | E; // 1 + private const int DNW = D | N | W; // 2 + private const int DNE = D | N | E; // 3 + private const int USW = U | S | W; // 4 + private const int USE = U | S | E; // 5 + private const int UNW = U | N | W; // 6 + private const int UNE = U | N | E; // 7 + + #endregion + + #region Modifiers + + public void Update(long entityId) + { + if (!Contains(entityId)) return; + var c = _idToContainer[entityId]; + try + { + var (removedChildren, addedChildren) = c.Update(); + if (c.EntityId != entityId) + { + var old = entityId; + entityId = c.EntityId; + // already contains new id.. just give up + if (Contains(entityId)) return; + // Entity id changed, update the octree + _idToContainer.Remove(old); + _idToContainer.Add(entityId, c); + } + + // Attempting to update subgrid + if (removedChildren != null) + foreach (var removedChild in removedChildren) + _idToContainer.Remove(removedChild); + if (addedChildren != null) + foreach (var addedChild in addedChildren) + _idToContainer.Add(addedChild, c); + + var parentTree = c.Parent; + if (parentTree._bounds.Contains(c)) return; + + parentTree._containers.Remove(c); + while (parentTree._parent != null && !parentTree._bounds.Contains(c)) parentTree = parentTree._parent; + + parentTree.Insert(c); + } + catch (Exception e) + { + GlobalInstance.Log.Critical(e, "Failed to update octree"); + } + } + + public void Insert(T value) + { + Insert(_cPool.Obtain().Set(value)); + } + + private void Insert(GenericContainer c) + { + if (_nodes[0] != null) + { + var index = IndexOf(c.X, c.Y, c.Z, c.Width, c.Height, c.Depth); + if (index != OUTSIDE) + { + _nodes[index].Insert(c); + return; + } + } + + c.Parent = this; + _idToContainer[c.EntityId] = c; + if (c.ChildIds != null) + foreach (var childId in c.ChildIds) + _idToContainer[childId] = c; + _containers.Add(c); + + // If under capacity or at/over max depth, we're done. + if (_containers.Size() <= _capacity || treeDepth >= _maxDepth) return; + + if (_nodes[0] == null) Split(); + if (_nodes[0] == null) return; + + var items = _containers.data; + for (var i = _containers.Size() - 1; i >= 0; i--) + { + var next = items[i]; + if (next == null) continue; + var index = IndexOf(next.X, next.Y, next.Z, next.Width, next.Height, next.Depth); + if (index == OUTSIDE) continue; + _nodes[index].Insert(next); + _containers.Remove(i); + } + } + + public void Remove(long entityId) + { + if (!_idToContainer.ContainsKey(entityId)) return; + + var container = _idToContainer[entityId]; + var parent = container.Parent; + parent?._containers.Remove(container); + _idToContainer.Remove(entityId); + if (container.ChildIds != null) + foreach (var childId in container.ChildIds) + _idToContainer.Remove(childId); + container.Value?.Dispose(); + container.Value = default; + _cPool.Release(container); + parent?._parent?.HandleMerge(); + } + + #endregion + + #region Getters + + public bool Contains(long entityId) + { + return _idToContainer.ContainsKey(entityId); + } + + public T Get(long entityId) + { + return !Contains(entityId) ? default : _idToContainer[entityId].Value; + } + + /// + /// Returns entityIds of entities that are inside the OcTrees that contain given point. + /// + /// List to fill + /// X position + /// Y position + /// Z position + public List Get(List entityIds, float x, float y, float z) + { + if (!_bounds.Contains(x, y, z)) return entityIds; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, 0f, 0f, 0f); + if (index != OUTSIDE) _nodes[index].Get(entityIds, x, y, z); + } + + entityIds.AddRange(_containers.Select(container => container.EntityId)); + + return entityIds; + } + + public List Get(List entityIds, float x, float y, float z, long flags) + { + if (flags == 0L) return Get(entityIds, x, y, z); + if (!_bounds.Contains(x, y, z)) return entityIds; + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, 0f, 0f, 0f); + if (index != OUTSIDE) _nodes[index].Get(entityIds, x, y, z, flags); + } + + entityIds.AddRange(from container in _containers + where container.MatchesFlags(flags) + select container.EntityId + ); + + return entityIds; + } + + public List Get(List entityIds, BoundingSphereD sphereD) + { + var box = sphereD.GetBoundingBox(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].Get(entityIds, sphereD); + else + foreach (var ocTree in _nodes) + ocTree.Get(entityIds, sphereD); + } + + entityIds.AddRange(_containers.Select(container => container.EntityId)); + + return entityIds; + } + + public List Get(List entityIds, long flags, BoundingSphereD sphereD) + { + if (flags == 0L) return Get(entityIds, sphereD); + var box = sphereD.GetBoundingBox(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].Get(entityIds, flags, sphereD); + else + foreach (var ocTree in _nodes) + ocTree.Get(entityIds, flags, sphereD); + } + + entityIds.AddRange(from container in _containers + where container.MatchesFlags(flags) + select container.EntityId); + + return entityIds; + } + + //TODO: Make exact check sphere rather than box + public List GetExact(List entityIds, BoundingSphereD sphereD) + { + var box = sphereD.GetBoundingBox(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].GetExact(entityIds, sphereD); + else + foreach (var ocTree in _nodes) + ocTree.GetExact(entityIds, sphereD); + } + + entityIds.AddRange(from container in _containers + where container.Overlaps(x, y, z, width, height, depth) && sphereD.Intersects( + new BoundingBoxD(new Vector3D(container.X, container.Y, container.Z), + new Vector3D( + container.X + container.Width, + container.Y + container.Height, + container.Z + container.Depth + ))) + select container.EntityId); + + return entityIds; + } + + public List GetExact(List entityIds, long flags, BoundingSphereD sphereD) + { + if (flags == 0L) return GetExact(entityIds, sphereD); + var box = sphereD.GetBoundingBox(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].GetExact(entityIds, flags, sphereD); + else + foreach (var ocTree in _nodes) + ocTree.GetExact(entityIds, flags, sphereD); + } + + entityIds.AddRange(from container in _containers + where container.MatchesFlags(flags) && + container.Overlaps(x, y, z, width, height, depth) && + sphereD.Intersects( + new BoundingBoxD(new Vector3D(container.X, container.Y, container.Z), + new Vector3D( + container.X + container.Width, + container.Y + container.Height, + container.Z + container.Depth + ))) + select container.EntityId); + + return entityIds; + } + + public List Get(List entityIds, BoundingBoxD boxD) + { + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].Get(entityIds, boxD); + else + foreach (var ocTree in _nodes) + ocTree.Get(entityIds, boxD); + } + + entityIds.AddRange(_containers.Select(container => container.EntityId)); + + return entityIds; + } + + public List Get(List entityIds, long flags, BoundingBoxD boxD) + { + if (flags == 0L) return Get(entityIds, boxD); + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].Get(entityIds, flags, boxD); + else + foreach (var ocTree in _nodes) + ocTree.Get(entityIds, flags, boxD); + } + + entityIds.AddRange(from container in _containers + where container.MatchesFlags(flags) && container.Overlaps(x, y, z, width, height, depth) + select container.EntityId); + + return entityIds; + } + + public List GetExact(List entityIds, MyOrientedBoundingBoxD boxBoundingBox) + { + var boxD = boxBoundingBox.GetAABB(); + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].GetExact(entityIds, boxD); + else + foreach (var ocTree in _nodes) + ocTree.GetExact(entityIds, boxD); + } + + foreach (var container in _containers) + { + if (!container.Overlaps(x, y, z, width, height, depth)) continue; + + var containerBox = container.BoundingBox; + if (boxBoundingBox.Intersects(ref containerBox)) entityIds.Add(container.EntityId); + } + + return entityIds; + } + + public List GetExact(List entityIds, long flags, MyOrientedBoundingBoxD boxBoundingBox) + { + if (flags == 0L) return GetExact(entityIds, boxBoundingBox); + var boxD = boxBoundingBox.GetAABB(); + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].GetExact(entityIds, boxD); + else + foreach (var ocTree in _nodes) + ocTree.GetExact(entityIds, boxD); + } + + foreach (var container in _containers) + { + if (!container.MatchesFlags(flags) || + !container.Overlaps(x, y, z, width, height, depth)) continue; + + var containerBox = container.BoundingBox; + if (boxBoundingBox.Intersects(ref containerBox)) entityIds.Add(container.EntityId); + } + + return entityIds; + } + + public List GetExact(List entityIds, BoundingBoxD boxD) + { + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].GetExact(entityIds, boxD); + else + foreach (var ocTree in _nodes) + ocTree.GetExact(entityIds, boxD); + } + + entityIds.AddRange(from container in _containers + where container.Overlaps(x, y, z, width, height, depth) + select container.EntityId); + + return entityIds; + } + + public List GetExact(List entityIds, long flags, BoundingBoxD boxD) + { + if (flags == 0L) return GetExact(entityIds, boxD); + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].GetExact(entityIds, flags, boxD); + else + foreach (var ocTree in _nodes) + ocTree.GetExact(entityIds, flags, boxD); + } + + entityIds.AddRange(from GenericContainer in _containers + where GenericContainer.MatchesFlags(flags) && GenericContainer.Overlaps(x, y, z, width, height, depth) + select GenericContainer.EntityId); + + return entityIds; + } + + + public void GetExact(List outList, BoundingSphereD sphereD, long flags = 0L) + { + var box = sphereD.GetBoundingBox(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].GetExact(outList, sphereD, flags); + else + foreach (var ocTree in _nodes) + ocTree.GetExact(outList, sphereD, flags); + } + + if (_containers.Any()) + outList.AddRange(from container in _containers + where container.MatchesFlags(flags) && + container.Overlaps(x, y, z, width, height, depth) && + sphereD.Intersects(container.BoundingBox) + select container.Value); + } + + public void GetExact(List outList, BoundingBoxD box, long flags = 0L) + { + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].GetExact(outList, box, flags); + else + foreach (var ocTree in _nodes) + ocTree.GetExact(outList, box, flags); + } + + if (_containers.Any()) + outList.AddRange(from container in _containers + where container.MatchesFlags(flags) && + container.Overlaps(x, y, z, width, height, depth) && + box.Intersects(container.BoundingBox) + select container.Value); + } + + public void GetExact(List outList, MyOrientedBoundingBoxD boxD, long flags = 0L) + { + var box = boxD.GetAABB(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) _nodes[index].GetExact(outList, box, flags); + else + foreach (var ocTree in _nodes) + ocTree.GetExact(outList, box, flags); + } + + if (_containers.Any()) + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var container in _containers) + { + if (!container.MatchesFlags(flags) || !container.Overlaps(x, y, z, width, height, depth)) continue; + var containerBox = container.BoundingBox; + if (boxD.Intersects(ref containerBox)) outList.Add(container.Value); + } + } + + + public IEnumerable GetEnumerable(BoundingSphereD sphereD, long flags = 0L) + { + var box = sphereD.GetBoundingBox(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) yield break; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) + foreach (var data in _nodes[index].GetEnumerable(sphereD, flags)) + yield return data; + else + foreach (var data in _nodes.SelectMany(e => e.GetEnumerable(sphereD, flags))) + yield return data; + } + + if (!_containers.Any()) yield break; + + foreach (var c in _containers) + if (c.MatchesFlags(flags) && c.Overlaps(x, y, z, width, height, depth) && + sphereD.Intersects(c.BoundingBox)) + yield return c.Value; + } + + + public IEnumerable GetEnumerable(BoundingBoxD box, long flags = 0L) + { + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) yield break; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) + foreach (var data in _nodes[index].GetEnumerable(box, flags)) + yield return data; + else + foreach (var data in _nodes.SelectMany(e => e.GetEnumerable(box, flags))) + yield return data; + } + + if (!_containers.Any()) yield break; + + foreach (var c in _containers) + if (c.MatchesFlags(flags) && c.Overlaps(x, y, z, width, height, depth) && box.Intersects(c.BoundingBox)) + yield return c.Value; + } + + + public IEnumerable GetEnumerable(MyOrientedBoundingBoxD boxD, long flags = 0L) + { + var box = boxD.GetAABB(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) yield break; + + if (_nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) + foreach (var data in _nodes[index].GetEnumerable(boxD, flags)) + yield return data; + else + foreach (var data in _nodes.SelectMany(e => e.GetEnumerable(boxD, flags))) + yield return data; + } + + if (!_containers.Any()) yield break; + + foreach (var c in _containers) + { + var cBox = c.BoundingBox; + if (c.MatchesFlags(flags) && c.Overlaps(x, y, z, width, height, depth) && boxD.Intersects(ref cBox)) + yield return c.Value; + } + } + + #endregion + } + + public class GenericContainer : IPoolable, IContainer where T : IOcTreeData + { + private static readonly BoundingBoxD ReusableBox = new BoundingBoxD(); + public List ChildIds; + public long EntityId; + + public long Flags; + public GenericOcTree Parent; + + public T Value; + + public BoundingBoxD BoundingBox => ReusableBox.SetToContainer(this); + public float X { get; private set; } + public float Y { get; private set; } + public float Z { get; private set; } + public float Width { get; private set; } + public float Height { get; private set; } + public float Depth { get; private set; } + + public void Reset() + { + ChildIds = null; + EntityId = -1; + Flags = 0; + X = 0; + Y = 0; + Z = 0; + Width = 0; + Height = 0; + Depth = 0; + Value = default; + } + + public GenericContainer Set(T value) + { + Value = value; + ChildIds = value.ChildIds; + EntityId = value.Id; + Flags = value.Flags; + var box = value.BoundingBox; + X = (float)box.Min.X; + Y = (float)box.Min.Y; + Z = (float)box.Min.Z; + Width = (float)box.Max.X - X; + Height = (float)box.Max.Y - Y; + Depth = (float)box.Max.Z - Z; + return this; + } + + public KeyValuePair, IEnumerable> Update() + { + if (Value == null) return new KeyValuePair, IEnumerable>(null, null); + Value.Update(); + var previousChildIds = ChildIds; + ChildIds = Value.ChildIds; + Flags = Value.Flags; + var box = Value.BoundingBox; + X = (float)box.Min.X; + Y = (float)box.Min.Y; + Z = (float)box.Min.Z; + Width = (float)box.Max.X - X; + Height = (float)box.Max.Y - Y; + Depth = (float)box.Max.Z - Z; + return new KeyValuePair, IEnumerable>( + previousChildIds.Where(e => !ChildIds.Contains(e)), + ChildIds.Where(e => !previousChildIds.Contains(e))); + } + + public GenericContainer Set(float x, float y, float z, float width, float height, float depth) + { + X = x; + Y = y; + Z = z; + Width = width; + Height = height; + Depth = depth; + return this; + } + + public bool MatchesFlags(long flags) + { + return flags == 0 || (Flags & flags) > 0; + } + + public bool Contains(float x, float y, float z) + { + return X <= x && x <= X + Width && + Y <= y && y <= Y + Height && + Z <= z && z <= Z + Depth; + } + + public bool Contains(float x, float y, float z, float width, float height, float depth) + { + var xMax = x + width; + var yMax = x + height; + var zMax = x + depth; + + return x > X && x < X + Width && xMax > X && xMax < X + Width && + y > Y && y < Y + Height && yMax > Y && yMax < Y + Height && + z > Z && z < Z + Depth && zMax > Z && zMax < Z + Depth; + } + + public bool Overlaps(float x, float y, float z, float width, float height, float depth) + { + return !(X > x + width || x > X + Width || Y > y + height || y > Y + Height || Z > z + depth || + z > Z + Depth); + } + + public bool IntersectsRay(Vector3 start, Vector3 end) + { + var lineD = new LineD(start, end); + var box = BoundingBox; + return box.Intersects(ref lineD); + } + + public bool Contains(GenericContainer c) + { + return Contains(c.X, c.Y, c.Z, c.Width, c.Height, c.Depth); + } + } +} \ No newline at end of file diff --git a/GlobalShared/OcTree/IContainer.cs b/GlobalShared/OcTree/IContainer.cs new file mode 100644 index 0000000..6fec185 --- /dev/null +++ b/GlobalShared/OcTree/IContainer.cs @@ -0,0 +1,12 @@ +namespace Global.Shared.OcTree +{ + public interface IContainer + { + float X { get; } + float Y { get; } + float Z { get; } + float Width { get; } + float Height { get; } + float Depth { get; } + } +} \ No newline at end of file diff --git a/GlobalShared/OcTree/OcTree.cs b/GlobalShared/OcTree/OcTree.cs new file mode 100644 index 0000000..0c54f45 --- /dev/null +++ b/GlobalShared/OcTree/OcTree.cs @@ -0,0 +1,707 @@ +using System.Collections.Generic; +using System.Linq; +using VRageMath; + +namespace Global.Shared.OcTree +{ + public class OcTree : IPoolable + { + private static int TotalIds; + private static readonly Pool cPool = new Pool(() => new Container()); + private static readonly Pool otPool = new Pool(() => new OcTree()); + + private readonly Container _bounds; + + private readonly List _containersToAdd = new List(); + private readonly OcTree[] nodes = new OcTree[8]; + private Bag _containers; + + private int capacity; + + private Dictionary idToContainer; + private int maxDepth; + + private OcTree parent; + + private BoundingBoxD reusableBox = new BoundingBoxD(); + private int treeDepth; + + private OcTree() : this(0, 0, 0, 0, 0, 0, 0, 0) + { + } + + public OcTree(float x, float y, float z, float width, float height, float depth, int capacity, + int maxDepth) + { + _bounds = new Container().Set(x, y, z, width, height, depth); + this.capacity = capacity; + this.maxDepth = maxDepth; + _containers = new Bag(capacity); + idToContainer = new Dictionary(); + } + + public void Reset() + { + for (var i = _containers.Size() - 1; i >= 0; i--) cPool.Release(_containers.Remove(i)); + for (var i = 0; i < nodes.Length; i++) + if (nodes[i] != null) + { + otPool.Release(nodes[i]); + nodes[i] = null; + } + } + + private OcTree Init(int currentTreeDepth, float x, float y, float z, float width, float height, float depth, + OcTree parent) + { + treeDepth = currentTreeDepth; + this.parent = parent; + capacity = parent?.capacity ?? 1; + maxDepth = parent?.maxDepth ?? 6; + _bounds.Set(x, y, z, width, height, depth); + idToContainer = parent?.idToContainer ?? new Dictionary(); + _containers = new Bag(capacity); + + return this; + } + + private int IndexOf(float x, float y, float z, float width, float height, float depth) + { + var midX = _bounds.X + _bounds.Width / 2; + var midY = _bounds.Y + _bounds.Height / 2; + var midZ = _bounds.Z + _bounds.Depth / 2; + + var res = 0; + if (x > midX) res |= E; + else if (x < midX && x + width < midX) res |= W; + else return OUTSIDE; + + if (y > midY) res |= U; + else if (y < midY && y + height < midY) res |= D; + else return OUTSIDE; + + if (z > midZ) res |= S; + else if (z < midZ && z + depth < midZ) res |= N; + else return OUTSIDE; + + return res; + } + + private bool ShouldMerge() + { + // If has no children, has nothing to merge + if (nodes[0] == null) return false; + // If has any children with children, cannot merge + if (nodes.Any(n => n.nodes[0] != null)) return false; + // If children combined contain less than capacity, can merge + return _containers.Size() + nodes.Sum(n => n._containers.Size()) <= capacity; + } + + private void Split() + { + var halfWidth = _bounds.Width / 2; + var halfHeight = _bounds.Height / 2; + var halfDepth = _bounds.Depth / 2; + nodes[DNW] = otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y, _bounds.Z, halfWidth, halfHeight, + halfDepth, this); + nodes[DNE] = otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y, _bounds.Z, halfWidth, + halfHeight, halfDepth, this); + nodes[DSW] = otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y, _bounds.Z + halfDepth, halfWidth, + halfHeight, halfDepth, this); + nodes[DSE] = otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y, + _bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this); + + nodes[UNW] = otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y + halfHeight, _bounds.Z, + halfWidth, halfHeight, halfDepth, this); + nodes[UNE] = otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y + halfHeight, + _bounds.Z, halfWidth, halfHeight, halfDepth, this); + nodes[USW] = otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y + halfHeight, + _bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this); + nodes[USE] = otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y + halfHeight, + _bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this); + } + + private void HandleMerge() + { + if (!ShouldMerge()) return; + + for (var index = nodes.Length - 1; index >= 0; index--) + { + var ocTree = nodes[index]; + for (var i = ocTree._containers.Size() - 1; i >= 0; i--) + { + var ocTreeContainer = ocTree._containers[i]; + _containersToAdd.Add(ocTreeContainer); + ocTree._containers.Remove(i); + } + } + + for (var index = nodes.Length - 1; index >= 0; index--) + { + var ocTree = nodes[index]; + nodes[index] = null; + otPool.Release(ocTree); + ocTree.Reset(); + } + + foreach (var container in _containersToAdd) Insert(container); + + _containersToAdd.Clear(); + } + + #region Node Bitwise + + private const int OUTSIDE = -1; + private const int D = 0b000; + private const int U = 0b100; + private const int S = 0b000; + private const int N = 0b010; + private const int W = 0b000; + private const int E = 0b001; + private const int DSW = D | S | W; // 0 + private const int DSE = D | S | E; // 1 + private const int DNW = D | N | W; // 2 + private const int DNE = D | N | E; // 3 + private const int USW = U | S | W; // 4 + private const int USE = U | S | E; // 5 + private const int UNW = U | N | W; // 6 + private const int UNE = U | N | E; // 7 + + #endregion + + #region Modifiers + + public void Upsert(long entityId, long flags, BoundingBoxD aabb) + { + Upsert(entityId, flags, (float)aabb.Min.X, (float)aabb.Min.Y, (float)aabb.Min.Z, + (float)aabb.Max.X - (float)aabb.Min.X, (float)aabb.Max.Y - (float)aabb.Min.Y, + (float)aabb.Max.Z - (float)aabb.Min.Z); + } + + public void Upsert(long entityId, float x, float y, float z, float width, float height, float depth) + { + Upsert(entityId, 0L, x, y, z, width, height, depth); + } + + public void Upsert(long entityId, long flags, float x, float y, float z, float width, float height, float depth) + { + Container container = null; + if (idToContainer.ContainsKey(entityId)) container = idToContainer[entityId]; + if (container != null && container.EntityId != -1) + { + container.Flags |= flags; + Update(entityId, x, y, z, width, height, depth); + return; + } + + Insert(entityId, flags, x, y, z, width, height, depth); + } + + private void Update(long entityId, float x, float y, float z, float width, float height, float depth) + { + var c = idToContainer[entityId]; + c.Set(entityId, c.Flags, x, y, z, width, height, depth); + + var parentTree = c.Parent; + if (parentTree._bounds.Contains(c)) return; + + parentTree._containers.Remove(c); + while (parentTree.parent != null && !parentTree._bounds.Contains(c)) parentTree = parentTree.parent; + + parentTree.Insert(c); + } + + private void Insert(long entityId, float x, float y, float z, float width, float height, float depth) + { + Insert(entityId, 0L, x, y, z, width, height, depth); + } + + private void Insert(long entityId, long flags, float x, float y, float z, float width, float height, + float depth) + { + Insert(cPool.Obtain().Set(entityId, flags, x, y, z, width, height, depth)); + } + + private void Insert(Container c) + { + if (nodes[0] != null) + { + var index = IndexOf(c.X, c.Y, c.Z, c.Width, c.Height, c.Depth); + if (index != OUTSIDE) + { + nodes[index].Insert(c); + return; + } + } + + c.Parent = this; + idToContainer[c.EntityId] = c; + _containers.Add(c); + + if (_containers.Size() <= capacity || treeDepth >= maxDepth) return; + + if (nodes[0] == null) Split(); + + var items = _containers.data; + for (var i = _containers.Size() - 1; i >= 0; i--) + { + var next = items[i]; + if (next == null) continue; + var index = IndexOf(next.X, next.Y, next.Z, next.Width, next.Height, next.Depth); + if (index == OUTSIDE) continue; + nodes[index].Insert(next); + _containers.Remove(i); + } + } + + public void Remove(long entityId) + { + if (!idToContainer.ContainsKey(entityId)) return; + + var container = idToContainer[entityId]; + var parent = container.Parent; + parent?._containers.Remove(container); + idToContainer.Remove(entityId); + cPool.Release(container); + parent?.parent?.HandleMerge(); + } + + #endregion + + #region Getters + + /// + /// Returns entityIds of entities that are inside the OcTrees that contain given point. + /// + /// List to fill + /// X position + /// Y position + /// Z position + public List Get(List entityIds, float x, float y, float z) + { + if (!_bounds.Contains(x, y, z)) return entityIds; + + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, 0f, 0f, 0f); + if (index != OUTSIDE) nodes[index].Get(entityIds, x, y, z); + } + + entityIds.AddRange(_containers.Select(container => container.EntityId)); + + return entityIds; + } + + public List Get(List entityIds, float x, float y, float z, long flags) + { + if (flags == 0L) return Get(entityIds, x, y, z); + if (!_bounds.Contains(x, y, z)) return entityIds; + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, 0f, 0f, 0f); + if (index != OUTSIDE) nodes[index].Get(entityIds, x, y, z, flags); + } + + entityIds.AddRange(from container in _containers + where container.MatchesFlags(flags) + select container.EntityId + ); + + return entityIds; + } + + public List Get(List entityIds, BoundingSphereD sphereD) + { + var box = sphereD.GetBoundingBox(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) nodes[index].Get(entityIds, sphereD); + else + foreach (var ocTree in nodes) + ocTree.Get(entityIds, sphereD); + } + + entityIds.AddRange(_containers.Select(container => container.EntityId)); + + return entityIds; + } + + public List Get(List entityIds, long flags, BoundingSphereD sphereD) + { + if (flags == 0L) return Get(entityIds, sphereD); + var box = sphereD.GetBoundingBox(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) nodes[index].Get(entityIds, flags, sphereD); + else + foreach (var ocTree in nodes) + ocTree.Get(entityIds, flags, sphereD); + } + + entityIds.AddRange(from container in _containers + where container.MatchesFlags(flags) + select container.EntityId); + + return entityIds; + } + + //TODO: Make exact check sphere rather than box + public List GetExact(List entityIds, BoundingSphereD sphereD) + { + var box = sphereD.GetBoundingBox(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) nodes[index].GetExact(entityIds, sphereD); + else + foreach (var ocTree in nodes) + ocTree.GetExact(entityIds, sphereD); + } + + entityIds.AddRange(from container in _containers + where container.Overlaps(x, y, z, width, height, depth) && sphereD.Intersects( + new BoundingBoxD(new Vector3D(container.X, container.Y, container.Z), + new Vector3D( + container.X + container.Width, + container.Y + container.Height, + container.Z + container.Depth + ))) + select container.EntityId); + + return entityIds; + } + + public List GetExact(List entityIds, long flags, BoundingSphereD sphereD) + { + if (flags == 0L) return GetExact(entityIds, sphereD); + var box = sphereD.GetBoundingBox(); + var x = (float)box.Min.X; + var y = (float)box.Min.Y; + var z = (float)box.Min.Z; + var width = (float)box.Max.X - x; + var height = (float)box.Max.Y - y; + var depth = (float)box.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) nodes[index].GetExact(entityIds, flags, sphereD); + else + foreach (var ocTree in nodes) + ocTree.GetExact(entityIds, flags, sphereD); + } + + entityIds.AddRange(from container in _containers + where container.MatchesFlags(flags) && container.Overlaps(x, y, z, width, height, depth) && + sphereD.Intersects( + new BoundingBoxD(new Vector3D(container.X, container.Y, container.Z), + new Vector3D( + container.X + container.Width, + container.Y + container.Height, + container.Z + container.Depth + ))) + select container.EntityId); + + return entityIds; + } + + public List Get(List entityIds, BoundingBoxD boxD) + { + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) nodes[index].Get(entityIds, boxD); + else + foreach (var ocTree in nodes) + ocTree.Get(entityIds, boxD); + } + + entityIds.AddRange(_containers.Select(container => container.EntityId)); + + return entityIds; + } + + public List Get(List entityIds, long flags, BoundingBoxD boxD) + { + if (flags == 0L) return Get(entityIds, boxD); + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) nodes[index].Get(entityIds, flags, boxD); + else + foreach (var ocTree in nodes) + ocTree.Get(entityIds, flags, boxD); + } + + entityIds.AddRange(from container in _containers + where container.MatchesFlags(flags) && container.Overlaps(x, y, z, width, height, depth) + select container.EntityId); + + return entityIds; + } + + public List GetExact(List entityIds, MyOrientedBoundingBoxD boxBoundingBox) + { + var boxD = boxBoundingBox.GetAABB(); + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) nodes[index].GetExact(entityIds, boxD); + else + foreach (var ocTree in nodes) + ocTree.GetExact(entityIds, boxD); + } + + foreach (var container in _containers) + { + if (!container.Overlaps(x, y, z, width, height, depth)) continue; + + var containerBox = container.BoundingBox; + if (boxBoundingBox.Intersects(ref containerBox)) entityIds.Add(container.EntityId); + } + + return entityIds; + } + + public List GetExact(List entityIds, long flags, MyOrientedBoundingBoxD boxBoundingBox) + { + if (flags == 0L) return GetExact(entityIds, boxBoundingBox); + var boxD = boxBoundingBox.GetAABB(); + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) nodes[index].GetExact(entityIds, boxD); + else + foreach (var ocTree in nodes) + ocTree.GetExact(entityIds, boxD); + } + + foreach (var container in _containers) + { + if (!container.MatchesFlags(flags) || !container.Overlaps(x, y, z, width, height, depth)) continue; + + var containerBox = container.BoundingBox; + if (boxBoundingBox.Intersects(ref containerBox)) entityIds.Add(container.EntityId); + } + + return entityIds; + } + + public List GetExact(List entityIds, BoundingBoxD boxD) + { + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) nodes[index].GetExact(entityIds, boxD); + else + foreach (var ocTree in nodes) + ocTree.GetExact(entityIds, boxD); + } + + entityIds.AddRange(from container in _containers + where container.Overlaps(x, y, z, width, height, depth) + select container.EntityId); + + return entityIds; + } + + public List GetExact(List entityIds, long flags, BoundingBoxD boxD) + { + if (flags == 0L) return GetExact(entityIds, boxD); + var x = (float)boxD.Min.X; + var y = (float)boxD.Min.Y; + var z = (float)boxD.Min.Z; + var width = (float)boxD.Max.X - x; + var height = (float)boxD.Max.Y - y; + var depth = (float)boxD.Max.Z - z; + + if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds; + + if (nodes[0] != null) + { + var index = IndexOf(x, y, z, width, height, depth); + if (index != OUTSIDE) nodes[index].GetExact(entityIds, flags, boxD); + else + foreach (var ocTree in nodes) + ocTree.GetExact(entityIds, flags, boxD); + } + + entityIds.AddRange(from container in _containers + where container.MatchesFlags(flags) && container.Overlaps(x, y, z, width, height, depth) + select container.EntityId); + + return entityIds; + } + + #endregion + } + + public class Container : IPoolable + { + public long EntityId; + public long Flags; + public OcTree Parent; + public float X { get; private set; } + public float Y { get; private set; } + public float Z { get; private set; } + public float Width { get; private set; } + public float Height { get; private set; } + public float Depth { get; private set; } + + public BoundingBoxD BoundingBox => + new BoundingBoxD(new Vector3(X, Y, Z), new Vector3(X + Width, Y + Height, Z + Depth)); + + public void Reset() + { + EntityId = -1; + Flags = 0; + X = 0; + Y = 0; + Z = 0; + Width = 0; + Height = 0; + Depth = 0; + } + + public Container Set(long entityId, long flags, float x, float y, float z, float width, float height, + float depth) + { + EntityId = entityId; + Flags = flags; + X = x; + Y = y; + Z = z; + Width = width; + Height = height; + Depth = depth; + return this; + } + + public Container Set(float x, float y, float z, float width, float height, float depth) + { + X = x; + Y = y; + Z = z; + Width = width; + Height = height; + Depth = depth; + return this; + } + + public bool MatchesFlags(long flags) + { + return (Flags & flags) > 0; + } + + public bool Contains(float x, float y, float z) + { + return X <= x && x <= X + Width && + Y <= y && y <= Y + Height && + Z <= z && z <= Z + Depth; + } + + public bool Contains(float x, float y, float z, float width, float height, float depth) + { + var xMax = x + width; + var yMax = x + height; + var zMax = x + depth; + + return x > X && x < X + Width && xMax > X && xMax < X + Width && + y > Y && y < Y + Height && yMax > Y && yMax < Y + Height && + z > Z && z < Z + Depth && zMax > Z && zMax < Z + Depth; + } + + public bool Overlaps(float x, float y, float z, float width, float height, float depth) + { + if (X > x + width) return false; + if (x > X + Width) return false; + if (Y > y + height) return false; + if (y > Y + Height) return false; + if (Z > z + depth) return false; + if (z > Z + Depth) return false; + + return true; + } + + public bool Contains(Container c) + { + return Contains(c.X, c.Y, c.Z, c.Width, c.Height, c.Depth); + } + } +} \ No newline at end of file diff --git a/GlobalShared/OcTree/OcTreeHandler.cs b/GlobalShared/OcTree/OcTreeHandler.cs new file mode 100644 index 0000000..b510217 --- /dev/null +++ b/GlobalShared/OcTree/OcTreeHandler.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.Linq; +using Global.Patches; +using Global.Shared.API; +using Global.Shared.Events; +using Global.Shared.OcTree.Data; +using Global.Shared.Plugin; +using Sandbox.Game.Entities; +using VRage.Game.Components; +using VRage.Game.Entity; +using VRage.Game.ModAPI; + +namespace Global.Shared.OcTree +{ + public class OcTreeHandler + { + public GenericOcTree GridTree { get; private set; } + + public GenericOcTree CharacterTree { get; private set; } + + internal void Init() + { + var cfg = GlobalInstance.Config; + var offset = cfg.CenterPosition; + var x = cfg.Size; + var y = cfg.Size; + var z = cfg.Size; + GridTree = new GenericOcTree(offset.X - x, offset.Y - y, offset.Z - z, x * 2, y * 2, z * 2, + cfg.Capacity, cfg.MaxDepth); + CharacterTree = + new GenericOcTree(offset.X - x, offset.Y - y, offset.Z - z, x * 2, y * 2, z * 2, + cfg.Capacity, cfg.MaxDepth); + + BlockEvents.OnPlayerControlAcquired += AddControllableEntity; + BlockEvents.OnPlayerControlReleased += RemoveControllableEntity; + } + + public IGridData AddGrid(MyCubeGrid grid) + { + var bigOwner = grid.BigOwners.Count == 0 ? "No Owner!" : grid.BigOwners.First().ToString(); + // check if grid is new subgrid + if (MyMechanicalConnectionBlockBasePatch.IsCreatingSubPart) + { + GlobalInstance.Log.Trace( + $"Grid is subgrid, ignoring {grid.DisplayName} {grid.PositionComp.GetPosition()} {bigOwner}"); + return null; + } + + if (!GridTree.Contains(grid.EntityId)) + { + var grids = grid.GetGridGroup(GridLinkTypeEnum.Mechanical).GetGrids(new List()); + var largestGrid = grid; + if (grids.Count > 1) + foreach (var g in grids.Cast()) + if (g.BlocksCount > largestGrid.BlocksCount) + largestGrid = g; + + grid = largestGrid; + //GlobalInstance.Log.Info( + //$"Adding grid to OcTree: {grid.EntityId} {grid.DisplayName} {grid.PositionComp.GetPosition()}"); + + var data = new GridData(grid); + grid.PositionComp.OnPositionChanged += Grid_OnPositionChanged; + grid.OnMarkForClose += RemoveGrid; + grid.OnTeleported += Grid_OnChanged; + GridTree.Insert(data); + grid.OnConnectionChanged += Grid_OnConnectionChanged; + + GridEvents.GridCreated?.Invoke(grid); + return data; + } + + GlobalInstance.Log.Debug( + $"Attempted to add grid to OcTree that already exists, {grid.DisplayName} {grid.PositionComp.GetPosition()} {bigOwner}"); + + var existing = GridTree[grid.EntityId]; + + if (existing != null) return existing; + + GridTree.Remove(grid.EntityId); + return AddGrid(grid); + } + + public void AddControllableEntity(IMyControllableEntity entity) + { + if (entity.ControllerInfo?.Controller?.Player == null && !(entity is IMyCharacter)) + //GlobalInstance.Log.Info($"Entity is not controlled {entity.Entity.EntityId} {entity.Entity.DisplayName}"); + return; + //GlobalInstance.Log.Debug($"AddControllableEntity {entity.Entity.EntityId} {entity.Entity.DisplayName}"); + entity.Entity.OnMarkForClose += RemoveControllableEntity; + + var data = new ControllableEntityData(entity); + + if (!CharacterTree.Contains(entity.Entity.EntityId)) CharacterTree.Insert(data); + } + + public void RemoveControllableEntity(IMyControllableEntity entity) + { + RemoveControllableEntity((MyEntity)entity); + } + + public void RemoveControllableEntity(MyEntity entity) + { + //GlobalInstance.Log.Debug("RemoveControllableEntity"); + CharacterTree.Remove(entity.EntityId); + } + + private void Grid_OnConnectionChanged(MyCubeGrid arg1, GridLinkTypeEnum arg2) + { + if (arg2 != GridLinkTypeEnum.Mechanical) return; + + GridTree.Update(arg1.EntityId); + } + + internal void RemoveGrid(MyEntity grid) + { + GlobalInstance.Log.Debug($"Removing grid from OcTree: {grid.EntityId} {grid.DisplayName}"); + GridEvents.GridRemoved?.Invoke(grid as MyCubeGrid); + + grid.PositionComp.OnPositionChanged -= Grid_OnPositionChanged; + grid.OnMarkForClose -= RemoveGrid; + grid.OnTeleported -= Grid_OnChanged; + + GridTree.Remove(grid.EntityId); + } + + private void Grid_OnChanged(MyEntity obj) + { + GridTree.Update(obj.EntityId); + } + + private void Grid_OnPositionChanged(MyPositionComponentBase obj) + { + if (!(obj.Entity is MyCubeGrid grid)) return; + if (grid.Closed || grid.MarkedForClose) return; + + GridTree.Update(grid.EntityId); + } + } +} \ No newline at end of file diff --git a/GlobalShared/OcTree/Pool.cs b/GlobalShared/OcTree/Pool.cs new file mode 100644 index 0000000..7df836c --- /dev/null +++ b/GlobalShared/OcTree/Pool.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Global.Shared.OcTree +{ + public interface IPoolable + { + void Reset(); + } + + public class Pool where E : IPoolable + { + private readonly Func creator; + private readonly List pool = new List(); + + public Pool(Func creator) + { + this.creator = creator; + } + + public E Obtain() + { + if (pool.Count <= 0) return creator(); + + var e = pool[0]; + pool.RemoveAt(0); + return e; + } + + public void Release(E e) + { + e.Reset(); + pool.Add(e); + } + } +} \ No newline at end of file diff --git a/GlobalShared/Patches/CubeGridPatch.cs b/GlobalShared/Patches/CubeGridPatch.cs new file mode 100644 index 0000000..73b1b7f --- /dev/null +++ b/GlobalShared/Patches/CubeGridPatch.cs @@ -0,0 +1,66 @@ +using System.Threading; +using Global.Shared.Events; +using HarmonyLib; +using Sandbox.Definitions; +using Sandbox.Game.Entities; +using VRageMath; + +namespace Global.Shared.Patches +{ + [HarmonyPatch(typeof(MyCubeGrid))] + public class CubeGridPatch + { + private static readonly ThreadLocal CallDepth = new ThreadLocal(); + + public static ThreadLocal PlayerId = new ThreadLocal(); + public static bool IsInMergeGridInternal => CallDepth.Value > 0; + + [HarmonyPrefix] + [HarmonyPatch("MergeGridInternal")] + private static bool MergeGridInternalPrefix() + { + // if (Config.Patches.DisableAllPatches || !Config.Patches.EnableGridMerge) return true; + + CallDepth.Value++; + + return true; + } + + [HarmonyPostfix] + [HarmonyPatch("MergeGridInternal")] + private static void MergeGridInternalPostfix(MyCubeGrid __instance) + { + if (!IsInMergeGridInternal) return; + if (--CallDepth.Value > 0) return; + __instance.GridSystems.ConveyorSystem.FlagForRecomputation(); + } + + [HarmonyPatch("CanPlaceBlock", typeof(Vector3I), typeof(Vector3I), typeof(MyBlockOrientation), + typeof(MyCubeBlockDefinition), typeof(ulong), typeof(int?), typeof(bool), typeof(bool))] + [HarmonyPostfix] + private static void CanPlaceBlockPostfix(Vector3I min, + Vector3I max, + MyBlockOrientation orientation, + MyCubeBlockDefinition definition, + ulong placingPlayer = 0, + int? ignoreMultiblockId = null, + bool ignoreFracturedPieces = false, + bool isProjection = false) + { + PlayerId.Value = placingPlayer; + } + + [HarmonyPatch("CanAddCubes", typeof(Vector3I), typeof(Vector3I), typeof(MyBlockOrientation), + typeof(MyCubeBlockDefinition))] + [HarmonyPostfix] + private static bool CanAddCubesPostfix(bool canPlace, MyCubeGrid __instance, Vector3I min, + Vector3I max, + MyBlockOrientation? orientation, + MyCubeBlockDefinition definition) + { + BlockEvents.OnCanPlaceBlockEvent(__instance, orientation, definition, PlayerId.Value, ref canPlace); + PlayerId.Value = 0; + return canPlace; + } + } +} \ No newline at end of file diff --git a/GlobalShared/Patches/EntityNamePatch.cs b/GlobalShared/Patches/EntityNamePatch.cs new file mode 100644 index 0000000..95fea3f --- /dev/null +++ b/GlobalShared/Patches/EntityNamePatch.cs @@ -0,0 +1,85 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using HarmonyLib; +using Sandbox.Game.Entities; +using VRage.Game.Entity; + +namespace Global.Shared.Patches +{ + [HarmonyPatch(typeof(MyEntities))] + public class EntityNamePatch + { + public static ConcurrentDictionary EntityNameReverseLookup = + new ConcurrentDictionary(); + // reverse dictionary + + [HarmonyPatch("SetEntityName")] + [HarmonyPrefix] + private static bool SetEntityNamePrefix(MyEntity myEntity, bool possibleRename) + { + if (string.IsNullOrEmpty(myEntity.Name)) + return false; + string previousName = null; + if (possibleRename) + if (EntityNameReverseLookup.ContainsKey(myEntity.EntityId)) + { + previousName = EntityNameReverseLookup[myEntity.EntityId]; + if (previousName != myEntity.Name) MyEntities.m_entityNameDictionary.Remove(previousName); + } + + if (MyEntities.m_entityNameDictionary.TryGetValue(myEntity.Name, out var myEntity1)) + { + if (myEntity1 == myEntity) + return false; + } + else + { + MyEntities.m_entityNameDictionary[myEntity.Name] = myEntity; + EntityNameReverseLookup[myEntity.EntityId] = myEntity.Name; + } + + // MyEntitiesButBetter.CallRename(myEntity, previousName, myEntity.Name); + + return false; + } + + [HarmonyPatch("RemoveName")] + [HarmonyPrefix] + private static bool RemoveNamePrefix(MyEntity entity) + { + if (string.IsNullOrEmpty(entity.Name)) + return false; + MyEntities.m_entityNameDictionary.Remove(entity.Name); + EntityNameReverseLookup.Remove(entity.EntityId); + return false; + } + + [HarmonyPatch("IsNameExists")] + [HarmonyPrefix] + private static bool IsNameExistsPrefix(ref bool __result, MyEntity entity, string name) + { + if (string.IsNullOrEmpty(entity.Name)) + { + __result = false; + return false; + } + + if (MyEntities.m_entityNameDictionary.ContainsKey(name)) + { + var ent = MyEntities.m_entityNameDictionary[entity.Name]; + __result = ent != entity; + return false; + } + + __result = false; + return false; + } + + [HarmonyPatch("UnloadData")] + [HarmonyPostfix] + private static void UnloadDataPostfix() + { + EntityNameReverseLookup.Clear(); + } + } +} \ No newline at end of file diff --git a/GlobalShared/Patches/MyConveyorLinePatch.cs b/GlobalShared/Patches/MyConveyorLinePatch.cs new file mode 100644 index 0000000..1696fce --- /dev/null +++ b/GlobalShared/Patches/MyConveyorLinePatch.cs @@ -0,0 +1,17 @@ +using Global.Shared.Patches; +using HarmonyLib; +using Sandbox.Game.GameSystems.Conveyors; + +namespace Global.Patches +{ + [HarmonyPatch(typeof(MyConveyorLine))] + public class MyConveyorLinePatch + { + [HarmonyPrefix] + [HarmonyPatch(nameof(MyConveyorLine.UpdateIsWorking))] + private static bool UpdateIsWorkingPrefix() + { + return !CubeGridPatch.IsInMergeGridInternal; + } + } +} \ No newline at end of file diff --git a/GlobalShared/Patches/MyGamePruningStructurePatch.cs b/GlobalShared/Patches/MyGamePruningStructurePatch.cs new file mode 100644 index 0000000..26feb1b --- /dev/null +++ b/GlobalShared/Patches/MyGamePruningStructurePatch.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq; +using Global.Shared.API; +using HarmonyLib; +using Sandbox.Game.Entities; +using VRage.Game.Entity; +using VRageMath; + +namespace Global.Shared.Patches +{ + [HarmonyPatch(typeof(MyGamePruningStructure))] + public class MyGamePruningStructurePatch + { + private static readonly List _gridQueryStorage = new List(); + private static readonly List _queryStorage = new List(); + + // [HarmonyPatch("GetTopMostEntitiesInBox")] + // [HarmonyPrefix] + public static bool GetTopMostEntitiesInBox( + ref BoundingBoxD box, + List result, + MyEntityQueryType qtype = MyEntityQueryType.Both) + { + GetTopMostBox(ref box, result, qtype); + return false; + } + + public static void GetTopMostBox(ref BoundingBoxD box, List result, MyEntityQueryType qType) + { + _queryStorage.Clear(); + _gridQueryStorage.Clear(); + var gridSearch = GridFlag.None; + if (qType.HasDynamic()) gridSearch |= GridFlag.DynamicGrid; + if (qType.HasStatic()) gridSearch |= GridFlag.StaticGrid; + if (gridSearch != GridFlag.None) GlobalAPI.LocateGrids(_gridQueryStorage, box, gridSearch); + + result.AddRange(_gridQueryStorage.Select(e => e.CubeGrid)); + + if (qType.HasDynamic()) GlobalAPI.LocateCharacters(_queryStorage, box, PlayerFlag.Character); + + result.AddRange(_queryStorage.Select(l => l.Entity.Entity)); + } + + // [HarmonyPatch("GetAllTopMostStaticEntitiesInBox")] + // [HarmonyPrefix] + public static bool GetAllTopMostStaticEntitiesInBox( + ref BoundingBoxD box, + List result, + MyEntityQueryType qtype = MyEntityQueryType.Both) + { + GetTopMostBox(ref box, result, qtype); + return false; + } + } +} \ No newline at end of file diff --git a/GlobalShared/Patches/MyMechanicalConnectionBlockBasePatch.cs b/GlobalShared/Patches/MyMechanicalConnectionBlockBasePatch.cs new file mode 100644 index 0000000..15c5ab5 --- /dev/null +++ b/GlobalShared/Patches/MyMechanicalConnectionBlockBasePatch.cs @@ -0,0 +1,33 @@ +using Global.Shared.Plugin; +using HarmonyLib; +using Sandbox.Game.Entities.Blocks; + +namespace Global.Patches +{ + [HarmonyPatch(typeof(MyMechanicalConnectionBlockBase))] + public class MyMechanicalConnectionBlockBasePatch + { + public static bool IsCreatingSubPart; + + [HarmonyPatch("RaiseAttachedEntityChanged")] + [HarmonyPrefix] + public static void RaiseAttachedEntityChanged(MyMechanicalConnectionBlockBase __instance) + { + GlobalStatic.OcTreeHandler.GridTree.Get(__instance.CubeGrid.EntityId)?.RaiseAttachedEntityChanged(); + } + + [HarmonyPatch("CreateTopPartAndAttach")] + [HarmonyPrefix] + public static void CreateTopPrefix(long builtBy, bool smallToLarge, bool instantBuild) + { + IsCreatingSubPart = true; + } + + [HarmonyPatch("CreateTopPartAndAttach")] + [HarmonyPostfix] + public static void CreateTopPostfix(long builtBy, bool smallToLarge, bool instantBuild) + { + IsCreatingSubPart = false; + } + } +} \ No newline at end of file diff --git a/GlobalShared/Patches/MyTerminalBlockPatch.cs b/GlobalShared/Patches/MyTerminalBlockPatch.cs new file mode 100644 index 0000000..776485c --- /dev/null +++ b/GlobalShared/Patches/MyTerminalBlockPatch.cs @@ -0,0 +1,24 @@ +using HarmonyLib; +using Sandbox.Game.Entities.Cube; +using VRage.Game; + +namespace Global.Patches +{ + [HarmonyPatch(typeof(MyTerminalBlock))] + public class MyTerminalBlockPatch + { + [HarmonyPatch("HasPlayerAccess")] + [HarmonyPrefix] + public static bool HasPlayerAccessPrefix(bool __result, long identityId, + MyRelationsBetweenPlayerAndBlock defaultNoUser = MyRelationsBetweenPlayerAndBlock.NoOwnership) + { + if (identityId != 0) return true; + + var defaultIsEnemyNeutralOrNoOwnership = defaultNoUser == MyRelationsBetweenPlayerAndBlock.Enemies || + defaultNoUser == MyRelationsBetweenPlayerAndBlock.Neutral || + defaultNoUser == MyRelationsBetweenPlayerAndBlock.NoOwnership; + __result = !defaultIsEnemyNeutralOrNoOwnership; + return false; + } + } +} \ No newline at end of file diff --git a/GlobalShared/Patches/TerminalSystemPatch.cs b/GlobalShared/Patches/TerminalSystemPatch.cs new file mode 100644 index 0000000..480c21f --- /dev/null +++ b/GlobalShared/Patches/TerminalSystemPatch.cs @@ -0,0 +1,31 @@ +using System.Runtime.CompilerServices; +using Global.Shared.Util; +using HarmonyLib; +using Sandbox.Game.GameSystems; + +namespace Global.Shared.Patches +{ + [HarmonyPatch(typeof(MyGridTerminalSystem))] + public class TerminalSystemPatch + { + public static readonly UintCache OwnerCache = new UintCache(337 * 60); + + [HarmonyPrefix] + [HarmonyPatch(nameof(MyGridTerminalSystem.UpdateGridBlocksOwnership))] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UpdateGridBlocksOwnershipPrefix(MyGridTerminalSystem __instance, long ownerID) + { + var key = __instance.GetHashCode() ^ ownerID; + if (OwnerCache.TryGetValue(key, out var value)) + { + if (value == (uint)ownerID) return false; + + OwnerCache.Forget(key); + return true; + } + + OwnerCache.Store(key, (uint)ownerID, 240 + ((uint)key & 63)); + return true; + } + } +} \ No newline at end of file diff --git a/GlobalShared/Patching/PatchHelpers.cs b/GlobalShared/Patching/PatchHelpers.cs new file mode 100644 index 0000000..cb48e01 --- /dev/null +++ b/GlobalShared/Patching/PatchHelpers.cs @@ -0,0 +1,27 @@ +using System; +using System.Reflection; +using Global.Shared.Logging; +using HarmonyLib; + +namespace Global.Shared.Patching +{ + public static class PatchHelpers + { + public static bool PatchAll(IPluginLogger log, Harmony harmony) + { + log.Info("Applying patches..."); + + try + { + harmony.PatchAll(Assembly.GetCallingAssembly()); + } + catch (Exception ex) + { + log.Critical(ex, "Failed to apply patches!"); + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/GlobalShared/Plugin/GlobalInstance.cs b/GlobalShared/Plugin/GlobalInstance.cs new file mode 100644 index 0000000..d8fbacb --- /dev/null +++ b/GlobalShared/Plugin/GlobalInstance.cs @@ -0,0 +1,24 @@ +using System; +using Global.Shared.Config; +using Global.Shared.Logging; + +namespace Global.Shared.Plugin +{ + public static class GlobalInstance + { + public static IPluginLogger Log => Plugin.Log; + public static IGlobalPlugin Plugin { get; private set; } + public static IPluginConfig Config => Plugin.Config; + + public static void SetPlugin(IGlobalPlugin plugin) + { + Plugin = plugin; + GlobalStatic.OcTreeHandler.Init(); + } + + public static void Run(Action action) + { + Plugin.Run(action); + } + } +} \ No newline at end of file diff --git a/GlobalShared/Plugin/GlobalStatic.cs b/GlobalShared/Plugin/GlobalStatic.cs new file mode 100644 index 0000000..0538ceb --- /dev/null +++ b/GlobalShared/Plugin/GlobalStatic.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Global.Shared.OcTree; +using Global.Shared.Patches; +using Sandbox.Game.Entities; +using Sandbox.ModAPI; +using VRage.ModAPI; + +namespace Global.Shared.Plugin +{ + public static class GlobalStatic + { + public static OcTreeHandler OcTreeHandler = new OcTreeHandler(); + + internal static void Init() + { + MyAPIGateway.Entities.OnEntityAdd += Entities_OnEntityAdd; + var set = new HashSet(); + var stopwatch = new Stopwatch(); + stopwatch.Start(); + MyAPIGateway.Entities.GetEntities(set); + foreach (var myEntity in set) Entities_OnEntityAdd(myEntity); + stopwatch.Stop(); + GlobalInstance.Log.Info($"Collected all entities from startup. Took {stopwatch.ElapsedMilliseconds}ms"); + } + + internal static void Update() + { + TerminalSystemPatch.OwnerCache.Cleanup(); + } + + private static void Entities_OnEntityAdd(IMyEntity obj) + { + if (obj is MyCubeGrid grid) + OcTreeHandler.AddGrid(grid); + else if (obj is IMyControllableEntity controllableEntity) + OcTreeHandler.AddControllableEntity(controllableEntity); + } + } +} \ No newline at end of file diff --git a/GlobalShared/Plugin/IGlobalPlugin.cs b/GlobalShared/Plugin/IGlobalPlugin.cs new file mode 100644 index 0000000..077d2ad --- /dev/null +++ b/GlobalShared/Plugin/IGlobalPlugin.cs @@ -0,0 +1,15 @@ +using System; +using Global.Shared.Config; +using Global.Shared.Logging; + +namespace Global.Shared.Plugin +{ + public interface IGlobalPlugin + { + IPluginLogger Log { get; } + IPluginConfig Config { get; } + long Tick { get; } + bool Started { get; } + void Run(Action action); + } +} \ No newline at end of file diff --git a/GlobalShared/Properties/AssemblyInfo.cs b/GlobalShared/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..40de19f --- /dev/null +++ b/GlobalShared/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GlobalShared")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GlobalShared")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("C04593FB-B399-43C1-83C5-E1262A3E0465")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/GlobalShared/Util/KeenExtensions.cs b/GlobalShared/Util/KeenExtensions.cs new file mode 100644 index 0000000..78643ca --- /dev/null +++ b/GlobalShared/Util/KeenExtensions.cs @@ -0,0 +1,19 @@ +using Global.Shared.OcTree; +using VRageMath; + +namespace Global.Shared.Util +{ + public static class KeenExtensions + { + public static BoundingBoxD SetToContainer(this BoundingBoxD box, IContainer container) + { + box.Min.X = container.X; + box.Min.Y = container.Y; + box.Min.Z = container.Z; + box.Max.X = container.X + container.Width; + box.Max.Y = container.Y + container.Height; + box.Max.Z = container.Z + container.Depth; + return box; + } + } +} \ No newline at end of file diff --git a/GlobalShared/Util/NexusUtils.cs b/GlobalShared/Util/NexusUtils.cs new file mode 100644 index 0000000..39e3f7e --- /dev/null +++ b/GlobalShared/Util/NexusUtils.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Nexus.API; +using Nexus.Utilities; +using Sandbox.Engine.Multiplayer; +using Sandbox.ModAPI; +using ServerNetwork.Sync; +using VRageMath; + +namespace Global.Shared.Util +{ + public class NexusUtils + { + public static NexusServerSideAPI nexusServerAPI = new NexusServerSideAPI(1234); + public static NexusAPI nexusAPI = new NexusAPI((ushort)nexusServerAPI.CrossServerModID); + + + public void BroadCastMessage(string author, string message, Color color) + { + var scriptedChatMsg1 = new ScriptedChatMsg + { + Author = author, + Text = message, + Font = "White", + Color = color, + Target = 0 + }; + + var msg = new ScriptedMessage + { + Author = scriptedChatMsg1.Author, + ChatColor = scriptedChatMsg1.Color.GetDrawingColor().ToArgb(), + Target = scriptedChatMsg1.Target, + Text = scriptedChatMsg1.Text + }; + NexusServerSideAPI.SendMessageToAllServers(ref nexusAPI, + MyAPIGateway.Utilities.SerializeToBinary(msg)); + } + } +} diff --git a/GlobalShared/Util/UintCache.cs b/GlobalShared/Util/UintCache.cs new file mode 100644 index 0000000..e01412e --- /dev/null +++ b/GlobalShared/Util/UintCache.cs @@ -0,0 +1,93 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Global.Shared.Plugin; + +namespace Global.Shared.Util +{ + public class UintCache + { + private readonly ConcurrentDictionary cache = new ConcurrentDictionary(); + private readonly ulong cleanupPeriod; + private readonly TK[] keysToDelete; + private readonly uint maxDeleteCount; + private ulong nextCleanup; + private ulong tick; + + public UintCache(uint cleanupPeriod, uint maxDeleteCount = 64) + { + this.cleanupPeriod = (ulong)cleanupPeriod << 32; + this.maxDeleteCount = maxDeleteCount; + + keysToDelete = new TK[this.maxDeleteCount]; + nextCleanup = tick + cleanupPeriod; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Cleanup() + { + if (GlobalInstance.Plugin == null) return; + if ((tick = (ulong)GlobalInstance.Plugin.Tick << 32) < nextCleanup) + return; + + nextCleanup = tick + cleanupPeriod; + + var count = 0u; + foreach (var (key, item) in cache) + { + if (item >= tick) + continue; + + keysToDelete[count++] = key; + if (count == maxDeleteCount) + break; + } + + if (count == 0) + return; + + for (var i = 0; i < count; i++) + cache.Remove(keysToDelete[i]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + cache.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Store(TK key, uint value, uint lifetime) + { + var expires = tick + ((ulong)lifetime << 32); + cache[key] = expires | value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Extend(TK key, uint lifetime) + { + if (cache.TryGetValue(key, out var item)) + cache[key] = (tick + ((ulong)lifetime << 32)) | (item & 0xfffffffful); + } + + public void Forget(TK key) + { + cache.Remove(key); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetValue(TK key, out uint value) + { + if (cache.TryGetValue(key, out var item)) + if (item >= tick) + { + value = (uint)item; + return true; + } + + value = 0u; + return false; + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Discord/DiscordStructs.cs b/GlobalTorch/API/Discord/DiscordStructs.cs new file mode 100644 index 0000000..7775b6b --- /dev/null +++ b/GlobalTorch/API/Discord/DiscordStructs.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using Newtonsoft.Json; + +namespace Global.API.Discord +{ + /// + /// Discord message data object + /// + public struct DiscordMessage + { + /// + /// Message content + /// + public string Content; + + /// + /// Read message to everyone on the channel + /// + public bool TTS; + + public DiscordAllowedMentions AllowedMentions; + + /// + /// Webhook profile username to be shown + /// + public string Username; + + /// + /// Webhook profile avater to be shown + /// + public string AvatarUrl; + + /// + /// List of embeds + /// + public List Embeds; + + public override string ToString() + { + return DiscordUtil.StructToJson(this).ToString(Formatting.None); + } + } + + public struct DiscordAllowedMentions + { + public List Parse; + public List Roles; + public List Users; + + public override string ToString() + { + return DiscordUtil.StructToJson(this).ToString(Formatting.None); + } + } + + /// + /// Discord embed data object + /// + public struct DiscordEmbed + { + /// + /// Embed title + /// + public string Title; + + /// + /// Embed description + /// + public string Description; + + /// + /// Embed url + /// + public string Url; + + /// + /// Embed timestamp + /// + public DateTime? Timestamp; + + /// + /// Embed color + /// + public Color? Color; + + /// + /// Embed footer + /// + public EmbedFooter? Footer; + + /// + /// Embed image + /// + public EmbedMedia? Image; + + /// + /// Embed thumbnail + /// + public EmbedMedia? Thumbnail; + + /// + /// Embed video + /// + public EmbedMedia? Video; + + /// + /// Embed provider + /// + public EmbedProvider? Provider; + + /// + /// Embed author + /// + public EmbedAuthor? Author; + + /// + /// Embed fields list + /// + public List Fields; + + public override string ToString() + { + return DiscordUtil.StructToJson(this).ToString(Formatting.None); + } + } + + /// + /// Discord embed footer data object + /// + public struct EmbedFooter + { + /// + /// Footer text + /// + public string Text; + + /// + /// Footer icon + /// + public string IconUrl; + + /// + /// Footer icon proxy + /// + public string ProxyIconUrl; + + public override string ToString() + { + return DiscordUtil.StructToJson(this).ToString(Formatting.None); + } + } + + /// + /// Discord embed media data object (images/thumbs/videos) + /// + public struct EmbedMedia + { + /// + /// Media url + /// + public string Url; + + /// + /// Media proxy url + /// + public string ProxyUrl; + + /// + /// Media height + /// + public int? Height; + + /// + /// Media width + /// + public int? Width; + + public override string ToString() + { + return DiscordUtil.StructToJson(this).ToString(Formatting.None); + } + } + + /// + /// Discord embed provider data object + /// + public struct EmbedProvider + { + /// + /// Provider name + /// + public string Name; + + /// + /// Provider url + /// + public string Url; + + public override string ToString() + { + return DiscordUtil.StructToJson(this).ToString(Formatting.None); + } + } + + /// + /// Discord embed author data object + /// + public struct EmbedAuthor + { + /// + /// Author name + /// + public string Name; + + /// + /// Author url + /// + public string Url; + + /// + /// Author icon + /// + public string IconUrl; + + /// + /// Author icon proxy + /// + public string ProxyIconUrl; + + public override string ToString() + { + return DiscordUtil.StructToJson(this).ToString(Formatting.None); + } + } + + /// + /// Discord embed field data object + /// + public struct EmbedField + { + /// + /// Field name + /// + public string Name; + + /// + /// Field value + /// + public string Value; + + /// + /// Field align + /// + public bool InLine; + + public override string ToString() + { + return DiscordUtil.StructToJson(this).ToString(Formatting.None); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Discord/DiscordUtil.cs b/GlobalTorch/API/Discord/DiscordUtil.cs new file mode 100644 index 0000000..13a0923 --- /dev/null +++ b/GlobalTorch/API/Discord/DiscordUtil.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using Newtonsoft.Json.Linq; + +namespace Global.API.Discord +{ + public static class DiscordUtil + { + private static readonly string[] ignore = { "InLine" }; + + /// + /// Convert Color object into hex integer + /// + /// Color to be converted + /// Converted hex integer + public static int ColorToHex(Color color) + { + var HS = + color.R.ToString("X2") + + color.G.ToString("X2") + + color.B.ToString("X2"); + + return int.Parse(HS, NumberStyles.HexNumber); + } + + internal static JObject StructToJson(object @struct) + { + var type = @struct.GetType(); + var json = new JObject(); + + var fields = type.GetFields(); + foreach (var field in fields) + { + var name = FieldNameToJsonName(field.Name); + var value = field.GetValue(@struct); + if (value == null) + continue; + + if (value is bool b) + { + json.Add(name, b); + } + else if (value is int i) + { + json.Add(name, i); + } + else if (value is Color color) + { + json.Add(name, ColorToHex(color)); + } + else if (value is string s) + { + json.Add(name, s); + } + else if (value is DateTime time) + { + json.Add(name, time.ToString("yyyy-MM-ddTHH\\:mm\\:ss.fffffffzzz")); + } + else if (value is IList list && list.GetType().IsGenericType) + { + var array = new JArray(); + foreach (var obj in list) + if (obj is string str) + array.Add(str); + else + array.Add(StructToJson(obj)); + + json.Add(name, array); + } + else + { + json.Add(name, StructToJson(value)); + } + } + + return json; + } + + internal static string FieldNameToJsonName(string name) + { + if (ignore.ToList().Contains(name)) + return name.ToLower(); + + var result = new List(); + + if (IsFullUpper(name)) + result.AddRange(name.ToLower().ToCharArray()); + else + for (var i = 0; i < name.Length; i++) + if (i > 0 && char.IsUpper(name[i])) + result.AddRange(new[] { '_', char.ToLower(name[i]) }); + else result.Add(char.ToLower(name[i])); + + return string.Join("", result); + } + + internal static bool IsFullUpper(string str) + { + var upper = true; + for (var i = 0; i < str.Length; i++) + if (!char.IsUpper(str[i])) + { + upper = false; + break; + } + + return upper; + } + + public static string Decode(Stream source) + { + using (var reader = new StreamReader(source)) + { + return reader.ReadToEnd(); + } + } + + public static byte[] Encode(string source, string encoding = "utf-8") + { + return Encoding.GetEncoding(encoding).GetBytes(source); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Discord/DiscordWebhook.cs b/GlobalTorch/API/Discord/DiscordWebhook.cs new file mode 100644 index 0000000..6ce3df7 --- /dev/null +++ b/GlobalTorch/API/Discord/DiscordWebhook.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Net; + +namespace Global.API.Discord +{ + public class DiscordWebhook + { + /// + /// Webhook url + /// + public string Url { get; set; } + + private void AddField(MemoryStream stream, string bound, string cDisposition, string cType, byte[] data) + { + var prefix = stream.Length > 0 ? "\r\n--" : "--"; + var fBegin = $"{prefix}{bound}\r\n"; + + var fBeginBuffer = DiscordUtil.Encode(fBegin); + var cDispositionBuffer = DiscordUtil.Encode(cDisposition); + var cTypeBuffer = DiscordUtil.Encode(cType); + + stream.Write(fBeginBuffer, 0, fBeginBuffer.Length); + stream.Write(cDispositionBuffer, 0, cDispositionBuffer.Length); + stream.Write(cTypeBuffer, 0, cTypeBuffer.Length); + stream.Write(data, 0, data.Length); + } + + private void SetJsonPayload(MemoryStream stream, string bound, string json) + { + const string cDisposition = "Content-Disposition: form-data; name=\"payload_json\"\r\n"; + const string cType = "Content-Type: application/octet-stream\r\n\r\n"; + AddField(stream, bound, cDisposition, cType, DiscordUtil.Encode(json)); + } + + private void SetFile(MemoryStream stream, string bound, int index, FileInfo file) + { + var cDisposition = $"Content-Disposition: form-data; name=\"file_{index}\"; filename=\"{file.Name}\"\r\n"; + const string cType = "Content-Type: application/octet-stream\r\n\r\n"; + AddField(stream, bound, cDisposition, cType, File.ReadAllBytes(file.FullName)); + } + + /// + /// Send webhook message + /// + public void Send(DiscordMessage message, params FileInfo[] files) + { + if (string.IsNullOrEmpty(Url)) + throw new ArgumentNullException($"Invalid Webhook URL {Url}."); + + var bound = "------------------------" + DateTime.Now.Ticks.ToString("x"); + var webhookRequest = new WebClient(); + webhookRequest.Headers.Add("Content-Type", "multipart/form-data; boundary=" + bound); + + var stream = new MemoryStream(); + for (var i = 0; i < files.Length; i++) + SetFile(stream, bound, i, files[i]); + + var json = message.ToString(); + SetJsonPayload(stream, bound, json); + + var bodyEnd = DiscordUtil.Encode($"\r\n--{bound}--"); + stream.Write(bodyEnd, 0, bodyEnd.Length); + + try + { + webhookRequest.UploadData(Url, stream.ToArray()); + } + catch (WebException ex) + { + throw new WebException(DiscordUtil.Decode(ex.Response.GetResponseStream())); + } + + stream.Dispose(); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/EventResult.cs b/GlobalTorch/API/EventResult.cs new file mode 100644 index 0000000..eea428e --- /dev/null +++ b/GlobalTorch/API/EventResult.cs @@ -0,0 +1,9 @@ +namespace Global.API +{ + public enum EventResult + { + Allow, + Continue, + Deny + } +} \ No newline at end of file diff --git a/GlobalTorch/API/GlobalModApi.cs b/GlobalTorch/API/GlobalModApi.cs new file mode 100644 index 0000000..7ad751a --- /dev/null +++ b/GlobalTorch/API/GlobalModApi.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using Sandbox.ModAPI; +using VRage.Game.ModAPI; +using VRage.ObjectBuilders; + +namespace Global.API +{ + public class GlobalModApi + { + public static bool IsRunningGlobal() + { + return false; + } + + public static void GetAllBlocksOfType(List list, IMyCubeGrid grid, MyObjectBuilderType type) + { + list.AddRange(grid.GetFatBlocks().Where(block => block.BlockDefinition.TypeId == type)); + } + + public static void GetAllBlocksOfTypeId(List list, long gridId, MyObjectBuilderType type) + { + var grid = MyAPIGateway.Entities.GetEntityById(gridId) as IMyCubeGrid; + if (grid == null) return; + list.AddRange(grid.GetFatBlocks().Where(block => block.BlockDefinition.TypeId == type)); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/GlobalServerModApi.cs b/GlobalTorch/API/GlobalServerModApi.cs new file mode 100644 index 0000000..320b57d --- /dev/null +++ b/GlobalTorch/API/GlobalServerModApi.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Global.Shared.API; +using VRage.Game.ModAPI; +using VRage.ObjectBuilders; + +namespace Global.API +{ + public class GlobalServerModApi + { + public static bool IsRunningGlobal(ref bool __result) + { + __result = true; + return false; + } + + public static bool GetAllBlocksOfType(List list, IMyCubeGrid grid, MyObjectBuilderType type) + { + GetAllBlocksOfTypeId(list, grid.EntityId, type); + return false; + } + + public static bool GetAllBlocksOfTypeId(List list, long gridId, MyObjectBuilderType type) + { + list.AddRange(GlobalAPI.GetGridById(gridId).GetBlocks(type)); + return false; + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Libraries/SENetworkAPI/Client.cs b/GlobalTorch/API/Libraries/SENetworkAPI/Client.cs new file mode 100644 index 0000000..17f82a3 --- /dev/null +++ b/GlobalTorch/API/Libraries/SENetworkAPI/Client.cs @@ -0,0 +1,104 @@ +using System; +using Sandbox.ModAPI; +using VRage; +using VRage.Utils; +using VRageMath; + +namespace SENetworkAPI +{ + public class Client : NetworkAPI + { + /// + /// Handles communication with the server + /// + /// Identifies the channel to pass information to and from this mod + /// identifies what chat entries should be captured and sent to the server + public Client(ushort comId, string modName, string keyword = null) : base(comId, modName, keyword) + { + } + + /// + /// Sends a command packet to the server + /// + /// The command to be executed + /// Text that will be displayed in client chat + /// A serialized object to be sent across the network + /// The date timestamp this command was sent + /// The client reciving this packet (if 0 it sends to all clients) + /// Enture delivery of the packet + public override void SendCommand(string commandString, string message = null, byte[] data = null, + DateTime? sent = null, ulong steamId = ulong.MinValue, bool isReliable = true) + { + if (MyAPIGateway.Session?.Player != null) + SendCommand( + new Command + { + CommandString = commandString, Message = message, Data = data, + Timestamp = sent == null ? DateTime.UtcNow.Ticks : sent.Value.Ticks, + SteamId = MyAPIGateway.Session.Player.SteamUserId + }, MyAPIGateway.Session.Player.SteamUserId, isReliable); + else + MyLog.Default.Warning($"[NetworkAPI] ComID: {ComId} | Failed to send command. Session does not exist."); + } + + /// + /// Sends a command packet to the server + /// + /// The object to be sent to the client + /// The users steam ID + /// Makes sure the message is recieved by the server + internal override void SendCommand(Command cmd, ulong steamId = ulong.MinValue, bool isReliable = true) + { + if (cmd.Data != null && cmd.Data.Length > CompressionThreshold) + { + cmd.Data = MyCompression.Compress(cmd.Data); + cmd.IsCompressed = true; + } + + cmd.Timestamp = DateTime.UtcNow.Ticks; + var packet = MyAPIGateway.Utilities.SerializeToBinary(cmd); + + if (LogNetworkTraffic) + MyLog.Default.Info( + $"[NetworkAPI] TRANSMITTING Bytes: {packet.Length} Command: {cmd.CommandString} User: {steamId}"); + + MyAPIGateway.Multiplayer.SendMessageToServer(ComId, packet, isReliable); + } + + /// + /// Sends a command packet to the server + /// + /// The command to be executed + /// Client side send to server this is not used + /// Client side send to server this is not used + /// Text that will be displayed in client chat + /// A serialized object to be sent across the network + /// The date timestamp this command was sent + /// The client reciving this packet (if 0 it sends to all clients) + /// Enture delivery of the packet + public override void SendCommand(string commandString, Vector3D point, double radius = 0, string message = null, + byte[] data = null, DateTime? sent = null, ulong steamId = 0, bool isReliable = true) + { + SendCommand(commandString, message, data, sent, steamId, isReliable); + } + + /// + /// Sends a command packet to the server + /// + /// The object to be sent to the client + /// Client side send to server this is not used + /// Client side send to server this is not used + /// The users steam ID + /// Makes sure the message is recieved by the server + internal override void SendCommand(Command cmd, Vector3D point, double radius = 0, ulong steamId = 0, + bool isReliable = true) + { + SendCommand(cmd, steamId, isReliable); + } + + public override void Say(string message) + { + SendCommand(null, message); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Libraries/SENetworkAPI/Command.cs b/GlobalTorch/API/Libraries/SENetworkAPI/Command.cs new file mode 100644 index 0000000..383e304 --- /dev/null +++ b/GlobalTorch/API/Libraries/SENetworkAPI/Command.cs @@ -0,0 +1,22 @@ +using ProtoBuf; + +namespace SENetworkAPI +{ + [ProtoContract] + internal class Command + { + [ProtoMember(1)] public ulong SteamId { get; set; } + + [ProtoMember(2)] public string CommandString { get; set; } + + [ProtoMember(3)] public string Message { get; set; } + + [ProtoMember(4)] public byte[] Data { get; set; } + + [ProtoMember(5)] public long Timestamp { get; set; } + + [ProtoMember(6)] public bool IsProperty { get; set; } + + [ProtoMember(7)] public bool IsCompressed { get; set; } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Libraries/SENetworkAPI/NetSync.cs b/GlobalTorch/API/Libraries/SENetworkAPI/NetSync.cs new file mode 100644 index 0000000..a9ef276 --- /dev/null +++ b/GlobalTorch/API/Libraries/SENetworkAPI/NetSync.cs @@ -0,0 +1,498 @@ +using System; +using System.Collections.Generic; +using ProtoBuf; +using Sandbox.Game.Entities; +using Sandbox.ModAPI; +using VRage.Game; +using VRage.Game.Components; +using VRage.Game.Entity; +using VRage.ModAPI; +using VRage.Utils; + +namespace SENetworkAPI +{ + public enum TransferType + { + ServerToClient, + ClientToServer, + Both + } + + public enum SyncType + { + Post, + Fetch, + Broadcast, + None + } + + [ProtoContract] + internal class SyncData + { + [ProtoMember(3)] public byte[] Data; + + [ProtoMember(2)] public long EntityId; + + [ProtoMember(1)] public long Id; + + [ProtoMember(4)] public SyncType SyncType; + } + + public abstract class NetSync + { + internal static Dictionary> PropertiesByEntity = + new Dictionary>(); + + internal static Dictionary PropertyById = new Dictionary(); + + internal static object locker = new object(); + internal static long generatorId = 1; + + /// + /// Triggers after recieving a fetch request from clients + /// and allows you to modify this property before it is sent. + /// + public Action BeforeFetchRequestResponse; + + /// + /// The allowed network communication direction + /// + public TransferType TransferType { get; internal set; } + + /// + /// The identity of this property + /// + public long Id { get; internal set; } + + /// + /// Enables/Disables network traffic out when setting a value + /// + public bool SyncOnLoad { get; internal set; } + + /// + /// Limits sync updates to within sync distance + /// + public bool LimitToSyncDistance { get; internal set; } + + /// + /// the last recorded network traffic + /// + public long LastMessageTimestamp { get; internal set; } + + internal static long GeneratePropertyId() + { + return generatorId++; + } + + /// + /// Request the lastest value from the server + /// + public abstract void Fetch(); + + internal abstract void Push(SyncType type, ulong sendTo); + + internal abstract void SetNetworkValue(byte[] data, ulong sender); + } + + public class NetSync : NetSync + { + private readonly string sessionName; + private T _value; + private MyEntity Entity; + + /// + /// Fires each time the value is changed + /// Provides the old value and the new value + /// + public Action ValueChanged; + + /// + /// Fires only when the a network call is made + /// Provides the old value and the new value + /// also provides the steamId + /// + public Action ValueChangedByNetwork; + + /// IMyEntity object this property is attached to + /// + /// Sets an initial value + /// automatically syncs data to clients when the class initializes + /// marking this true only sends data to clients within sync distance + public NetSync(IMyEntity entity, TransferType transferType, T startingValue = default, bool syncOnLoad = true, + bool limitToSyncDistance = true) + { + if (entity == null) + throw new Exception("[NetworkAPI] Attemped to create a NetSync property. MyEntity was null."); + + Init(entity as MyEntity, transferType, startingValue, syncOnLoad, limitToSyncDistance); + } + + /// MyEntity object this property is attached to + /// + /// Sets an initial value + /// automatically syncs data to clients when the class initializes + /// marking this true only sends data to clients within sync distance + public NetSync(MyEntity entity, TransferType transferType, T startingValue = default, bool syncOnLoad = true, + bool limitToSyncDistance = true) + { + if (entity == null) + throw new Exception("[NetworkAPI] Attemped to create a NetSync property. MyEntity was null."); + + Init(entity, transferType, startingValue, syncOnLoad, limitToSyncDistance); + } + + /// MyGameLogicComponent object this property is attached to + /// + /// Sets an initial value + /// automatically syncs data to clients when the class initializes + /// marking this true only sends data to clients within sync distance + public NetSync(MyGameLogicComponent logic, TransferType transferType, T startingValue = default, + bool syncOnLoad = true, bool limitToSyncDistance = true) + { + if (logic?.Entity == null) + throw new Exception( + "[NetworkAPI] Attemped to create a NetSync property. MyGameLogicComponent was null."); + + Init(logic.Entity as MyEntity, transferType, startingValue, syncOnLoad, limitToSyncDistance); + } + + /// MySessionComponentBase object this property is attached to + /// + /// Sets an initial value + /// automatically syncs data to clients when the class initializes + /// marking this true only sends data to clients within sync distance + public NetSync(MySessionComponentBase logic, TransferType transferType, T startingValue = default, + bool syncOnLoad = true, bool limitToSyncDistance = true) + { + if (logic == null) + throw new Exception( + "[NetworkAPI] Attemped to create a NetSync property. MySessionComponentBase was null."); + + sessionName = logic.GetType().Name; + Init(null, transferType, startingValue, syncOnLoad, limitToSyncDistance); + } + + /// + /// this property syncs across the network when changed + /// + public T Value + { + get => _value; + set => SetValue(value, SyncType.Broadcast); + } + + /// + /// This funtion is called by the constructer + /// + /// + /// Sets an initial value + /// automatically syncs data to clients when the class initializes + /// marking this true only sends data to clients within sync distance + private void Init(MyEntity entity, TransferType transferType, T startingValue = default, bool syncOnLoad = true, + bool limitToSyncDistance = true) + { + TransferType = transferType; + _value = startingValue; + SyncOnLoad = syncOnLoad; + LimitToSyncDistance = limitToSyncDistance; + + if (entity != null) + { + Entity = entity; + Entity.OnClose += Entity_OnClose; + + if (PropertiesByEntity.ContainsKey(Entity)) + { + PropertiesByEntity[Entity].Add(this); + Id = PropertiesByEntity[Entity].Count - 1; + } + else + { + PropertiesByEntity.Add(Entity, new List { this }); + Id = 0; + } + } + else + { + lock (locker) + { + Id = GeneratePropertyId(); + PropertyById.Add(Id, this); + } + } + + if (SyncOnLoad) + { + if (Entity != null) + Entity.AddedToScene += SyncOnAddedToScene; + else + Fetch(); + } + + if (NetworkAPI.LogNetworkTraffic) + MyLog.Default.Info( + $"[NetworkAPI] Property Created: {Descriptor()}, Transfer: {transferType}, SyncOnLoad: {SyncOnLoad}"); + } + + private void SyncOnAddedToScene(MyEntity e) + { + if (Entity != e) + return; + + Fetch(); + Entity.AddedToScene -= SyncOnAddedToScene; + } + + private void Entity_OnClose(MyEntity entity) + { + PropertyById.Remove(Id); + } + + /// + /// Allows you to change how syncing works when setting the value this way + /// + public void SetValue(T val, SyncType syncType = SyncType.None) + { + var oldval = _value; + lock (_value) + { + _value = val; + } + + SendValue(syncType); + ValueChanged?.Invoke(oldval, val); + } + + /// + /// Sets the data received over the network + /// + internal override void SetNetworkValue(byte[] data, ulong sender) + { + try + { + var oldval = _value; + lock (_value) + { + _value = MyAPIGateway.Utilities.SerializeFromBinary(data); + + if (NetworkAPI.LogNetworkTraffic) + MyLog.Default.Info($"[NetworkAPI] {Descriptor()} New value: {oldval} --- Old value: {_value}"); + } + + if (MyAPIGateway.Multiplayer.IsServer) SendValue(); + + ValueChanged?.Invoke(oldval, _value); + ValueChangedByNetwork?.Invoke(oldval, _value, sender); + } + catch (Exception e) + { + MyLog.Default.Error($"[NetworkAPI] Failed to deserialize network property data\n{e}"); + } + } + + /// + /// sends the value across the network + /// + private void SendValue(SyncType syncType = SyncType.Broadcast, ulong sendTo = ulong.MinValue) + { + try + { + if (!NetworkAPI.IsInitialized) + { + MyLog.Default.Error( + "[NetworkAPI] _ERROR_ The NetworkAPI has not been initialized. Use NetworkAPI.Init() to initialize it."); + return; + } + + if (syncType == SyncType.None) + { + if (NetworkAPI.LogNetworkTraffic) + MyLog.Default.Info($"[NetworkAPI] _INTERNAL_ {Descriptor()} Wont send value: {Value}"); + + return; + } + + if ((syncType != SyncType.Fetch && TransferType == TransferType.ServerToClient && + !MyAPIGateway.Multiplayer.IsServer) || + (TransferType == TransferType.ClientToServer && MyAPIGateway.Multiplayer.IsServer)) + { + if (NetworkAPI.LogNetworkTraffic) + MyLog.Default.Info( + $"[NetworkAPI] {Descriptor()} Bad send direction transfer type is {TransferType}"); + + return; + } + + if (MyAPIGateway.Session.OnlineMode == MyOnlineModeEnum.OFFLINE) + { + if (NetworkAPI.LogNetworkTraffic) + MyLog.Default.Info($"[NetworkAPI] _OFFLINE_ {Descriptor()} Wont send value: {Value}"); + + return; + } + + if (Value == null) + { + if (NetworkAPI.LogNetworkTraffic) + MyLog.Default.Error( + $"[NetworkAPI] _ERROR_ {Descriptor()} Value is null. Cannot transmit null value."); + + return; + } + + var data = new SyncData + { + Id = Id, + EntityId = Entity != null ? Entity.EntityId : 0, + Data = MyAPIGateway.Utilities.SerializeToBinary(_value), + SyncType = syncType + }; + + var id = ulong.MinValue; + if (MyAPIGateway.Session?.LocalHumanPlayer != null) + id = MyAPIGateway.Session.LocalHumanPlayer.SteamUserId; + + if (id == sendTo && id != ulong.MinValue) + MyLog.Default.Error( + $"[NetworkAPI] _ERROR_ {Descriptor()} The sender id is the same as the recievers id. data will not be sent."); + + if (NetworkAPI.LogNetworkTraffic) + MyLog.Default.Info( + $"[NetworkAPI] _TRANSMITTING_ {Descriptor()} - Id:{data.Id}, EId:{data.EntityId}, {data.SyncType}, {(data.SyncType == SyncType.Fetch ? "" : $"Val:{_value}")}"); + + if (LimitToSyncDistance && Entity != null) + NetworkAPI.Instance.SendCommand( + new Command + { + IsProperty = true, Data = MyAPIGateway.Utilities.SerializeToBinary(data), SteamId = id + }, Entity.PositionComp.GetPosition(), steamId: sendTo); + else + NetworkAPI.Instance.SendCommand( + new Command + { + IsProperty = true, Data = MyAPIGateway.Utilities.SerializeToBinary(data), SteamId = id + }, sendTo); + } + catch (Exception e) + { + MyLog.Default.Error($"[NetworkAPI] _ERROR_ SendValue(): Problem syncing value: {e}"); + } + } + + /// + /// Receives and redirects all property traffic + /// + /// this hold the path to the property and the data to sync + internal static void RouteMessage(SyncData pack, ulong sender, long timestamp) + { + if (pack == null) + { + MyLog.Default.Error("[NetworkAPI] Property data is null"); + return; + } + + if (NetworkAPI.LogNetworkTraffic) + MyLog.Default.Info($"[NetworkAPI] Id:{pack.Id}, EId:{pack.EntityId}, {pack.SyncType}"); + + NetSync property; + if (pack.EntityId == 0) + { + if (!PropertyById.ContainsKey(pack.Id)) + { + MyLog.Default.Info("[NetworkAPI] id not registered in dictionary 'PropertyById'"); + return; + } + + property = PropertyById[pack.Id]; + } + else + { + var entity = (MyEntity)MyAPIGateway.Entities.GetEntityById(pack.EntityId); + + if (entity == null) + { + MyLog.Default.Info("[NetworkAPI] Failed to get entity by id"); + return; + } + + if (!PropertiesByEntity.ContainsKey(entity)) + { + MyLog.Default.Info("[NetworkAPI] Entity not registered in dictionary 'PropertiesByEntity'"); + return; + } + + var properties = PropertiesByEntity[entity]; + + if (pack.Id >= properties.Count) + { + MyLog.Default.Info("[NetworkAPI] property index out of range"); + return; + } + + property = properties[(int)pack.Id]; + } + + property.LastMessageTimestamp = timestamp; + if (pack.SyncType == SyncType.Fetch) + { + property.BeforeFetchRequestResponse?.Invoke(sender); + property.Push(SyncType.Post, sender); + } + else + { + property.SetNetworkValue(pack.Data, sender); + } + } + + /// + /// Request the lastest value from the server + /// Servers are not allowed to fetch from clients + /// + public override void Fetch() + { + if (!MyAPIGateway.Multiplayer.IsServer) SendValue(SyncType.Fetch); + } + + /// + /// Send data now + /// + public void Push() + { + SendValue(); + } + + /// + /// Send data to single user + /// + public void Push(ulong sendTo) + { + SendValue(SyncType.Post, sendTo); + } + + /// + /// Send data across the network now + /// + internal override void Push(SyncType type, ulong sendTo = ulong.MinValue) + { + SendValue(type, sendTo); + } + + /// + /// Identifier for logging readability + /// + internal string Descriptor() + { + if (Entity != null) + { + if (Entity is MyCubeBlock) + return + $"<{(Entity as MyCubeBlock).CubeGrid.DisplayName}_{(Entity.DefinitionId?.SubtypeId == null ? Entity.GetType().Name : Entity.DefinitionId?.SubtypeId.ToString())}.{Entity.EntityId}_{typeof(T).Name}.{Id}>"; + + return + $"<{(Entity.DefinitionId?.SubtypeId == null ? Entity.GetType().Name : Entity.DefinitionId?.SubtypeId.ToString())}.{Entity.EntityId}_{typeof(T).Name}.{Id}>"; + } + + return $"<{sessionName}_{typeof(T).Name}.{Id}>"; + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Libraries/SENetworkAPI/Network.cs b/GlobalTorch/API/Libraries/SENetworkAPI/Network.cs new file mode 100644 index 0000000..b0b9c65 --- /dev/null +++ b/GlobalTorch/API/Libraries/SENetworkAPI/Network.cs @@ -0,0 +1,323 @@ +using System; +using System.Collections.Generic; +using Sandbox.ModAPI; +using VRage; +using VRage.Utils; +using VRageMath; + +namespace SENetworkAPI +{ + public enum NetworkTypes + { + Dedicated, + Server, + Client + } + + public abstract class NetworkAPI + { + public const int CompressionThreshold = 100000; + public static NetworkAPI Instance; + public static bool LogNetworkTraffic = false; + + /// + /// Gets the diffrence between now and a given timestamp in frames (60 fps) + /// + /// + /// + private static readonly double frames = 1000d / 60d; + + public readonly ushort ComId; + public readonly string Keyword; + public readonly string ModName; + internal Dictionary> ChatCommands = new Dictionary>(); + + internal Dictionary> NetworkCommands = + new Dictionary>(); + + /// + /// Event driven client, server syncing API. + /// + /// The communication channel this mod will listen on + /// The title use for displaying chat messages + /// The string identifying a chat command + public NetworkAPI(ushort comId, string modName, string keyword = null) + { + ComId = comId; + ModName = modName == null ? string.Empty : modName; + Keyword = keyword != null ? keyword.ToLower() : null; + + if (UsingTextCommands) + { + MyAPIGateway.Utilities.MessageEntered -= HandleChatInput; + MyAPIGateway.Utilities.MessageEntered += HandleChatInput; + } + + MyAPIGateway.Multiplayer.UnregisterMessageHandler(ComId, HandleIncomingPacket); + MyAPIGateway.Multiplayer.RegisterMessageHandler(ComId, HandleIncomingPacket); + + MyLog.Default.Info( + $"[NetworkAPI] Initialized. Type: {GetType().Name} ComId: {ComId} Name: {ModName} Keyword: {Keyword}"); + } + + public static bool IsInitialized => Instance != null; + + internal bool UsingTextCommands => Keyword != null; + + /// + /// Event triggers apon reciveing data over the network + /// steamId, command, data + /// + public event Action OnCommandRecived; + + /// + /// Invokes chat command events + /// + /// Chat message string + /// should be shown normally in global chat + private void HandleChatInput(string messageText, ref bool sendToOthers) + { + var args = messageText.ToLower().Split(' '); + if (args[0] != Keyword) + return; + sendToOthers = false; + + var arguments = messageText.Substring(Keyword.Length).Trim(' '); + + // Meh... this is kinda yucky + if (args.Length == 1 && ChatCommands.ContainsKey(string.Empty)) + { + ChatCommands[string.Empty]?.Invoke(string.Empty); + } + else if (args.Length > 1 && ChatCommands.ContainsKey(args[1])) + { + ChatCommands[args[1]]?.Invoke(arguments.Substring(args[1].Length).Trim(' ')); + } + else + { + if (!MyAPIGateway.Utilities.IsDedicated) + MyAPIGateway.Utilities.ShowMessage(ModName, "Command not recognized."); + } + } + + /// + /// Unpacks commands and handles arguments + /// + /// Data chunck recived from the network + private void HandleIncomingPacket(byte[] msg) + { + try + { + var cmd = MyAPIGateway.Utilities.SerializeFromBinary(msg); + + if (LogNetworkTraffic) + { + MyLog.Default.Info("[NetworkAPI] ----- TRANSMISSION RECIEVED -----"); + MyLog.Default.Info( + $"[NetworkAPI] Type: {(cmd.IsProperty ? "Property" : $"Command ID: {cmd.CommandString}")}, {(cmd.IsCompressed ? "Compressed, " : "")}From: {cmd.SteamId} "); + } + + if (cmd.IsCompressed) + { + cmd.Data = MyCompression.Decompress(cmd.Data); + cmd.IsCompressed = false; + } + + if (cmd.IsProperty) + { + NetSync.RouteMessage(MyAPIGateway.Utilities.SerializeFromBinary(cmd.Data), + cmd.SteamId, cmd.Timestamp); + } + else + { + if (!string.IsNullOrWhiteSpace(cmd.Message)) + { + if (!MyAPIGateway.Utilities.IsDedicated) + if (MyAPIGateway.Session != null) + MyAPIGateway.Utilities.ShowMessage(ModName, cmd.Message); + + if (MyAPIGateway.Multiplayer.IsServer) SendCommand(null, cmd.Message); + } + + if (cmd.CommandString != null) + { + OnCommandRecived?.Invoke(cmd.SteamId, cmd.CommandString, cmd.Data, new DateTime(cmd.Timestamp)); + + var command = cmd.CommandString.Split(' ')[0]; + + if (NetworkCommands.ContainsKey(command)) + NetworkCommands[command]?.Invoke(cmd.SteamId, cmd.CommandString, cmd.Data, + new DateTime(cmd.Timestamp)); + } + } + + if (LogNetworkTraffic) MyLog.Default.Info("[NetworkAPI] ----- END -----"); + } + catch (Exception e) + { + MyLog.Default.Error($"[NetworkAPI] Failure in message processing:\n{e}"); + } + } + + /// + /// Registers a callback that will fire when the command string is sent + /// + /// The command that triggers the callback + /// The function that runs when a command is recived + public void RegisterNetworkCommand(string command, Action callback) + { + if (command == null) + throw new Exception( + "[NetworkAPI] Cannot register a command using null. null is reserved for chat messages."); + + command = command.ToLower(); + + if (NetworkCommands.ContainsKey(command)) + throw new Exception( + $"[NetworkAPI] Failed to add the network command callback '{command}'. A command with the same name was already added."); + + NetworkCommands.Add(command, callback); + } + + /// + /// Unregisters a command + /// + /// + public void UnregisterNetworkCommand(string command) + { + if (NetworkCommands.ContainsKey(command)) NetworkCommands.Remove(command); + } + + /// + /// will trigger when you type + /// + /// + /// + /// this is the text command that will be typed into chat + /// this is the function that will be called when the keyword is typed + public void RegisterChatCommand(string command, Action callback) + { + if (command == null) command = string.Empty; + + command = command.ToLower(); + + if (ChatCommands.ContainsKey(command)) + throw new Exception( + $"[NetworkAPI] Failed to add the network command callback '{command}'. A command with the same name was already added."); + + ChatCommands.Add(command, callback); + } + + /// + /// Unregisters a chat command + /// + /// the chat command to unregister + public void UnregisterChatCommand(string command) + { + if (ChatCommands.ContainsKey(command)) ChatCommands.Remove(command); + } + + /// + /// Sends a command packet across the network + /// + /// The command word and any arguments delimidated with spaces + /// Text to be writen in chat + /// A serialized object used to send game information + /// The date timestamp this command was sent + /// A players steam id + /// Makes sure the data gets to the target + public abstract void SendCommand(string commandString, string message = null, byte[] data = null, + DateTime? sent = null, ulong steamId = ulong.MinValue, bool isReliable = true); + + /// + /// Sends a command packet across the network + /// + /// The command word and any arguments delimidated with spaces + /// + /// + /// Text to be writen in chat + /// A serialized object used to send game information + /// The date timestamp this command was sent + /// A players steam id + /// Makes sure the data gets to the target + public abstract void SendCommand(string commandString, Vector3D point, double radius = 0, string message = null, + byte[] data = null, DateTime? sent = null, ulong steamId = ulong.MinValue, bool isReliable = true); + + /// + /// Sends a command packet to the server / client + /// + /// The object to be sent across the network + /// the id of the user this is being sent to. 0 sends it to all users in range + /// make sure the packet reaches its destination + internal abstract void SendCommand(Command cmd, ulong steamId = ulong.MinValue, bool isReliable = true); + + + /// + /// Sends a command packet to the server / client if in range + /// + /// The object to be sent across the network + /// the center of the sending sphere + /// the radius of the sending sphere + /// the id of the user this is being sent to. 0 sends it to all users in range + /// make sure the packet reaches its destination + internal abstract void SendCommand(Command cmd, Vector3D point, double range = 0, + ulong steamId = ulong.MinValue, bool isReliable = true); + + /// + /// Posts text into the ingame chat. + /// + /// + public abstract void Say(string message); + + /// + /// Unregisters listeners + /// + [ObsoleteAttribute("This property is obsolete. Close is no longer required", false)] + public void Close() + { + MyLog.Default.Info($"[NetworkAPI] Unregistering communication stream: {ComId}"); + if (UsingTextCommands) MyAPIGateway.Utilities.MessageEntered -= HandleChatInput; + + MyAPIGateway.Multiplayer.UnregisterMessageHandler(ComId, HandleIncomingPacket); + } + + /// + /// Calls Instance.Close() + /// + [ObsoleteAttribute("This property is obsolete. Dispose is no longer required", false)] + public static void Dispose() + { + if (IsInitialized) Instance.Close(); + + Instance = null; + } + + /// + /// Initializes the default instance of the NetworkAPI + /// + public static void Init(ushort comId, string modName, string keyword = null) + { + if (IsInitialized) + return; + + if (!MyAPIGateway.Multiplayer.IsServer) + Instance = new Client(comId, modName, keyword); + else + Instance = new Server(comId, modName, keyword); + } + + /// + /// Gets the diffrence between now and a given timestamp in milliseconds + /// + /// + public static float GetDeltaMilliseconds(long timestamp) + { + return (DateTime.UtcNow.Ticks - timestamp) / TimeSpan.TicksPerMillisecond; + } + + public static int GetDeltaFrames(long timestamp) + { + return (int)Math.Ceiling(GetDeltaMilliseconds(timestamp) / frames); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Libraries/SENetworkAPI/Server.cs b/GlobalTorch/API/Libraries/SENetworkAPI/Server.cs new file mode 100644 index 0000000..a65aff5 --- /dev/null +++ b/GlobalTorch/API/Libraries/SENetworkAPI/Server.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using Sandbox.ModAPI; +using VRage; +using VRage.Game.ModAPI; +using VRage.Utils; +using VRageMath; + +namespace SENetworkAPI +{ + public class Server : NetworkAPI + { + /// + /// Server class contains a few server only feature beond what is inharited from the NetworkAPI + /// + /// Identifies the channel to pass information to and from this mod + /// identifies what chat entries should be captured and sent to the server + public Server(ushort comId, string modName, string keyword = null) : base(comId, modName, keyword) + { + } + + /// + /// Sends a command packet to the client(s) + /// + /// The command to be executed + /// Text that will be displayed in client chat + /// A serialized object to be sent across the network + /// The date timestamp this command was sent + /// The client reciving this packet (if 0 it sends to all clients) + /// Enture delivery of the packet + public override void SendCommand(string commandString, string message = null, byte[] data = null, + DateTime? sent = null, ulong steamId = ulong.MinValue, bool isReliable = true) + { + SendCommand( + new Command + { + SteamId = steamId, CommandString = commandString, Message = message, Data = data, + Timestamp = sent == null ? DateTime.UtcNow.Ticks : sent.Value.Ticks + }, steamId, isReliable); + } + + /// + /// + /// Sends a command packet to the client(s) + /// the center of the sync location + /// the distance the message reaches (defaults to sync distance) + /// Text that will be displayed in client chat + /// A serialized object to be sent across the network + /// The date timestamp this command was sent + /// The client reciving this packet (if 0 it sends to all clients) + /// Enture delivery of the packet + public override void SendCommand(string commandString, Vector3D point, double radius = 0, string message = null, + byte[] data = null, DateTime? sent = null, ulong steamId = ulong.MinValue, bool isReliable = true) + { + SendCommand( + new Command + { + SteamId = steamId, CommandString = commandString, Message = message, Data = data, + Timestamp = sent == null ? DateTime.UtcNow.Ticks : sent.Value.Ticks + }, point, radius, steamId, isReliable); + } + + /// + /// Sends a command packet to a list of clients + /// + /// + /// The command to be executed + /// Text that will be displayed in client chat + /// A serialized object to be sent across the network + /// The date timestamp this command was sent + /// Enture delivery of the packet + public void SendCommandTo(ulong[] steamIds, string commandString, string message = null, byte[] data = null, + DateTime? sent = null, bool isReliable = true) + { + foreach (var id in steamIds) + SendCommand( + new Command + { + SteamId = id, CommandString = commandString, Message = message, Data = data, + Timestamp = sent == null ? DateTime.UtcNow.Ticks : sent.Value.Ticks + }, id, isReliable); + } + + /// + /// Sends a command packet to the client(s) + /// + /// The object to be sent to the client + /// The players steam id + /// Make sure the data arrives + internal override void SendCommand(Command cmd, ulong steamId = ulong.MinValue, bool isReliable = true) + { + if (cmd.Data != null && cmd.Data.Length > CompressionThreshold) + { + cmd.Data = MyCompression.Compress(cmd.Data); + cmd.IsCompressed = true; + } + + if (!string.IsNullOrWhiteSpace(cmd.Message) && MyAPIGateway.Multiplayer.IsServer && + MyAPIGateway.Session != null) MyAPIGateway.Utilities.ShowMessage(ModName, cmd.Message); + + var packet = MyAPIGateway.Utilities.SerializeToBinary(cmd); + + if (LogNetworkTraffic) + MyLog.Default.Info( + $"[NetworkAPI] TRANSMITTING Bytes: {packet.Length} Command: {cmd.CommandString} User: {steamId}"); + + if (steamId == ulong.MinValue) + MyAPIGateway.Multiplayer.SendMessageToOthers(ComId, packet, isReliable); + else + MyAPIGateway.Multiplayer.SendMessageTo(ComId, packet, steamId, isReliable); + } + + /// + /// Sends a command packet to the client(s) + /// + /// The object to be sent to the client + /// the center of the sync location + /// the distance the message reaches (defaults to sync distance) + /// The players steam id + /// Make sure the data arrives + internal override void SendCommand(Command cmd, Vector3D point, double radius = 0, + ulong steamId = ulong.MinValue, bool isReliable = true) + { + if (cmd.Data != null && cmd.Data.Length > CompressionThreshold) + { + cmd.Data = MyCompression.Compress(cmd.Data); + cmd.IsCompressed = true; + } + + if (radius == 0) radius = MyAPIGateway.Session.SessionSettings.SyncDistance; + + var players = new List(); + if (steamId == ulong.MinValue) + MyAPIGateway.Players.GetPlayers(players, + p => (p.GetPosition() - point).LengthSquared() < radius * radius && p.SteamUserId != cmd.SteamId); + else + MyAPIGateway.Players.GetPlayers(players, p => p.SteamUserId == steamId); + + if (!string.IsNullOrWhiteSpace(cmd.Message) && MyAPIGateway.Multiplayer.IsServer && + MyAPIGateway.Session != null) MyAPIGateway.Utilities.ShowMessage(ModName, cmd.Message); + + cmd.Timestamp = DateTime.UtcNow.Ticks; + var packet = MyAPIGateway.Utilities.SerializeToBinary(cmd); + + if (LogNetworkTraffic) + MyLog.Default.Info( + $"[NetworkAPI] _TRANSMITTING_ Bytes: {packet.Length} Command: {cmd.CommandString} To: {players.Count} Users within {radius}m"); + + foreach (var player in players) + MyAPIGateway.Multiplayer.SendMessageTo(ComId, packet, player.SteamUserId, isReliable); + } + + public override void Say(string message) + { + SendCommand(null, message); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Libraries/SENetworkAPI/SessionTools.cs b/GlobalTorch/API/Libraries/SENetworkAPI/SessionTools.cs new file mode 100644 index 0000000..c51a313 --- /dev/null +++ b/GlobalTorch/API/Libraries/SENetworkAPI/SessionTools.cs @@ -0,0 +1,13 @@ +using VRage.Game.Components; + +namespace SENetworkAPI +{ + [MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)] + public class SessionTools : MySessionComponentBase + { + protected override void UnloadData() + { + NetworkAPI.Dispose(); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Util/CollectionUtils.cs b/GlobalTorch/API/Util/CollectionUtils.cs new file mode 100644 index 0000000..04932eb --- /dev/null +++ b/GlobalTorch/API/Util/CollectionUtils.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; + +namespace Global.API.Util +{ + public static class CollectionUtils + { + public static TV ComputeIfAbsent(this Dictionary self, TK key, Func valueCreator) + { + if (self.ContainsKey(key)) + return self[key]; + var val = valueCreator(key); + self.Add(key, val); + return val; + } + + public static void SetOrAdd(this Dictionary self, TK key, Func valueCreator, + Func valueIncrement) + { + if (self.ContainsKey(key)) + { + var current = self[key]; + self.Remove(key); + self.Add(key, valueIncrement(current)); + return; + } + + var val = valueCreator(key); + self.Add(key, val); + } + + public static bool TryGetElementAt(this IReadOnlyList self, int index, out T foundValue) + { + if (self.Count < index + 1) + { + foundValue = default; + return false; + } + + foundValue = self[index]; + return true; + } + + public static T GetElementAtIndexOrElse(this IReadOnlyList self, int index, T defaultValue) + { + return self.TryGetElementAt(index, out var e) ? e : defaultValue; + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Util/FacUtils.cs b/GlobalTorch/API/Util/FacUtils.cs new file mode 100644 index 0000000..af2fee9 --- /dev/null +++ b/GlobalTorch/API/Util/FacUtils.cs @@ -0,0 +1,45 @@ +using Sandbox.Game.Entities; +using Sandbox.Game.World; +using VRage.Game.ModAPI; + +namespace Global.API.Util +{ + public static class FacUtils + { + public static IMyFaction GetPlayersFaction(long playerId) + { + return MySession.Static.Factions.TryGetPlayerFaction(playerId); + } + + public static bool InSameFaction(long player1, long player2) + { + return GetPlayersFaction(player1) == GetPlayersFaction(player2); + } + + public static string GetFactionTag(long playerId) + { + var faction = MySession.Static.Factions.TryGetPlayerFaction(playerId); + + return faction == null ? "" : faction.Tag; + } + + public static long GetOwner(MyCubeGrid grid) + { + var ownersList = grid.BigOwners; + var totalOwners = ownersList.Count; + + if (totalOwners > 0 && ownersList[0] != 0) return ownersList[0]; + return totalOwners > 1 ? ownersList[1] : 0L; + } + + public static bool IsOwnerOrFactionOwned(MyCubeGrid grid, long playerId, bool doFactionCheck) + { + if (grid.BigOwners.Contains(playerId)) return true; + + if (!doFactionCheck) return false; + var ownerId = GetOwner(grid); + //check if the owner is a faction member, i honestly dont know the difference between grid.BigOwners and grid.SmallOwners + return InSameFaction(playerId, ownerId); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Util/FileUtils.cs b/GlobalTorch/API/Util/FileUtils.cs new file mode 100644 index 0000000..1a49e07 --- /dev/null +++ b/GlobalTorch/API/Util/FileUtils.cs @@ -0,0 +1,71 @@ +using System.IO; +using System.Xml.Serialization; +using Newtonsoft.Json; + +namespace Global.API.Util +{ + public static class FileUtils + { + public static void WriteToJsonFile(string filePath, T objectToWrite, bool append = false) where T : new() + { + TextWriter writer = null; + try + { + var contentsToWriteToFile = JsonConvert.SerializeObject(objectToWrite, Formatting.Indented); + writer = new StreamWriter(filePath, append); + writer.Write(contentsToWriteToFile); + } + finally + { + writer?.Close(); + } + } + + public static T ReadFromJsonFile(string filePath) where T : new() + { + TextReader reader = null; + try + { + reader = new StreamReader(filePath); + var fileContents = reader.ReadToEnd(); + return JsonConvert.DeserializeObject(fileContents); + } + finally + { + reader?.Close(); + } + } + + public static void WriteToXmlFile(string filePath, T objectToWrite, bool append = false) where T : new() + { + TextWriter writer = null; + try + { + var serializer = new XmlSerializer(typeof(T)); + writer = new StreamWriter(filePath, append); + serializer.Serialize(writer, objectToWrite); + } + finally + { + if (writer != null) + writer.Close(); + } + } + + public static T ReadFromXmlFile(string filePath) where T : new() + { + TextReader reader = null; + try + { + var serializer = new XmlSerializer(typeof(T)); + reader = new StreamReader(filePath); + return (T)serializer.Deserialize(reader); + } + finally + { + if (reader != null) + reader.Close(); + } + } + } +} \ No newline at end of file diff --git a/GlobalTorch/API/Util/GlobalLogManager.cs b/GlobalTorch/API/Util/GlobalLogManager.cs new file mode 100644 index 0000000..f67af07 --- /dev/null +++ b/GlobalTorch/API/Util/GlobalLogManager.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using Global.API.Util; +using Global.Shared.Logging; +using NLog; +using NLog.Config; +using NLog.Targets; + +namespace Global +{ + public static class GlobalLogManager + { + private static readonly Dictionary Targets = new Dictionary(); + + public static IPluginLogger GetLogger(string name) + { + return new TorchLogger(name); + } + + public static void PatchToUseCustomFileLogger(string name, string filename = null) + { + if (filename == null) filename = name; + var rules = LogManager.Configuration.LoggingRules; + for (var i = rules.Count - 1; i >= 0; i--) + if (rules[i].NameMatches(name)) + rules.RemoveAtFast(i); + + var logTarget = Targets.ComputeIfAbsent(filename, fn => new FileTarget(filename) + { + FileName = $"Logs/{fn}-latest.log", + Layout = "${var:logStamp} ${logger}: ${var:logContent}", + ArchiveOldFileOnStartup = true, + ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, + ArchiveDateFormat = "dd-MM-yyyy", + ArchiveFileName = $"Logs/Archive/{fn}-{{###}}.zip", + EnableArchiveFileCompression = true + }); + var debugRule = new LoggingRule(name, LogLevel.Debug, logTarget); + rules.Insert(0, debugRule); + if (name == "Keen" && GlobalPlugin.LegacyConfig.LogKeenToConsole) + { + var consoleRule = new LoggingRule(name, LogLevel.Info, + LogManager.Configuration.FindTargetByName("console")); + consoleRule.Targets.Add(LogManager.Configuration.FindTargetByName("wpf")); + rules.Insert(0, consoleRule); + } + + LogManager.Configuration.Reload(); + LogManager.ReconfigExistingLoggers(); + } + + public static Logger GetCustomFileLogger(string name, string filename = null) + { + if (filename == null) filename = name; + var rules = LogManager.Configuration.LoggingRules; + var logTarget = Targets.ComputeIfAbsent(filename, fn => new FileTarget(filename) + { + FileName = $"Logs/{fn}-latest.log", + Layout = "${var:logStamp} ${logger}: ${var:logContent}", + ArchiveOldFileOnStartup = true, + ArchiveNumbering = ArchiveNumberingMode.DateAndSequence, + ArchiveDateFormat = "dd-MM-yyyy", + ArchiveFileName = $"Logs/Archive/{fn}-{{###}}.zip", + EnableArchiveFileCompression = true + }); + + var logRule = new LoggingRule(name, LogLevel.Debug, logTarget); + + rules.Insert(0, logRule); + + LogManager.Configuration.Reload(); + return LogManager.GetLogger(name); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/App.config b/GlobalTorch/App.config new file mode 100644 index 0000000..591889e --- /dev/null +++ b/GlobalTorch/App.config @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GlobalTorch/AudioSendManager.cs b/GlobalTorch/AudioSendManager.cs new file mode 100644 index 0000000..e75176d --- /dev/null +++ b/GlobalTorch/AudioSendManager.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using FragLabs.Audio.Codecs; +using FragLabs.Audio.Codecs.Opus; +using Global.Util; +using Sandbox.Game.VoiceChat; +using Torch.API; +using Torch.Managers; +using Torch.Utils; +using VRage.Library.Collections; +using VRage.Network; + +namespace Global +{ + public class AudioSendManager : Manager + { + [ReflectedMethodInfo(typeof(MyVoiceChatSessionComponent), "SendVoicePlayer")] + private static readonly MethodInfo _sendVoiceMethod; + + private readonly LegacyConfig _configData; + + private readonly object _lock = new object(); + + private readonly Dictionary _plays = new Dictionary(); + private readonly SendBuffer _sendBuffer = new SendBuffer(); + private readonly Thread _thread; + + private byte[] _buffer = Array.Empty(); + + private OpusEncoder _encoder; + + public AudioSendManager(ITorchBase torchInstance, LegacyConfig configData) : base(torchInstance) + { + _configData = configData; + _thread = new Thread(UpdateProc); + } + + public override void Attach() + { + base.Attach(); + _encoder = OpusEncoder.Create(24000, 1, Application.Restricted_LowLatency); + _thread.Start(); + } + + public override void Detach() + { + base.Detach(); + _encoder.Dispose(); + _thread.Join(1); + } + + private void UpdateProc() + { + while (true) + { + lock (_lock) + { + foreach (var (player, stream) in _plays) + { + // if stream can read and is not at the end + if (stream.CanRead && stream.Position < stream.Length) + { + ArrayExtensions.EnsureCapacity(ref _buffer, _configData.SendDataMs * 48); + var sampleLength = stream.Read(_buffer, 0, _configData.SendDataMs * 48); + if (sampleLength == 0) continue; + _sendBuffer.SenderUserId = (long)player; + _sendBuffer.VoiceDataBuffer = + _encoder.Encode(_buffer, sampleLength, out var encodedLength); + _sendBuffer.NumElements = encodedLength; + NetworkManager.RaiseStaticEvent(_sendVoiceMethod, 0UL, + _sendBuffer, + new EndpointId(player)); + } + else + { + stream.Dispose(); + _plays.Remove(player); + } + } + + Thread.Sleep(1); + } + } + } + } +} \ No newline at end of file diff --git a/GlobalTorch/Config/WpfConfig.cs b/GlobalTorch/Config/WpfConfig.cs new file mode 100644 index 0000000..421582a --- /dev/null +++ b/GlobalTorch/Config/WpfConfig.cs @@ -0,0 +1,183 @@ +using System; +using System.Xml.Serialization; +using Global.Shared.Config; +using Global.Shared.Logging; +using Torch; +using Torch.Views; +using VRageMath; + +namespace Global.Config +{ + [Serializable] + public class WpfConfig : ViewModel, IPluginConfig + { + #region Base + + private bool _isEnabled = true; + + [Display(Name = "Enabled", Description = "Whether the plugin is enabled or not", GroupName = "Main")] + public bool IsEnabled + { + get => _isEnabled; + set => SetValue(ref _isEnabled, value); + } + + #endregion + + #region Database + + private string _host = "localhost"; + private int _port = 3306; + private string _database = "torch"; + private string _username = "root"; + private string _password = ""; + + [Display(Name = "Host", Description = "Address where database is located", GroupName = "Database", Order = 1)] + public string Host + { + get => _host; + set => SetValue(ref _host, value); + } + + [Display(Name = "Port", Description = "Port the database has access through", GroupName = "Database", + Order = 2)] + public int Port + { + get => _port; + set => SetValue(ref _port, value); + } + + [Display(Name = "Username", Description = "User to connect to the database with", GroupName = "Database", + Order = 3)] + public string Username + { + get => _username; + set => SetValue(ref _username, value); + } + + [Display(Name = "Password", Description = "Password for connection authentication", GroupName = "Database", + Order = 4)] + public string Password + { + get => _password; + set => SetValue(ref _password, value); + } + + [Display(Name = "Database", Description = "Which database you want to connect to", GroupName = "Database", + Order = 5)] + public string Database + { + get => _database; + set => SetValue(ref _database, value); + } + + #endregion + + #region Monitoring + + private bool _discordStatus; + private string _heartbeatUrl; + + [Display(Name = "Send Discord Status", + Description = "Whether to send a discord message when server is detected as crashed", + GroupName = "Monitoring")] + public bool SendDiscordStatus + { + get => _discordStatus; + set => SetValue(ref _discordStatus, value); + } + + [Display(Name = "Heartbeat URL", Description = "URL for heartbeat to be sent", GroupName = "Monitoring")] + public string HeartbeatUrl + { + get => _heartbeatUrl; + set => SetValue(ref _heartbeatUrl, value); + } + + #endregion + + #region Logging + + private LoggingLevel _level = LoggingLevel.Info; + + [Display(Name = "Logging Level", Description = "")] + public LoggingLevel Level + { + get => _level; + set => SetValue(ref _level, value); + } + + #endregion + + + #region OcTree + + private int _size = 40000000; + + [Display(Name = "Size", Description = "Size of the OcTree", GroupName = "OcTree", Order = 1)] + public int Size + { + get => _size; + set => SetValue(ref _size, value); + } + + private int _centerX = 0; + + [Display(Name = "Center X", Description = "X coordinate of the center of the OcTree", GroupName = "OcTree", Order = 2)] + public int CenterX + { + get => _centerX; + set => SetValue(ref _centerX, value); + } + + private int _centerY = 0; + + [Display(Name = "Center Y", Description = "Y coordinate of the center of the OcTree", GroupName = "OcTree", Order = 3)] + public int CenterY + { + get => _centerY; + set => SetValue(ref _centerY, value); + } + + private int _centerZ = 0; + + [Display(Name = "Center Z", Description = "Z coordinate of the center of the OcTree", GroupName = "OcTree", Order = 4)] + public int CenterZ + { + get => _centerZ; + set => SetValue(ref _centerZ, value); + } + + [XmlIgnore] + public Vector3I CenterPosition + { + get => new Vector3I(_centerX, _centerY, _centerZ); + set + { + SetValue(ref _centerX, value.X); + SetValue(ref _centerY, value.Y); + SetValue(ref _centerZ, value.Z); + } + } + + private int _capacity = 10; + + [Display(Name = "Capacity", Description = "Capacity of the OcTree", GroupName = "OcTree", Order = 5)] + public int Capacity + { + get => _capacity; + set => SetValue(ref _capacity, value); + } + + private int _maxDepth = 12; + + [Display(Name = "Max Depth", Description = "Max depth of the OcTree", GroupName = "OcTree", Order = 6)] + public int MaxDepth + { + get => _maxDepth; + set => SetValue(ref _maxDepth, value); + } + + #endregion + } +} \ No newline at end of file diff --git a/GlobalTorch/DBManager.cs b/GlobalTorch/DBManager.cs new file mode 100644 index 0000000..7215d03 --- /dev/null +++ b/GlobalTorch/DBManager.cs @@ -0,0 +1,16 @@ +namespace Global +{ + public class DbManager + { + public static string GetDbConnectionString() + { + return GlobalPlugin.WpfConfig == null + ? CreateConnectionString("mi3-ss47.a2hosting.com", 3306, "sigmadra_admin", "yW=ri?_=UnD^", "sigmadra_lexicon") + : CreateConnectionString(GlobalPlugin.WpfConfig.Host, GlobalPlugin.WpfConfig.Port, + GlobalPlugin.WpfConfig.Username, GlobalPlugin.WpfConfig.Password, GlobalPlugin.WpfConfig.Database); + } + + private static string CreateConnectionString(string server, int port, string username, string password, + string database) => $"server={server},{port};database={database};user={username};Password={password};"; + } +} \ No newline at end of file diff --git a/GlobalTorch/GlobalCommands.cs b/GlobalTorch/GlobalCommands.cs new file mode 100644 index 0000000..cc96897 --- /dev/null +++ b/GlobalTorch/GlobalCommands.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Global.API.Util; +using Sandbox.Game.Entities; +using Torch.Commands; +using Torch.Commands.Permissions; +using VRage.Game.ModAPI; + +namespace Global +{ + [Category("global")] + public class GlobalCommands : CommandModule + { + [Command("debug", "does thing.")] + [Permission(MyPromoteLevel.Admin)] + public void ReloadConfig() + { + Context.Respond($"There are {MyEntities.m_entityNameDictionary.Count} entities in the dictionary."); + var dictionary = new Dictionary(); + + foreach (var key in MyEntities.m_entityNameDictionary) + { + var type = key.Value.GetType(); + dictionary.SetOrAdd(type, t => 1, e => e + 1); + } + + foreach (var (k, v) in dictionary) Context.Respond($"{k.Name} : {v}"); + } + + [Command("crash")] + [Permission(MyPromoteLevel.Admin)] + public void Crash() + { + Context.Torch.Invoke(() => throw new Exception("This is a crash")); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/GlobalPlugin.cs b/GlobalTorch/GlobalPlugin.cs new file mode 100644 index 0000000..06e45e4 --- /dev/null +++ b/GlobalTorch/GlobalPlugin.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading; +using System.Windows.Controls; +using Global.API.Discord; +using Global.API.Util; +using Global.Config; +using Global.Patches; +using Global.Shared.Config; +using Global.Shared.Logging; +using Global.Shared.Patching; +using Global.Shared.Plugin; +using HarmonyLib; +using Sandbox.Game.GameSystems; +using SENetworkAPI; +using Torch; +using Torch.API; +using Torch.API.Managers; +using Torch.API.Plugins; +using Torch.Managers; +using Torch.Managers.PatchManager; +using Torch.Views; +using Timer = System.Timers.Timer; + +namespace Global +{ + public class GlobalPlugin : TorchPluginBase, IGlobalPlugin, IWpfPlugin + { + private const ushort ModId = 21031; + private const string ModName = "GlobalClientMod"; + public static Persistent PersistentConfig; + + private bool _init; + + public static LegacyConfig LegacyConfig { get; private set; } + public IPluginConfig Config => WpfConfig; + public static WpfConfig WpfConfig => PersistentConfig?.Data; + + public static AudioSendManager AudioManager { get; private set; } + + public long LastSent { get; private set; } = -1; + public bool SentDiscord { get; private set; } + public long Tick { get; private set; } + public bool Started { get; private set; } + public IPluginLogger Log { get; } = new TorchLogger("GlobalTorch"); + + public void Run(Action action) + { + Torch.Invoke(action); + } + + public UserControl GetControl() + { + return new PropertyGrid + { + DataContext = WpfConfig + }; + } + + public override void Init(ITorchBase torch) + { +#if DEBUG + // Allow the debugger some time to connect once the plugin assembly is loaded + Thread.Sleep(100); +#endif + base.Init(torch); + if (!File.Exists("opus.dll")) + using (var destination = File.Create("opus.dll")) + { + using (var manifestResourceStream = + typeof(GlobalPlugin).Assembly.GetManifestResourceStream("Global.opus.dll")) + { + if (manifestResourceStream != null) manifestResourceStream.CopyTo(destination); + else throw new Exception("Could not find opus.dll"); + } + } + + SetupConfig(); + if (LegacyConfig.OverwriteLogs) + { + // GlobalLogManager.PatchToUseCustomFileLogger("Keen"); + // GlobalLogManager.PatchToUseCustomFileLogger("Torch"); + } + + GlobalInstance.SetPlugin(this); + + // AudioManager = new AudioSendManager(Torch, Config); + // Torch.Managers.AddManager(AudioManager); + // Torch.Managers.AddManager(new ConsoleManager(Torch)); + + Patcher.InitilizePatcherContext(Torch.Managers.GetManager().AcquireContext()); + + if (!PatchHelpers.PatchAll(Log, new Harmony("Draconis.GlobalTorch"))) + { + Log.Error("Failed to patch all"); + return; + } + + MyScriptManagerPatch.ApplyPluginAPIPatch(); + + Log.Info("GlobalTorch Plugin Initialized"); + + if (LegacyConfig.EnableNexusIntegration) + { + var pluginManager = Torch.Managers.GetManager(); + if (pluginManager.Plugins.TryGetValue(NexusIntegration.NexusId, out _)) + { + Log.Info("Nexus Plugin Found, Loading Nexus Integration"); + NexusIntegration.Load(); + } + else + { + Log.Info("Nexus Plugin Not Found, Nexus Integration Not Loaded"); + } + } + + var timer = new Timer(15000); + timer.Elapsed += (sender, args) => + { + if (!Started || Torch.GameState > TorchGameState.Loaded) return; + + if (!string.IsNullOrWhiteSpace(WpfConfig.HeartbeatUrl)) + { + if (LastSent != Tick) + try + { + var request = WebRequest.Create(WpfConfig.HeartbeatUrl); + using (var res = request.GetResponse()) + { + } + } + catch (Exception e) + { + Log.Error(e, $"Error sending heartbeat to {WpfConfig.HeartbeatUrl}"); + } + else + Log.Error("Game crashed, not sending."); + } + + if (LastSent == Tick && WpfConfig.SendDiscordStatus && !SentDiscord) + try + { + var discordWebhook = new DiscordWebhook + { + Url = + "https://discord.com/api/webhooks/966330173303689297/9-71wWH7XQjXKVg92kFIaFQl8lFdtjsCWfsEWLQH2SeZP2Zc7tA_AbbovSGeN4asFCwZ" + }; + var discordMessage = new DiscordMessage + { + Content = $"<@&671423106102853642> {Torch.Config.InstanceName} HAS GONE DOWN!", + Username = Torch.Config.InstanceName, + AllowedMentions = new DiscordAllowedMentions + { + Parse = new List { "roles" } + } + }; + discordWebhook.Send(discordMessage); + SentDiscord = true; + } + catch (Exception e) + { + Log.Error(e, "Error sending discord status"); + } + + LastSent = Tick; + }; + timer.Enabled = true; + } + + public override void Update() + { + Tick++; + if (!_init) + { + GlobalStatic.Init(); + + // if (Config.EnableBoundingPatches) + // MyAPIGateway.Entities = new MyEntitiesButBetter(MyAPIGateway.Entities); + + _init = true; + + if (!NetworkAPI.IsInitialized) NetworkAPI.Init(ModId, ModName); + + Traverse.Create().Field("MAX_DISTANCE_RELATIVE_DAMPENING") + .SetValue(LegacyConfig.PatchesConfig.MaxDistanceRelativeDamping); + Traverse.Create().Field("MAX_DISTANCE_RELATIVE_DAMPENING_SQ").SetValue( + LegacyConfig.PatchesConfig.MaxDistanceRelativeDamping * + LegacyConfig.PatchesConfig.MaxDistanceRelativeDamping); + + Log.Info("GlobalTorch Plugin Initialized"); + + Started = true; + } + + GlobalStatic.Update(); + } + + public void SetupConfig() + { + var wpfConfigPath = Path.Combine(StoragePath, "Global2.cfg"); + PersistentConfig = Persistent.Load(wpfConfigPath); + + var configPath = $"{StoragePath}\\GlobalTorch.xml"; + LegacyConfig = File.Exists(configPath) + ? FileUtils.ReadFromXmlFile(configPath) + : new LegacyConfig(); + + FileUtils.WriteToXmlFile(configPath, LegacyConfig); + } + + public static bool IsLoggingLevel(LoggingLevel level) + { + if (WpfConfig == null) + return level.IsOfLevel(LoggingLevel.Info); + return WpfConfig.Level.IsOfLevel(level); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/GlobalTorch.csproj b/GlobalTorch/GlobalTorch.csproj new file mode 100644 index 0000000..1c56c5d --- /dev/null +++ b/GlobalTorch/GlobalTorch.csproj @@ -0,0 +1,273 @@ + + + + + Debug + AnyCPU + {BB44D53C-70F4-4EDD-9B03-321CDF62BCA4} + Library + Properties + Global + GlobalTorch + v4.8 + 512 + 7.3 + disable + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + if exist $(SolutionDir)TorchBinaries\Plugins\$(AssemblyName).zip ( Del $(SolutionDir)TorchBinaries\Plugins\$(AssemblyName).zip) + powershell.exe -command Compress-Archive -Path $(AssemblyName).dll, $(AssemblyName).pdb, 0Harmony.dll, RCONServerLib.dll, OpusWrapper.dll, manifest.xml -DestinationPath $(SolutionDir)TorchBinaries\Plugins\$(AssemblyName).zip + + + + + ..\..\NexusBinaries\0Harmony.dll + + + + False + ..\..\TorchBinaries\DedicatedServer64\Newtonsoft.Json.dll + False + + + ..\..\NexusBinaries\Nexus.dll + False + + + OpusWrapper.dll + + + + ..\..\TorchBinaries\DedicatedServer64\SpaceEngineers.Game.dll + False + + + ..\..\TorchBinaries\DedicatedServer64\SpaceEngineers.ObjectBuilders.dll + False + + + ..\..\TorchBinaries\DedicatedServer64\NLog.dll + False + + + ..\..\TorchBinaries\DedicatedServer64\ProtoBuf.Net.dll + False + + + ..\..\TorchBinaries\DedicatedServer64\ProtoBuf.Net.Core.dll + False + + + ..\..\TorchBinaries\DedicatedServer64\Sandbox.Common.dll + False + + + ..\..\TorchBinaries\DedicatedServer64\Sandbox.Game.dll + False + + + ..\..\TorchBinaries\DedicatedServer64\Sandbox.Game.XmlSerializers.dll + False + + + ..\..\TorchBinaries\DedicatedServer64\Sandbox.Graphics.dll + False + False + + + ..\..\TorchBinaries\DedicatedServer64\Sandbox.RenderDirect.dll + False + + + ..\..\TorchBinaries\DedicatedServer64\Steamworks.NET.dll + False + + + + + + + + + + ..\..\TorchBinaries\System.ComponentModel.Annotations.dll + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ..\..\TorchBinaries\Torch.dll + + + ..\..\TorchBinaries\Torch.API.dll + + + ..\..\TorchBinaries\Torch.Server.exe + + + ..\..\TorchBinaries\DedicatedServer64\VRage.dll + + + ..\..\TorchBinaries\DedicatedServer64\VRage.Audio.dll + + + ..\..\TorchBinaries\DedicatedServer64\VRage.Dedicated.dll + + + ..\..\TorchBinaries\DedicatedServer64\VRage.EOS.dll + + + ..\..\TorchBinaries\DedicatedServer64\VRage.Game.dll + + + ..\..\TorchBinaries\DedicatedServer64\VRage.Game.XmlSerializers.dll + + + ..\..\TorchBinaries\DedicatedServer64\VRage.Input.dll + + + ..\..\TorchBinaries\DedicatedServer64\VRage.Library.dll + + + ..\..\TorchBinaries\DedicatedServer64\VRage.Math.dll + + + ..\..\TorchBinaries\DedicatedServer64\VRage.Math.XmlSerializers.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + + + + 1.6.0 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + 1.3.1 + + + 6.0.3 + + + 4.5.5 + + + 6.0.0 + + + + + + \ No newline at end of file diff --git a/GlobalTorch/GlobalTorch.csproj.user b/GlobalTorch/GlobalTorch.csproj.user new file mode 100644 index 0000000..1e3b11e --- /dev/null +++ b/GlobalTorch/GlobalTorch.csproj.user @@ -0,0 +1,9 @@ + + + + ProjectFiles + + + F:\SteamLibrary\steamapps\common\SpaceEngineersDedicatedServer\DedicatedServer64\;C:\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\;C:\Users\Allen\Desktop\Stuff\SE Stuff\plugins\Nexus\;C:\Users\Allen\Desktop\Stuff\SE Stuff\Torch\ + + \ No newline at end of file diff --git a/GlobalTorch/LegacyConfig.cs b/GlobalTorch/LegacyConfig.cs new file mode 100644 index 0000000..cdce505 --- /dev/null +++ b/GlobalTorch/LegacyConfig.cs @@ -0,0 +1,52 @@ +using System.Xml.Serialization; +using VRageMath; + +namespace Global +{ + [XmlRoot("Config")] + public class LegacyConfig + { + public int BlockUpdateFrequency = 100; + public int Capacity = 8; + public bool EnableBoundingPatches = true; + + public bool EnableDebugLogging = false; + public bool EnableEntityNamePatch = true; + public bool EnableNexusIntegration = false; + public bool LogKeenToConsole = false; + public int MaxDepth = 10; + + public bool OverwriteLogs = false; + + [XmlElement("Patches")] public PatchesConfig PatchesConfig = new PatchesConfig(); + public int SendDataMs = 120; + + public Sync Sync = new Sync(); + public Vector3 WorldCenter = Vector3.Zero; + public Vector3 WorldSize = new Vector3(40000000, 40000000, 40000000); + public string InstanceName { get; set; } = "Unset Instance Name"; + public static LegacyConfig Instance => GlobalPlugin.LegacyConfig; + public static PatchesConfig Patches => Instance.PatchesConfig; + } + + public class PatchesConfig + { + public bool DisableAllPatches = false; + public bool EnableGridMerge = true; + public bool EnableModAPIPatch = true; + public float MaxDistanceRelativeDamping = 100f; + } + + public class Sync + { + public bool EnableSyncOverride = false; + + public float RadiusMultiplier = 4f; + public float SendFrequencyMultiplier = 2f; + + public int StartRadius = 20; + public int StartSendFrequency = 4; + public int StartUpdateFrequency = 60; + public float UpdateFrequencyMultiplier = 2f; + } +} \ No newline at end of file diff --git a/GlobalTorch/NexusIntegration.cs b/GlobalTorch/NexusIntegration.cs new file mode 100644 index 0000000..0ecae3d --- /dev/null +++ b/GlobalTorch/NexusIntegration.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Nexus.API; +using VRageMath; + +namespace Global +{ + public static class NexusIntegration + { + public static Guid NexusId = new Guid("28a12184-0422-43ba-a6e6-2e228611cca5"); + public static bool EnableNexusIntegration; + + public static void Load() + { + EnableNexusIntegration = true; + } + + public static List GetSectors(BoundingSphereD sphereD) + { + if (!EnableNexusIntegration) return new List(); + return (from sector in NexusAPI.GetSectors() + let sectorSphere = new BoundingSphereD(sector.Center, sector.Radius) + where sectorSphere.Intersects(sphereD) + select sector).ToList(); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/OpusWrapper.dll b/GlobalTorch/OpusWrapper.dll new file mode 100644 index 0000000000000000000000000000000000000000..0fcf9900013b33891bce8d2ad9c6e45bba47ca68 GIT binary patch literal 10240 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 Vnt#)st*Y@qk70gc^Z#Z9{u|0S@eTk0 literal 0 HcmV?d00001 diff --git a/GlobalTorch/Overwrites/MyEntitiesButBetter.cs b/GlobalTorch/Overwrites/MyEntitiesButBetter.cs new file mode 100644 index 0000000..79f2253 --- /dev/null +++ b/GlobalTorch/Overwrites/MyEntitiesButBetter.cs @@ -0,0 +1,366 @@ +using System; +using System.Collections.Generic; +using VRage.ModAPI; +using VRage.ObjectBuilders; +using VRageMath; + +namespace Global +{ + public class MyEntitiesButBetter : IMyEntities + { + private readonly List _entities = new List(); + private readonly List _entityIds = new List(); + private readonly IMyEntities m_entities; + + public MyEntitiesButBetter(IMyEntities mEntities) + { + m_entities = mEntities; + } + + public bool TryGetEntityById(long id, out IMyEntity entity) + { + return m_entities.TryGetEntityById(id, out entity); + } + + public bool TryGetEntityById(long? id, out IMyEntity entity) + { + return m_entities.TryGetEntityById(id, out entity); + } + + public bool TryGetEntityByName(string name, out IMyEntity entity) + { + return m_entities.TryGetEntityByName(name, out entity); + } + + public bool EntityExists(string name) + { + return m_entities.EntityExists(name); + } + + public void AddEntity(IMyEntity entity, bool insertIntoScene = true) + { + m_entities.AddEntity(entity, insertIntoScene); + } + + public IMyEntity CreateFromObjectBuilder(MyObjectBuilder_EntityBase objectBuilder) + { + return m_entities.CreateFromObjectBuilder(objectBuilder); + } + + public IMyEntity CreateFromObjectBuilderAndAdd(MyObjectBuilder_EntityBase objectBuilder) + { + return m_entities.CreateFromObjectBuilderAndAdd(objectBuilder); + } + + public void RemoveEntity(IMyEntity entity) + { + m_entities.RemoveEntity(entity); + } + + public bool IsSpherePenetrating(ref BoundingSphereD bs) + { + return m_entities.IsSpherePenetrating(ref bs); + } + + public Vector3D? FindFreePlace(Vector3D basePos, float radius, int maxTestCount = 20, int testsPerDistance = 5, + float stepSize = 1) + { + return m_entities.FindFreePlace(basePos, radius, maxTestCount, testsPerDistance, stepSize); + } + + public void GetInflatedPlayerBoundingBox(ref BoundingBox playerBox, float inflation) + { + m_entities.GetInflatedPlayerBoundingBox(ref playerBox, inflation); + } + + public void GetInflatedPlayerBoundingBox(ref BoundingBoxD playerBox, float inflation) + { + m_entities.GetInflatedPlayerBoundingBox(ref playerBox, inflation); + } + + public bool IsInsideVoxel(Vector3 pos, Vector3 hintPosition, out Vector3 lastOutsidePos) + { + return m_entities.IsInsideVoxel(pos, hintPosition, out lastOutsidePos); + } + + public bool IsInsideVoxel(Vector3D pos, Vector3D hintPosition, out Vector3D lastOutsidePos) + { + return m_entities.IsInsideVoxel(pos, hintPosition, out lastOutsidePos); + } + + public bool IsWorldLimited() + { + return m_entities.IsWorldLimited(); + } + + public float WorldHalfExtent() + { + return m_entities.WorldHalfExtent(); + } + + public float WorldSafeHalfExtent() + { + return m_entities.WorldSafeHalfExtent(); + } + + public bool IsInsideWorld(Vector3D pos) + { + return m_entities.IsInsideWorld(pos); + } + + public bool IsRaycastBlocked(Vector3D pos, Vector3D target) + { + return m_entities.IsRaycastBlocked(pos, target); + } + + public void SetEntityName(IMyEntity IMyEntity, bool possibleRename = true) + { + m_entities.SetEntityName(IMyEntity, possibleRename); + } + + public bool IsNameExists(IMyEntity entity, string name) + { + return m_entities.IsNameExists(entity, name); + } + + public void RemoveFromClosedEntities(IMyEntity entity) + { + m_entities.RemoveFromClosedEntities(entity); + } + + public void RemoveName(IMyEntity entity) + { + m_entities.RemoveName(entity); + } + + public bool Exist(IMyEntity entity) + { + return m_entities.Exist(entity); + } + + public void MarkForClose(IMyEntity entity) + { + m_entities.MarkForClose(entity); + } + + public void RegisterForUpdate(IMyEntity entity) + { + m_entities.RegisterForUpdate(entity); + } + + public void RegisterForDraw(IMyEntity entity) + { + m_entities.RegisterForDraw(entity); + } + + public void UnregisterForUpdate(IMyEntity entity, bool immediate = false) + { + m_entities.UnregisterForUpdate(entity, immediate); + } + + public void UnregisterForDraw(IMyEntity entity) + { + m_entities.UnregisterForDraw(entity); + } + + public IMyEntity GetIntersectionWithSphere(ref BoundingSphereD sphere) + { + return m_entities.GetIntersectionWithSphere(ref sphere); + } + + public IMyEntity GetIntersectionWithSphere(ref BoundingSphereD sphere, IMyEntity ignoreEntity0, + IMyEntity ignoreEntity1) + { + return m_entities.GetIntersectionWithSphere(ref sphere, ignoreEntity0, ignoreEntity1); + } + + public IMyEntity GetIntersectionWithSphere(ref BoundingSphereD sphere, IMyEntity ignoreEntity0, + IMyEntity ignoreEntity1, + bool ignoreVoxelMaps, bool volumetricTest, bool excludeEntitiesWithDisabledPhysics = false, + bool ignoreFloatingObjects = true, bool ignoreHandWeapons = true) + { + return m_entities.GetIntersectionWithSphere( + ref sphere, ignoreEntity0, ignoreEntity1, ignoreVoxelMaps, volumetricTest, + excludeEntitiesWithDisabledPhysics, ignoreFloatingObjects, ignoreHandWeapons); + } + + public IMyEntity GetEntityById(long entityId) + { + return m_entities.GetEntityById(entityId); + } + + public IMyEntity GetEntityById(long? entityId) + { + return m_entities.GetEntityById(entityId); + } + + public bool EntityExists(long entityId) + { + return m_entities.EntityExists(entityId); + } + + public bool EntityExists(long? entityId) + { + return m_entities.EntityExists(entityId); + } + + public IMyEntity GetEntityByName(string name) + { + return m_entities.GetEntityByName(name); + } + + public void SetTypeHidden(Type type, bool hidden) + { + m_entities.SetTypeHidden(type, hidden); + } + + public bool IsTypeHidden(Type type) + { + return m_entities.IsTypeHidden(type); + } + + public bool IsVisible(IMyEntity entity) + { + return m_entities.IsVisible(entity); + } + + public void UnhideAllTypes() + { + m_entities.UnhideAllTypes(); + } + + public void RemapObjectBuilderCollection(IEnumerable objectBuilders) + { + m_entities.RemapObjectBuilderCollection(objectBuilders); + } + + public void RemapObjectBuilder(MyObjectBuilder_EntityBase objectBuilder) + { + m_entities.RemapObjectBuilder(objectBuilder); + } + + public IMyEntity CreateFromObjectBuilderNoinit(MyObjectBuilder_EntityBase objectBuilder) + { + return m_entities.CreateFromObjectBuilderNoinit(objectBuilder); + } + + public void EnableEntityBoundingBoxDraw(IMyEntity entity, bool enable, Vector4? color = null, + float lineWidth = 0.01f, + Vector3? inflateAmount = null) + { + m_entities.EnableEntityBoundingBoxDraw(entity, enable, color, lineWidth, inflateAmount); + } + + public IMyEntity GetEntity(Func match) + { + return m_entities.GetEntity(match); + } + + public void GetEntities(HashSet entities, Func collect = null) + { + m_entities.GetEntities(entities, collect); + } + + public List GetIntersectionWithSphere(ref BoundingSphereD sphere, IMyEntity ignoreEntity0, + IMyEntity ignoreEntity1, + bool ignoreVoxelMaps, bool volumetricTest) + { + return m_entities.GetIntersectionWithSphere(ref sphere, + ignoreEntity0, ignoreEntity1, ignoreVoxelMaps, volumetricTest); + } + + public List GetEntitiesInAABB(ref BoundingBoxD boundingBox) + { + return m_entities.GetEntitiesInAABB(ref boundingBox); + } + + // public List GetEntitiesInSphere(ref BoundingSphereD boundingSphere) + // { + // var ents = MyGlobalGateway.OcTreeManager.GridTree.GetExact(_entityIds, boundingSphere); + // foreach (var ent in ents) _entities.Add(GetEntityById(ent)); + // ents.Clear(); + // return _entities; + // } + + public List GetElementsInBox(ref BoundingBoxD boundingBox) + { + return m_entities.GetElementsInBox(ref boundingBox); + } + + // public List GetTopMostEntitiesInSphere(ref BoundingSphereD boundingSphere) + // { + // var ents = MyGlobalGateway.OcTreeManager.GridTree.GetExact(_entityIds, boundingSphere); + // foreach (var ent in ents) _entities.Add(GetEntityById(ent)); + // ents.Clear(); + // ents = MyGlobalGateway.OcTreeManager.CharacterTree.GetExact(_entityIds, boundingSphere); + // foreach (var ent in ents) _entities.Add(GetEntityById(ent)); + // ents.Clear(); + // return _entities; + // } + // + // public List GetTopMostEntitiesInBox(ref BoundingBoxD boundingBox) + // { + // var ents = MyGlobalGateway.OcTreeManager.GridTree.GetExact(_entityIds, boundingBox); + // foreach (var ent in ents) _entities.Add(GetEntityById(ent)); + // ents.Clear(); + // ents = MyGlobalGateway.OcTreeManager.CharacterTree.GetExact(_entityIds, boundingBox); + // foreach (var ent in ents) _entities.Add(GetEntityById(ent)); + // ents.Clear(); + // return _entities; + // } + + //TODO: Replace with OcTree + public List GetEntitiesInSphere(ref BoundingSphereD boundingSphere) + { + return m_entities.GetEntitiesInSphere(ref boundingSphere); + } + + public List GetTopMostEntitiesInSphere(ref BoundingSphereD boundingSphere) + { + return m_entities.GetTopMostEntitiesInSphere(ref boundingSphere); + } + + public List GetTopMostEntitiesInBox(ref BoundingBoxD boundingBox) + { + return m_entities.GetTopMostEntitiesInBox(ref boundingBox); + } + + public IMyEntity CreateFromObjectBuilderParallel(MyObjectBuilder_EntityBase objectBuilder, + bool addToScene = false, + Action completionCallback = null) + { + return m_entities.CreateFromObjectBuilderParallel(objectBuilder, addToScene, completionCallback); + } + + event Action IMyEntities.OnEntityRemove + { + add => m_entities.OnEntityRemove += value; + remove => m_entities.OnEntityRemove -= value; + } + + event Action IMyEntities.OnEntityAdd + { + add => m_entities.OnEntityAdd += value; + remove => m_entities.OnEntityAdd -= value; + } + + event Action IMyEntities.OnCloseAll + { + add => m_entities.OnCloseAll += value; + remove => m_entities.OnCloseAll -= value; + } + + event Action IMyEntities.OnEntityNameSet + { + add => customEventDef += value; + remove => customEventDef -= value; + } + + private static event Action customEventDef; + + public static void CallRename(IMyEntity entity, string oldName, string newName) + { + customEventDef?.Invoke(entity, oldName, newName); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/Patches/MyScriptManagerPatch.cs b/GlobalTorch/Patches/MyScriptManagerPatch.cs new file mode 100644 index 0000000..7d2eefd --- /dev/null +++ b/GlobalTorch/Patches/MyScriptManagerPatch.cs @@ -0,0 +1,71 @@ +using System; +using System.Reflection; +using Global.API; +using Global.Shared.Plugin; +using HarmonyLib; +using Sandbox.Game.World; +using VRage.Game; +using VRage.Utils; + +namespace Global.Patches +{ + public class MyScriptManagerPatch + { + public static readonly Harmony Patcher = new Harmony("GlobalModApi"); + + public static void ApplyPluginAPIPatch() + { + Patches.Patcher.SuffixPatch("AddAssembly", + BindingFlags.Instance | BindingFlags.NonPublic, new[] + { + typeof(MyModContext), + typeof(MyStringId), + typeof(Assembly) + }, "CompileSuffix"); + } + + private static void CompileSuffix(MyModContext context, MyStringId myStringId, Assembly assembly) + { + if (!GlobalPlugin.LegacyConfig.PatchesConfig.EnableModAPIPatch) return; + var found = false; + foreach (var type in assembly.GetTypes()) + if (type.Name == "GlobalModApi") + { + GlobalInstance.Log.Info($"Found GlobalModApi in file: {type.Name}"); + PatchMethod(type, "IsRunningGlobal"); + PatchMethod(type, "GetAllBlocksOfType"); + PatchMethod(type, "GetAllBlocksOfTypeId"); + found = true; + } + } + + public static void PatchMethod(Type type, string methodName) + { + try + { + var method = type.GetMethod(methodName, + BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (method != null) + { + Patcher.Patch(method, + new HarmonyMethod(GetAPIMethod(methodName))); + GlobalInstance.Log.Info($"Patched {methodName}"); + } + else + { + GlobalInstance.Log.Warning($"Could not find method {methodName} in type {type.FullName}"); + } + } + catch (Exception e) + { + GlobalInstance.Log.Error(e, $"Failed to patch method {methodName} in {type.FullName}"); + } + } + + public static MethodInfo GetAPIMethod(string v) + { + return typeof(GlobalServerModApi).GetMethod(v, + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/Patches/Patcher.cs b/GlobalTorch/Patches/Patcher.cs new file mode 100644 index 0000000..891da50 --- /dev/null +++ b/GlobalTorch/Patches/Patcher.cs @@ -0,0 +1,131 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using NLog; +using Torch.Managers.PatchManager; + +namespace Global.Patches +{ + public static class Patcher + { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + public static PatchContext ctx; + + + public static void InitilizePatcherContext(PatchContext Context) + { + ctx = Context; + } + + public static MethodInfo GetMethod(string TargetMethodName, BindingFlags Flags) + { + return MethodGet(typeof(T), TargetMethodName, Flags, null); + } + + public static MethodInfo GetMethod( + string TargetMethodName, + BindingFlags Flags, + Type[] Types) + { + return MethodGet(typeof(T), TargetMethodName, Flags, Types); + } + + public static MethodInfo SuffixPatch( + string TargetMethodName, + BindingFlags Flags, + string ReplaceMentMethodName) + { + return Patch(false, typeof(T), TargetMethodName, Flags, ReplaceMentMethodName, null); + } + + public static MethodInfo SuffixPatch( + string TargetMethodName, + BindingFlags Flags, + Type[] Types, + string ReplaceMentMethodName) + { + return Patch(false, typeof(T), TargetMethodName, Flags, ReplaceMentMethodName, Types); + } + + public static MethodInfo PrePatch( + string TargetMethodName, + BindingFlags Flags, + string ReplaceMentMethodName) + { + return Patch(true, typeof(T), TargetMethodName, Flags, ReplaceMentMethodName, null); + } + + public static MethodInfo PrePatch( + string TargetMethodName, + BindingFlags Flags, + Type[] Types, + string ReplaceMentMethodName) + { + return Patch(true, typeof(T), TargetMethodName, Flags, ReplaceMentMethodName, Types); + } + + private static MethodInfo Patch( + bool PreFix, + Type TargetClass, + string TargetMethodName, + BindingFlags Flags, + string ReplaceMentMethodName, + Type[] Types) + { + try + { + var method1 = Types != null + ? TargetClass.GetMethod(TargetMethodName, Flags, null, Types, null) + : TargetClass.GetMethod(TargetMethodName, Flags); + if (method1 == null) + { + Log.Error("Unable to find patch method " + TargetClass.Name + "." + TargetMethodName); + return null; + } + + var declaringType = new StackFrame(2, false).GetMethod().DeclaringType; + if (declaringType == null) + Log.Error("Unable to find calling class!"); + var method2 = declaringType.GetMethod(ReplaceMentMethodName, + BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (PreFix) + ctx.GetPattern(method1).Prefixes.Add(method2); + else + ctx.GetPattern(method1).Suffixes.Add(method2); + return method1; + } + catch (AmbiguousMatchException ex) + { + Log.Error(ex, + "You need to specify the method types! More than one method named: " + TargetClass.Name + "." + + TargetMethodName, Array.Empty()); + } + catch (ArgumentException ex) + { + Log.Error(ex, "Invalid Arguments for " + TargetMethodName + " exsisting in: " + TargetClass.Name, + Array.Empty()); + } + catch (Exception ex) + { + Log.Error(ex, "Unkown Patch Error!", Array.Empty()); + } + + return null; + } + + private static MethodInfo MethodGet( + Type TargetClass, + string TargetMethodName, + BindingFlags Flags, + Type[] Types) + { + var methodInfo = Types != null + ? TargetClass.GetMethod(TargetMethodName, Flags, null, Types, null) + : TargetClass.GetMethod(TargetMethodName, Flags); + if (!(methodInfo == null)) + return methodInfo; + Log.Error("Unable to find patch method " + TargetClass.Name + "." + TargetMethodName); + return null; + } + } +} \ No newline at end of file diff --git a/GlobalTorch/Program.cs b/GlobalTorch/Program.cs new file mode 100644 index 0000000..c2fc18a --- /dev/null +++ b/GlobalTorch/Program.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Sandbox.ModAPI.Ingame; +using SpaceEngineers.Game.ModAPI.Ingame; + +namespace IngameScript +{ + public class Program : MyGridProgram + { + private const string InputMap = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#&^£%!$@[]{};:.,<>/|\\|!~\"'`()=+-*_"; + + private const string Password = "password"; + + public Program() + { + Runtime.UpdateFrequency = UpdateFrequency.Update1; + } + + public void Main(string argument, UpdateType updateType) + { + } + + private void Setup() + { + var keypad = GridTerminalSystem.GetBlockGroupWithName("Keypad"); + // get list of button panels + var buttons = new List(); + keypad.GetBlocksOfType(buttons); + + var maxCharacters = buttons.Count; + if (maxCharacters > InputMap.Length) + { + Echo("Too many buttons"); + return; + } + + for (var index = 0; index < buttons.Count; index++) + { + var myButtonPanel = buttons[index]; + myButtonPanel.SetCustomButtonName(0, InputMap[index].ToString()); + } + } + } +} \ No newline at end of file diff --git a/GlobalTorch/Properties/AssemblyInfo.cs b/GlobalTorch/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b62c633 --- /dev/null +++ b/GlobalTorch/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GlobalTorch")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GlobalTorch")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("BB44D53C-70F4-4EDD-9B03-321CDF62BCA4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/GlobalTorch/TorchLogger.cs b/GlobalTorch/TorchLogger.cs new file mode 100644 index 0000000..5249092 --- /dev/null +++ b/GlobalTorch/TorchLogger.cs @@ -0,0 +1,114 @@ +using System; +using System.Runtime.CompilerServices; +using Global.Shared.Logging; +using NLog; + +namespace Global +{ + public class TorchLogger : LogFormatter, IPluginLogger + { + private readonly Logger _logger; + + public TorchLogger(string pluginName) : base("") + { + _logger = LogManager.GetLogger(pluginName); + } + + public bool IsTraceEnabled => _logger.IsTraceEnabled && GlobalPlugin.IsLoggingLevel(LoggingLevel.Trace); + public bool IsDebugEnabled => _logger.IsDebugEnabled && GlobalPlugin.IsLoggingLevel(LoggingLevel.Debug); + public bool IsInfoEnabled => _logger.IsInfoEnabled && GlobalPlugin.IsLoggingLevel(LoggingLevel.Info); + public bool IsWarningEnabled => _logger.IsWarnEnabled && GlobalPlugin.IsLoggingLevel(LoggingLevel.Warning); + public bool IsErrorEnabled => _logger.IsErrorEnabled && GlobalPlugin.IsLoggingLevel(LoggingLevel.Error); + public bool IsCriticalEnabled => _logger.IsFatalEnabled && GlobalPlugin.IsLoggingLevel(LoggingLevel.Fatal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Trace(Exception ex, string message, params object[] data) + { + if (!IsTraceEnabled) + return; + + _logger.Trace(Format(ex, message, data)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Debug(Exception ex, string message, params object[] data) + { + if (!IsDebugEnabled) + return; + + _logger.Debug(Format(ex, message, data)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Info(Exception ex, string message, params object[] data) + { + if (!IsInfoEnabled) + return; + + _logger.Info(Format(ex, message, data)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Warning(Exception ex, string message, params object[] data) + { + if (!IsWarningEnabled) + return; + + _logger.Warn(Format(ex, message, data)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Error(Exception ex, string message, params object[] data) + { + if (!IsErrorEnabled) + return; + + _logger.Error(Format(ex, message, data)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Critical(Exception ex, string message, params object[] data) + { + if (!IsCriticalEnabled) + return; + + _logger.Fatal(Format(ex, message, data)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Trace(string message, params object[] data) + { + Trace(null, message, data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Debug(string message, params object[] data) + { + Debug(null, message, data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Info(string message, params object[] data) + { + Info(null, message, data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Warning(string message, params object[] data) + { + Warning(null, message, data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Error(string message, params object[] data) + { + Error(null, message, data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Critical(string message, params object[] data) + { + Critical(null, message, data); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/Util/MTObservableCollection.cs b/GlobalTorch/Util/MTObservableCollection.cs new file mode 100644 index 0000000..9797cd1 --- /dev/null +++ b/GlobalTorch/Util/MTObservableCollection.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Windows.Threading; + +namespace Global.Util +{ + public class MtObservableCollection : ObservableCollection + { + public override event NotifyCollectionChangedEventHandler CollectionChanged; + + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + var collectionChanged = CollectionChanged; + if (collectionChanged == null) return; + foreach (var @delegate in collectionChanged.GetInvocationList()) + { + var nh = (NotifyCollectionChangedEventHandler)@delegate; + var dispObj = nh.Target as DispatcherObject; + var dispatcher = dispObj?.Dispatcher; + if (dispatcher != null && !dispatcher.CheckAccess()) + { + dispatcher.BeginInvoke( + (Action)(() => nh.Invoke(this, + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))), + DispatcherPriority.DataBind); + continue; + } + + nh.Invoke(this, e); + } + } + } +} \ No newline at end of file diff --git a/GlobalTorch/Util/SEUtils.cs b/GlobalTorch/Util/SEUtils.cs new file mode 100644 index 0000000..d175371 --- /dev/null +++ b/GlobalTorch/Util/SEUtils.cs @@ -0,0 +1,60 @@ +using Sandbox.Game.World; + +namespace Global.Util +{ + public static class SEUtils + { + public static MyIdentity TryGetIdentity(string playerNameOrSteamId) + { + var player = MySession.Static.Players.GetPlayerByName(playerNameOrSteamId); + if (player != null) return player.Identity; + player = MySession.Static.Players.GetPlayerById(new MyPlayer.PlayerId(ulong.Parse(playerNameOrSteamId))); + if (player != null) return player.Identity; + if (MySession.Static.Players.TryGetPlayerBySteamId(ulong.Parse(playerNameOrSteamId), out player)) + return player.Identity; + + foreach (var identity in MySession.Static.Players.GetAllIdentities()) + { + if (identity.DisplayName == playerNameOrSteamId) return identity; + + if (!ulong.TryParse(playerNameOrSteamId, out var steamId)) continue; + + var id = MySession.Static.Players.TryGetSteamId(identity.IdentityId); + if (id == steamId) return identity; + if (identity.IdentityId == (long)steamId) return identity; + } + + return null; + } + + public static MyIdentity GetIdentityByNameOrId(string playerNameOrSteamId) + { + var player = MySession.Static.Players.GetPlayerByName(playerNameOrSteamId); + if (player != null) return player.Identity; + player = MySession.Static.Players.GetPlayerById(new MyPlayer.PlayerId(ulong.Parse(playerNameOrSteamId))); + if (player != null) return player.Identity; + + foreach (var identity in MySession.Static.Players.GetAllIdentities()) + { + if (identity.DisplayName == playerNameOrSteamId) return identity; + + if (!ulong.TryParse(playerNameOrSteamId, out var steamId)) continue; + + var id = MySession.Static.Players.TryGetSteamId(identity.IdentityId); + + if (id == steamId) return identity; + if (identity.IdentityId == (long)steamId) return identity; + } + + return null; + } + + public static string GetPlayerName(ulong steamId) + { + var id = GetIdentityByNameOrId(steamId.ToString()); + if (id != null && id.DisplayName != null) return id.DisplayName; + + return steamId.ToString(); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/Util/SendBuffer.cs b/GlobalTorch/Util/SendBuffer.cs new file mode 100644 index 0000000..5ad28f6 --- /dev/null +++ b/GlobalTorch/Util/SendBuffer.cs @@ -0,0 +1,36 @@ +using System; +using VRage.Library.Collections; + +namespace Global.Util +{ + internal class SendBuffer : IBitSerializable + { + public int NumElements; + public long SenderUserId; + public byte[] VoiceDataBuffer; + + public bool Serialize(BitStream stream, bool validate, bool acceptAndSetValue = true) + { + if (stream.Reading) + { + SenderUserId = stream.ReadInt64(); + NumElements = stream.ReadInt32(); + ArrayExtensions.EnsureCapacity(ref VoiceDataBuffer, NumElements); + stream.ReadBytes(VoiceDataBuffer, 0, NumElements); + } + else + { + stream.WriteInt64(SenderUserId); + stream.WriteInt32(NumElements); + stream.WriteBytes(VoiceDataBuffer, 0, NumElements); + } + + return true; + } + + public static implicit operator BitReaderWriter(SendBuffer buffer) + { + return new BitReaderWriter(buffer); + } + } +} \ No newline at end of file diff --git a/GlobalTorch/manifest.xml b/GlobalTorch/manifest.xml new file mode 100644 index 0000000..b95ca74 --- /dev/null +++ b/GlobalTorch/manifest.xml @@ -0,0 +1,7 @@ + + + GlobalTorch + FB668D21-68CE-4037-9564-807E2709836C + None + 0.1.0 + \ No newline at end of file diff --git a/GlobalTorch/opus.dll b/GlobalTorch/opus.dll new file mode 100644 index 0000000000000000000000000000000000000000..4265850b3ed1b0163b6d6e4925418f0729873e0d GIT binary patch literal 426640 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 z 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⋘rIk%9=HoUSeCp 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