zz
This commit is contained in:
174
GlobalShared/API/GlobalAPI.cs
Normal file
174
GlobalShared/API/GlobalAPI.cs
Normal file
@@ -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<IGridData> GetAllGrids()
|
||||||
|
{
|
||||||
|
return OcTreeHandler.GridTree.GetAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Experimental Spatial Data API
|
||||||
|
|
||||||
|
public static IEnumerable<IGridData> LocateGridsExperimental(BoundingBoxD box)
|
||||||
|
{
|
||||||
|
return OcTreeHandler.GridTree.GetEnumerable(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IGridData> LocateGridsExperimental(BoundingSphereD sphere)
|
||||||
|
{
|
||||||
|
return OcTreeHandler.GridTree.GetEnumerable(sphere);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IGridData> LocateGridsExperimental(MyOrientedBoundingBoxD box)
|
||||||
|
{
|
||||||
|
return OcTreeHandler.GridTree.GetEnumerable(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IControllableEntityData> LocateCharactersExperimental(BoundingBoxD box)
|
||||||
|
{
|
||||||
|
return OcTreeHandler.CharacterTree.GetEnumerable(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IControllableEntityData> LocateCharactersExperimental(BoundingSphereD sphere)
|
||||||
|
{
|
||||||
|
return OcTreeHandler.CharacterTree.GetEnumerable(sphere);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<IControllableEntityData> LocateCharactersExperimental(MyOrientedBoundingBoxD box)
|
||||||
|
{
|
||||||
|
return OcTreeHandler.CharacterTree.GetEnumerable(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Spatial Data API
|
||||||
|
|
||||||
|
public static void LocateGrids(List<IGridData> list, BoundingBoxD box, long flags = 0L)
|
||||||
|
{
|
||||||
|
OcTreeHandler.GridTree.GetExact(list, box, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LocateGrids(List<IGridData> list, BoundingSphereD sphere, long flags = 0L)
|
||||||
|
{
|
||||||
|
OcTreeHandler.GridTree.GetExact(list, sphere, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LocateGrids(List<IGridData> list, MyOrientedBoundingBoxD box, long flags = 0L)
|
||||||
|
{
|
||||||
|
OcTreeHandler.GridTree.GetExact(list, box, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LocateCharacters(List<IControllableEntityData> list, BoundingBoxD box, long flags = 0L)
|
||||||
|
{
|
||||||
|
OcTreeHandler.CharacterTree.GetExact(list, box, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LocateCharacters(List<IControllableEntityData> list, BoundingSphereD sphere,
|
||||||
|
long flags = 0L)
|
||||||
|
{
|
||||||
|
OcTreeHandler.CharacterTree.GetExact(list, sphere, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LocateCharacters(List<IControllableEntityData> list, MyOrientedBoundingBoxD box,
|
||||||
|
long flags = 0L)
|
||||||
|
{
|
||||||
|
OcTreeHandler.CharacterTree.GetExact(list, box, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Extensions
|
||||||
|
|
||||||
|
private static readonly List<GridData> _internalGridList = new List<GridData>();
|
||||||
|
|
||||||
|
private static readonly List<ControllableEntityData>
|
||||||
|
_internalCharacterList = new List<ControllableEntityData>();
|
||||||
|
|
||||||
|
private static void GetExact(this GenericOcTree<GridData> ocTree, List<IGridData> list, BoundingSphereD sphere,
|
||||||
|
long flags = 0L)
|
||||||
|
{
|
||||||
|
_internalGridList.Clear();
|
||||||
|
ocTree.GetExact(_internalGridList, sphere, flags);
|
||||||
|
list.AddRange(_internalGridList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GetExact(this GenericOcTree<GridData> ocTree, List<IGridData> list, BoundingBoxD box,
|
||||||
|
long flags = 0L)
|
||||||
|
{
|
||||||
|
_internalGridList.Clear();
|
||||||
|
ocTree.GetExact(_internalGridList, box, flags);
|
||||||
|
list.AddRange(_internalGridList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GetExact(this GenericOcTree<GridData> ocTree, List<IGridData> list,
|
||||||
|
MyOrientedBoundingBoxD box,
|
||||||
|
long flags = 0L)
|
||||||
|
{
|
||||||
|
_internalGridList.Clear();
|
||||||
|
ocTree.GetExact(_internalGridList, box, flags);
|
||||||
|
list.AddRange(_internalGridList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GetExact(this GenericOcTree<ControllableEntityData> ocTree,
|
||||||
|
List<IControllableEntityData> list, BoundingSphereD sphere,
|
||||||
|
long flags = 0L)
|
||||||
|
{
|
||||||
|
_internalCharacterList.Clear();
|
||||||
|
ocTree.GetExact(_internalCharacterList, sphere, flags);
|
||||||
|
list.AddRange(_internalCharacterList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GetExact(this GenericOcTree<ControllableEntityData> ocTree,
|
||||||
|
List<IControllableEntityData> list, BoundingBoxD box,
|
||||||
|
long flags = 0L)
|
||||||
|
{
|
||||||
|
_internalCharacterList.Clear();
|
||||||
|
ocTree.GetExact(_internalCharacterList, box, flags);
|
||||||
|
list.AddRange(_internalCharacterList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GetExact(this GenericOcTree<ControllableEntityData> ocTree,
|
||||||
|
List<IControllableEntityData> list,
|
||||||
|
MyOrientedBoundingBoxD box,
|
||||||
|
long flags = 0L)
|
||||||
|
{
|
||||||
|
_internalCharacterList.Clear();
|
||||||
|
ocTree.GetExact(_internalCharacterList, box, flags);
|
||||||
|
list.AddRange(_internalCharacterList);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
17
GlobalShared/API/IBooleanFunction.cs
Normal file
17
GlobalShared/API/IBooleanFunction.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
9
GlobalShared/API/IControllableEntityData.cs
Normal file
9
GlobalShared/API/IControllableEntityData.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Sandbox.Game.Entities;
|
||||||
|
|
||||||
|
namespace Global.Shared.API
|
||||||
|
{
|
||||||
|
public interface IControllableEntityData
|
||||||
|
{
|
||||||
|
IMyControllableEntity Entity { get; }
|
||||||
|
}
|
||||||
|
}
|
9
GlobalShared/API/ICustomData.cs
Normal file
9
GlobalShared/API/ICustomData.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Global.Shared.API
|
||||||
|
{
|
||||||
|
public interface ICustomData : IDisposable
|
||||||
|
{
|
||||||
|
IGridData GridData { get; }
|
||||||
|
}
|
||||||
|
}
|
7
GlobalShared/API/ICustomManager.cs
Normal file
7
GlobalShared/API/ICustomManager.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Global.Shared.API
|
||||||
|
{
|
||||||
|
public interface ICustomManager<out T> where T : ICustomData
|
||||||
|
{
|
||||||
|
T CreateData(IGridData data);
|
||||||
|
}
|
||||||
|
}
|
26
GlobalShared/API/IGridData.cs
Normal file
26
GlobalShared/API/IGridData.cs
Normal file
@@ -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<MyCubeBlock> GetBlocks(MyObjectBuilderType type);
|
||||||
|
|
||||||
|
IEnumerable<MyFunctionalBlock> GetAllFunctionalBlocks();
|
||||||
|
|
||||||
|
bool Contains(MyObjectBuilderType type);
|
||||||
|
}
|
||||||
|
}
|
34
GlobalShared/API/OcTreeVariables.cs
Normal file
34
GlobalShared/API/OcTreeVariables.cs
Normal file
@@ -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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
GlobalShared/Config/IPluginConfig.cs
Normal file
14
GlobalShared/Config/IPluginConfig.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
27
GlobalShared/Events/BlockEvents.cs
Normal file
27
GlobalShared/Events/BlockEvents.cs
Normal file
@@ -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<IMyControllableEntity> OnPlayerControlAcquired;
|
||||||
|
public static Action<IMyControllableEntity> OnPlayerControlReleased;
|
||||||
|
|
||||||
|
public static Action<MyCubeBlock> OnBlockAdded;
|
||||||
|
public static Action<MyCubeBlock> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
GlobalShared/Events/GridEvents.cs
Normal file
13
GlobalShared/Events/GridEvents.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using Sandbox.Game.Entities;
|
||||||
|
|
||||||
|
namespace Global.Shared.Events
|
||||||
|
{
|
||||||
|
public class GridEvents
|
||||||
|
{
|
||||||
|
public static Action<MyCubeGrid> GridCreated = l => { };
|
||||||
|
public static Action<MyCubeGrid> GridRemoved = l => { };
|
||||||
|
public static Action<MyCubeGrid, MyCubeGrid> SubGridAdded = (parent, child) => { };
|
||||||
|
public static Action<MyCubeGrid, MyCubeGrid> SubGridRemoved = (parent, child) => { };
|
||||||
|
}
|
||||||
|
}
|
49
GlobalShared/GlobalShared.projitems
Normal file
49
GlobalShared/GlobalShared.projitems
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||||
|
<HasSharedItems>true</HasSharedItems>
|
||||||
|
<SharedGUID>B127E405-3C0F-48C3-9F8E-7437E8F4E106</SharedGUID>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Label="Configuration">
|
||||||
|
<Import_RootNamespace>Global.Shared</Import_RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)API\GlobalAPI.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)API\IBooleanFunction.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)API\IControllableEntityData.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)API\ICustomData.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)API\ICustomManager.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)API\IGridData.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)API\OcTreeVariables.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Config\IPluginConfig.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Events\BlockEvents.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Events\GridEvents.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Logging\IPluginLogger.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Logging\LogFormatter.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Logging\LoggingLevel.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)OcTree\Bag.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)OcTree\Data\ControllableEntityData.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)OcTree\Data\GridData.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)OcTree\Data\IOcTreeData.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)OcTree\GenericOcTree.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)OcTree\IContainer.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)OcTree\OcTree.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)OcTree\OcTreeHandler.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)OcTree\Pool.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Patches\CubeGridPatch.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Patches\EntityNamePatch.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Patches\MyConveyorLinePatch.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Patches\MyGamePruningStructurePatch.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Patches\MyMechanicalConnectionBlockBasePatch.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Patches\MyTerminalBlockPatch.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Patches\TerminalSystemPatch.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Patching\PatchHelpers.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Plugin\GlobalInstance.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Plugin\GlobalStatic.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Plugin\IGlobalPlugin.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Util\KeenExtensions.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Util\NexusUtils.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Util\UintCache.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
13
GlobalShared/GlobalShared.shproj
Normal file
13
GlobalShared/GlobalShared.shproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>{C5784FE0-CF0A-4870-9DEF-7BEA8B64C01A}</ProjectGuid>
|
||||||
|
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||||
|
<RootNamespace>Shared</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||||
|
<Import Project="GlobalShared.projitems" Label="Shared" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||||
|
</Project>
|
28
GlobalShared/Logging/IPluginLogger.cs
Normal file
28
GlobalShared/Logging/IPluginLogger.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
90
GlobalShared/Logging/LogFormatter.cs
Normal file
90
GlobalShared/Logging/LogFormatter.cs
Normal file
@@ -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> _stringBuilder = new ThreadLocal<StringBuilder>();
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
GlobalShared/Logging/LoggingLevel.cs
Normal file
20
GlobalShared/Logging/LoggingLevel.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
173
GlobalShared/OcTree/Bag.cs
Normal file
173
GlobalShared/OcTree/Bag.cs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Global.Shared.OcTree
|
||||||
|
{
|
||||||
|
public interface IImmutableBag<E> : IEnumerable<E>
|
||||||
|
{
|
||||||
|
/// <summary>Returns the element at the specified position in Bag</summary>
|
||||||
|
/// <param name="index">the index of the element to return</param>
|
||||||
|
/// <returns>the element at the specified position in bag</returns>
|
||||||
|
E Get(int index);
|
||||||
|
|
||||||
|
/// <summary>Returns the number of elements in this Bag</summary>
|
||||||
|
/// <returns>the number of elements in this bag</returns>
|
||||||
|
int Size();
|
||||||
|
|
||||||
|
bool IsEmpty();
|
||||||
|
|
||||||
|
bool Contains(E element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Bag<E> : IImmutableBag<E>
|
||||||
|
{
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Returns the number of elements in this Bag</summary>
|
||||||
|
/// <returns>the number of elements in this bag</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Returns the number of elements this bag can hold without growing.</summary>
|
||||||
|
/// <returns>the number of elements this bag can hold without growing</returns>
|
||||||
|
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<E> GetEnumerator()
|
||||||
|
{
|
||||||
|
if (it == null) it = new BagEnumerator(this);
|
||||||
|
|
||||||
|
it.Cursor = -1;
|
||||||
|
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BagEnumerator : IEnumerator<E>
|
||||||
|
{
|
||||||
|
private readonly Bag<E> bag;
|
||||||
|
internal int Cursor;
|
||||||
|
|
||||||
|
public BagEnumerator(Bag<E> 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
|
||||||
|
}
|
||||||
|
}
|
51
GlobalShared/OcTree/Data/ControllableEntityData.cs
Normal file
51
GlobalShared/OcTree/Data/ControllableEntityData.cs
Normal file
@@ -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<long> ChildIds { get; } = new List<long>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
334
GlobalShared/OcTree/Data/GridData.cs
Normal file
334
GlobalShared/OcTree/Data/GridData.cs
Normal file
@@ -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<MyObjectBuilderType, ArrayList> Blocks;
|
||||||
|
public MyConcurrentList<MyFunctionalBlock> FunctionalBlocks;
|
||||||
|
public List<MyCubeGrid> 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<MyCubeBlock> GetBlocks(MyObjectBuilderType type)
|
||||||
|
{
|
||||||
|
// TODO: Improve memory allocation of this.
|
||||||
|
return Blocks.TryGetValue(type, out var list)
|
||||||
|
? list.Cast<MyCubeBlock>().ToArray()
|
||||||
|
: Enumerable.Empty<MyCubeBlock>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public IEnumerable<MyFunctionalBlock> GetAllFunctionalBlocks()
|
||||||
|
{
|
||||||
|
return FunctionalBlocks ?? Enumerable.Empty<MyFunctionalBlock>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(MyObjectBuilderType type)
|
||||||
|
{
|
||||||
|
return Blocks.ContainsKey(type) && Blocks[type].Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
Recompute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long Id { get; set; }
|
||||||
|
public List<long> 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<MyObjectBuilderType, ArrayList>();
|
||||||
|
FunctionalBlocks = new MyConcurrentList<MyFunctionalBlock>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
GlobalShared/OcTree/Data/IOcTreeData.cs
Normal file
17
GlobalShared/OcTree/Data/IOcTreeData.cs
Normal file
@@ -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<long> ChildIds { get; }
|
||||||
|
long Flags { get; }
|
||||||
|
BoundingBoxD BoundingBox { get; }
|
||||||
|
|
||||||
|
void Update();
|
||||||
|
void Recompute();
|
||||||
|
}
|
||||||
|
}
|
986
GlobalShared/OcTree/GenericOcTree.cs
Normal file
986
GlobalShared/OcTree/GenericOcTree.cs
Normal file
@@ -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<T> : IPoolable where T : IOcTreeData
|
||||||
|
{
|
||||||
|
private readonly GenericContainer<T> _bounds;
|
||||||
|
|
||||||
|
private readonly List<GenericContainer<T>> _containersToAdd = new List<GenericContainer<T>>();
|
||||||
|
|
||||||
|
private readonly Pool<GenericContainer<T>> _cPool =
|
||||||
|
new Pool<GenericContainer<T>>(() => new GenericContainer<T>());
|
||||||
|
|
||||||
|
private readonly GenericOcTree<T>[] _nodes = new GenericOcTree<T>[8];
|
||||||
|
|
||||||
|
private readonly Pool<GenericOcTree<T>> _otPool = new Pool<GenericOcTree<T>>(() => new GenericOcTree<T>());
|
||||||
|
|
||||||
|
private int _capacity;
|
||||||
|
private Bag<GenericContainer<T>> _containers;
|
||||||
|
|
||||||
|
private Dictionary<long, GenericContainer<T>> _idToContainer;
|
||||||
|
private int _maxDepth;
|
||||||
|
|
||||||
|
private GenericOcTree<T> _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<T>().Set(x, y, z, width, height, depth);
|
||||||
|
_capacity = capacity;
|
||||||
|
_maxDepth = maxDepth;
|
||||||
|
_containers = new Bag<GenericContainer<T>>(capacity);
|
||||||
|
_idToContainer = new Dictionary<long, GenericContainer<T>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<T> Init(int currentTreeDepth, float x, float y, float z, float width, float height,
|
||||||
|
float depth,
|
||||||
|
GenericOcTree<T> 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<long, GenericContainer<T>>();
|
||||||
|
_containers = new Bag<GenericContainer<T>>(_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<T>();
|
||||||
|
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<T> 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<T> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns entityIds of entities that are inside the OcTrees that contain given point.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entityIds">List to fill</param>
|
||||||
|
/// <param name="x">X position</param>
|
||||||
|
/// <param name="y">Y position</param>
|
||||||
|
/// <param name="z">Z position</param>
|
||||||
|
public List<long> Get(List<long> 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<long> Get(List<long> 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<long> Get(List<long> 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<long> Get(List<long> 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<long> GetExact(List<long> 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<long> GetExact(List<long> 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<long> Get(List<long> 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<long> Get(List<long> 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<long> GetExact(List<long> 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<long> GetExact(List<long> 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<long> GetExact(List<long> 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<long> GetExact(List<long> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> : IPoolable, IContainer where T : IOcTreeData
|
||||||
|
{
|
||||||
|
private static readonly BoundingBoxD ReusableBox = new BoundingBoxD();
|
||||||
|
public List<long> ChildIds;
|
||||||
|
public long EntityId;
|
||||||
|
|
||||||
|
public long Flags;
|
||||||
|
public GenericOcTree<T> 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<T> 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<long>, IEnumerable<long>> Update()
|
||||||
|
{
|
||||||
|
if (Value == null) return new KeyValuePair<IEnumerable<long>, IEnumerable<long>>(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<long>, IEnumerable<long>>(
|
||||||
|
previousChildIds.Where(e => !ChildIds.Contains(e)),
|
||||||
|
ChildIds.Where(e => !previousChildIds.Contains(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenericContainer<T> 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<T> c)
|
||||||
|
{
|
||||||
|
return Contains(c.X, c.Y, c.Z, c.Width, c.Height, c.Depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
GlobalShared/OcTree/IContainer.cs
Normal file
12
GlobalShared/OcTree/IContainer.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
707
GlobalShared/OcTree/OcTree.cs
Normal file
707
GlobalShared/OcTree/OcTree.cs
Normal file
@@ -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<Container> cPool = new Pool<Container>(() => new Container());
|
||||||
|
private static readonly Pool<OcTree> otPool = new Pool<OcTree>(() => new OcTree());
|
||||||
|
|
||||||
|
private readonly Container _bounds;
|
||||||
|
|
||||||
|
private readonly List<Container> _containersToAdd = new List<Container>();
|
||||||
|
private readonly OcTree[] nodes = new OcTree[8];
|
||||||
|
private Bag<Container> _containers;
|
||||||
|
|
||||||
|
private int capacity;
|
||||||
|
|
||||||
|
private Dictionary<long, Container> 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<Container>(capacity);
|
||||||
|
idToContainer = new Dictionary<long, Container>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<long, Container>();
|
||||||
|
_containers = new Bag<Container>(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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns entityIds of entities that are inside the OcTrees that contain given point.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entityIds">List to fill</param>
|
||||||
|
/// <param name="x">X position</param>
|
||||||
|
/// <param name="y">Y position</param>
|
||||||
|
/// <param name="z">Z position</param>
|
||||||
|
public List<long> Get(List<long> 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<long> Get(List<long> 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<long> Get(List<long> 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<long> Get(List<long> 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<long> GetExact(List<long> 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<long> GetExact(List<long> 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<long> Get(List<long> 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<long> Get(List<long> 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<long> GetExact(List<long> 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<long> GetExact(List<long> 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<long> GetExact(List<long> 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<long> GetExact(List<long> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
140
GlobalShared/OcTree/OcTreeHandler.cs
Normal file
140
GlobalShared/OcTree/OcTreeHandler.cs
Normal file
@@ -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<GridData> GridTree { get; private set; }
|
||||||
|
|
||||||
|
public GenericOcTree<ControllableEntityData> 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<GridData>(offset.X - x, offset.Y - y, offset.Z - z, x * 2, y * 2, z * 2,
|
||||||
|
cfg.Capacity, cfg.MaxDepth);
|
||||||
|
CharacterTree =
|
||||||
|
new GenericOcTree<ControllableEntityData>(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<IMyCubeGrid>());
|
||||||
|
var largestGrid = grid;
|
||||||
|
if (grids.Count > 1)
|
||||||
|
foreach (var g in grids.Cast<MyCubeGrid>())
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
GlobalShared/OcTree/Pool.cs
Normal file
36
GlobalShared/OcTree/Pool.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Global.Shared.OcTree
|
||||||
|
{
|
||||||
|
public interface IPoolable
|
||||||
|
{
|
||||||
|
void Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Pool<E> where E : IPoolable
|
||||||
|
{
|
||||||
|
private readonly Func<E> creator;
|
||||||
|
private readonly List<E> pool = new List<E>();
|
||||||
|
|
||||||
|
public Pool(Func<E> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
GlobalShared/Patches/CubeGridPatch.cs
Normal file
66
GlobalShared/Patches/CubeGridPatch.cs
Normal file
@@ -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<int> CallDepth = new ThreadLocal<int>();
|
||||||
|
|
||||||
|
public static ThreadLocal<ulong> PlayerId = new ThreadLocal<ulong>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
GlobalShared/Patches/EntityNamePatch.cs
Normal file
85
GlobalShared/Patches/EntityNamePatch.cs
Normal file
@@ -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<long, string> EntityNameReverseLookup =
|
||||||
|
new ConcurrentDictionary<long, string>();
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
GlobalShared/Patches/MyConveyorLinePatch.cs
Normal file
17
GlobalShared/Patches/MyConveyorLinePatch.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
GlobalShared/Patches/MyGamePruningStructurePatch.cs
Normal file
55
GlobalShared/Patches/MyGamePruningStructurePatch.cs
Normal file
@@ -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<IGridData> _gridQueryStorage = new List<IGridData>();
|
||||||
|
private static readonly List<IControllableEntityData> _queryStorage = new List<IControllableEntityData>();
|
||||||
|
|
||||||
|
// [HarmonyPatch("GetTopMostEntitiesInBox")]
|
||||||
|
// [HarmonyPrefix]
|
||||||
|
public static bool GetTopMostEntitiesInBox(
|
||||||
|
ref BoundingBoxD box,
|
||||||
|
List<MyEntity> result,
|
||||||
|
MyEntityQueryType qtype = MyEntityQueryType.Both)
|
||||||
|
{
|
||||||
|
GetTopMostBox(ref box, result, qtype);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GetTopMostBox(ref BoundingBoxD box, List<MyEntity> 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<MyEntity> result,
|
||||||
|
MyEntityQueryType qtype = MyEntityQueryType.Both)
|
||||||
|
{
|
||||||
|
GetTopMostBox(ref box, result, qtype);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
GlobalShared/Patches/MyMechanicalConnectionBlockBasePatch.cs
Normal file
33
GlobalShared/Patches/MyMechanicalConnectionBlockBasePatch.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
GlobalShared/Patches/MyTerminalBlockPatch.cs
Normal file
24
GlobalShared/Patches/MyTerminalBlockPatch.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
GlobalShared/Patches/TerminalSystemPatch.cs
Normal file
31
GlobalShared/Patches/TerminalSystemPatch.cs
Normal file
@@ -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<long> OwnerCache = new UintCache<long>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
GlobalShared/Patching/PatchHelpers.cs
Normal file
27
GlobalShared/Patching/PatchHelpers.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
GlobalShared/Plugin/GlobalInstance.cs
Normal file
24
GlobalShared/Plugin/GlobalInstance.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
GlobalShared/Plugin/GlobalStatic.cs
Normal file
40
GlobalShared/Plugin/GlobalStatic.cs
Normal file
@@ -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<IMyEntity>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
GlobalShared/Plugin/IGlobalPlugin.cs
Normal file
15
GlobalShared/Plugin/IGlobalPlugin.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
35
GlobalShared/Properties/AssemblyInfo.cs
Normal file
35
GlobalShared/Properties/AssemblyInfo.cs
Normal file
@@ -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")]
|
19
GlobalShared/Util/KeenExtensions.cs
Normal file
19
GlobalShared/Util/KeenExtensions.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
GlobalShared/Util/NexusUtils.cs
Normal file
42
GlobalShared/Util/NexusUtils.cs
Normal file
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
93
GlobalShared/Util/UintCache.cs
Normal file
93
GlobalShared/Util/UintCache.cs
Normal file
@@ -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<TK>
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<TK, ulong> cache = new ConcurrentDictionary<TK, ulong>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
263
GlobalTorch/API/Discord/DiscordStructs.cs
Normal file
263
GlobalTorch/API/Discord/DiscordStructs.cs
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Global.API.Discord
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Discord message data object
|
||||||
|
/// </summary>
|
||||||
|
public struct DiscordMessage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Message content
|
||||||
|
/// </summary>
|
||||||
|
public string Content;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read message to everyone on the channel
|
||||||
|
/// </summary>
|
||||||
|
public bool TTS;
|
||||||
|
|
||||||
|
public DiscordAllowedMentions AllowedMentions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Webhook profile username to be shown
|
||||||
|
/// </summary>
|
||||||
|
public string Username;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Webhook profile avater to be shown
|
||||||
|
/// </summary>
|
||||||
|
public string AvatarUrl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of embeds
|
||||||
|
/// </summary>
|
||||||
|
public List<DiscordEmbed> Embeds;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return DiscordUtil.StructToJson(this).ToString(Formatting.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DiscordAllowedMentions
|
||||||
|
{
|
||||||
|
public List<string> Parse;
|
||||||
|
public List<string> Roles;
|
||||||
|
public List<string> Users;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return DiscordUtil.StructToJson(this).ToString(Formatting.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discord embed data object
|
||||||
|
/// </summary>
|
||||||
|
public struct DiscordEmbed
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Embed title
|
||||||
|
/// </summary>
|
||||||
|
public string Title;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embed description
|
||||||
|
/// </summary>
|
||||||
|
public string Description;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embed url
|
||||||
|
/// </summary>
|
||||||
|
public string Url;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embed timestamp
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? Timestamp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embed color
|
||||||
|
/// </summary>
|
||||||
|
public Color? Color;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embed footer
|
||||||
|
/// </summary>
|
||||||
|
public EmbedFooter? Footer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embed image
|
||||||
|
/// </summary>
|
||||||
|
public EmbedMedia? Image;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embed thumbnail
|
||||||
|
/// </summary>
|
||||||
|
public EmbedMedia? Thumbnail;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embed video
|
||||||
|
/// </summary>
|
||||||
|
public EmbedMedia? Video;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embed provider
|
||||||
|
/// </summary>
|
||||||
|
public EmbedProvider? Provider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embed author
|
||||||
|
/// </summary>
|
||||||
|
public EmbedAuthor? Author;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Embed fields list
|
||||||
|
/// </summary>
|
||||||
|
public List<EmbedField> Fields;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return DiscordUtil.StructToJson(this).ToString(Formatting.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discord embed footer data object
|
||||||
|
/// </summary>
|
||||||
|
public struct EmbedFooter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Footer text
|
||||||
|
/// </summary>
|
||||||
|
public string Text;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Footer icon
|
||||||
|
/// </summary>
|
||||||
|
public string IconUrl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Footer icon proxy
|
||||||
|
/// </summary>
|
||||||
|
public string ProxyIconUrl;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return DiscordUtil.StructToJson(this).ToString(Formatting.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discord embed media data object (images/thumbs/videos)
|
||||||
|
/// </summary>
|
||||||
|
public struct EmbedMedia
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Media url
|
||||||
|
/// </summary>
|
||||||
|
public string Url;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Media proxy url
|
||||||
|
/// </summary>
|
||||||
|
public string ProxyUrl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Media height
|
||||||
|
/// </summary>
|
||||||
|
public int? Height;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Media width
|
||||||
|
/// </summary>
|
||||||
|
public int? Width;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return DiscordUtil.StructToJson(this).ToString(Formatting.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discord embed provider data object
|
||||||
|
/// </summary>
|
||||||
|
public struct EmbedProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provider name
|
||||||
|
/// </summary>
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provider url
|
||||||
|
/// </summary>
|
||||||
|
public string Url;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return DiscordUtil.StructToJson(this).ToString(Formatting.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discord embed author data object
|
||||||
|
/// </summary>
|
||||||
|
public struct EmbedAuthor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Author name
|
||||||
|
/// </summary>
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Author url
|
||||||
|
/// </summary>
|
||||||
|
public string Url;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Author icon
|
||||||
|
/// </summary>
|
||||||
|
public string IconUrl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Author icon proxy
|
||||||
|
/// </summary>
|
||||||
|
public string ProxyIconUrl;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return DiscordUtil.StructToJson(this).ToString(Formatting.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discord embed field data object
|
||||||
|
/// </summary>
|
||||||
|
public struct EmbedField
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Field name
|
||||||
|
/// </summary>
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Field value
|
||||||
|
/// </summary>
|
||||||
|
public string Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Field align
|
||||||
|
/// </summary>
|
||||||
|
public bool InLine;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return DiscordUtil.StructToJson(this).ToString(Formatting.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
129
GlobalTorch/API/Discord/DiscordUtil.cs
Normal file
129
GlobalTorch/API/Discord/DiscordUtil.cs
Normal file
@@ -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" };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert Color object into hex integer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="color">Color to be converted</param>
|
||||||
|
/// <returns>Converted hex integer</returns>
|
||||||
|
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<char>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
GlobalTorch/API/Discord/DiscordWebhook.cs
Normal file
77
GlobalTorch/API/Discord/DiscordWebhook.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace Global.API.Discord
|
||||||
|
{
|
||||||
|
public class DiscordWebhook
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Webhook url
|
||||||
|
/// </summary>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send webhook message
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
GlobalTorch/API/EventResult.cs
Normal file
9
GlobalTorch/API/EventResult.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Global.API
|
||||||
|
{
|
||||||
|
public enum EventResult
|
||||||
|
{
|
||||||
|
Allow,
|
||||||
|
Continue,
|
||||||
|
Deny
|
||||||
|
}
|
||||||
|
}
|
28
GlobalTorch/API/GlobalModApi.cs
Normal file
28
GlobalTorch/API/GlobalModApi.cs
Normal file
@@ -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<IMyCubeBlock> list, IMyCubeGrid grid, MyObjectBuilderType type)
|
||||||
|
{
|
||||||
|
list.AddRange(grid.GetFatBlocks<IMyCubeBlock>().Where(block => block.BlockDefinition.TypeId == type));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GetAllBlocksOfTypeId(List<IMyCubeBlock> list, long gridId, MyObjectBuilderType type)
|
||||||
|
{
|
||||||
|
var grid = MyAPIGateway.Entities.GetEntityById(gridId) as IMyCubeGrid;
|
||||||
|
if (grid == null) return;
|
||||||
|
list.AddRange(grid.GetFatBlocks<IMyCubeBlock>().Where(block => block.BlockDefinition.TypeId == type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
GlobalTorch/API/GlobalServerModApi.cs
Normal file
28
GlobalTorch/API/GlobalServerModApi.cs
Normal file
@@ -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<IMyCubeBlock> list, IMyCubeGrid grid, MyObjectBuilderType type)
|
||||||
|
{
|
||||||
|
GetAllBlocksOfTypeId(list, grid.EntityId, type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GetAllBlocksOfTypeId(List<IMyCubeBlock> list, long gridId, MyObjectBuilderType type)
|
||||||
|
{
|
||||||
|
list.AddRange(GlobalAPI.GetGridById(gridId).GetBlocks(type));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
GlobalTorch/API/Libraries/SENetworkAPI/Client.cs
Normal file
104
GlobalTorch/API/Libraries/SENetworkAPI/Client.cs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using Sandbox.ModAPI;
|
||||||
|
using VRage;
|
||||||
|
using VRage.Utils;
|
||||||
|
using VRageMath;
|
||||||
|
|
||||||
|
namespace SENetworkAPI
|
||||||
|
{
|
||||||
|
public class Client : NetworkAPI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles communication with the server
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="comId">Identifies the channel to pass information to and from this mod</param>
|
||||||
|
/// <param name="keyword">identifies what chat entries should be captured and sent to the server</param>
|
||||||
|
public Client(ushort comId, string modName, string keyword = null) : base(comId, modName, keyword)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet to the server
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandString">The command to be executed</param>
|
||||||
|
/// <param name="message">Text that will be displayed in client chat</param>
|
||||||
|
/// <param name="data">A serialized object to be sent across the network</param>
|
||||||
|
/// <param name="sent">The date timestamp this command was sent</param>
|
||||||
|
/// <param name="steamId">The client reciving this packet (if 0 it sends to all clients)</param>
|
||||||
|
/// <param name="isReliable">Enture delivery of the packet</param>
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet to the server
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cmd">The object to be sent to the client</param>
|
||||||
|
/// <param name="steamId">The users steam ID</param>
|
||||||
|
/// <param name="isReliable">Makes sure the message is recieved by the server</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet to the server
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandString">The command to be executed</param>
|
||||||
|
/// <param name="point">Client side send to server this is not used</param>
|
||||||
|
/// <param name="radius">Client side send to server this is not used</param>
|
||||||
|
/// <param name="message">Text that will be displayed in client chat</param>
|
||||||
|
/// <param name="data">A serialized object to be sent across the network</param>
|
||||||
|
/// <param name="sent">The date timestamp this command was sent</param>
|
||||||
|
/// <param name="steamId">The client reciving this packet (if 0 it sends to all clients)</param>
|
||||||
|
/// <param name="isReliable">Enture delivery of the packet</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet to the server
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cmd">The object to be sent to the client</param>
|
||||||
|
/// <param name="point">Client side send to server this is not used</param>
|
||||||
|
/// <param name="radius">Client side send to server this is not used</param>
|
||||||
|
/// <param name="steamId">The users steam ID</param>
|
||||||
|
/// <param name="isReliable">Makes sure the message is recieved by the server</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
GlobalTorch/API/Libraries/SENetworkAPI/Command.cs
Normal file
22
GlobalTorch/API/Libraries/SENetworkAPI/Command.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
498
GlobalTorch/API/Libraries/SENetworkAPI/NetSync.cs
Normal file
498
GlobalTorch/API/Libraries/SENetworkAPI/NetSync.cs
Normal file
@@ -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<MyEntity, List<NetSync>> PropertiesByEntity =
|
||||||
|
new Dictionary<MyEntity, List<NetSync>>();
|
||||||
|
|
||||||
|
internal static Dictionary<long, NetSync> PropertyById = new Dictionary<long, NetSync>();
|
||||||
|
|
||||||
|
internal static object locker = new object();
|
||||||
|
internal static long generatorId = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers after recieving a fetch request from clients
|
||||||
|
/// and allows you to modify this property before it is sent.
|
||||||
|
/// </summary>
|
||||||
|
public Action<ulong> BeforeFetchRequestResponse;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The allowed network communication direction
|
||||||
|
/// </summary>
|
||||||
|
public TransferType TransferType { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The identity of this property
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables/Disables network traffic out when setting a value
|
||||||
|
/// </summary>
|
||||||
|
public bool SyncOnLoad { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Limits sync updates to within sync distance
|
||||||
|
/// </summary>
|
||||||
|
public bool LimitToSyncDistance { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the last recorded network traffic
|
||||||
|
/// </summary>
|
||||||
|
public long LastMessageTimestamp { get; internal set; }
|
||||||
|
|
||||||
|
internal static long GeneratePropertyId()
|
||||||
|
{
|
||||||
|
return generatorId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request the lastest value from the server
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Fetch();
|
||||||
|
|
||||||
|
internal abstract void Push(SyncType type, ulong sendTo);
|
||||||
|
|
||||||
|
internal abstract void SetNetworkValue(byte[] data, ulong sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NetSync<T> : NetSync
|
||||||
|
{
|
||||||
|
private readonly string sessionName;
|
||||||
|
private T _value;
|
||||||
|
private MyEntity Entity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires each time the value is changed
|
||||||
|
/// Provides the old value and the new value
|
||||||
|
/// </summary>
|
||||||
|
public Action<T, T> ValueChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires only when the a network call is made
|
||||||
|
/// Provides the old value and the new value
|
||||||
|
/// also provides the steamId
|
||||||
|
/// </summary>
|
||||||
|
public Action<T, T, ulong> ValueChangedByNetwork;
|
||||||
|
|
||||||
|
/// <param name="entity">IMyEntity object this property is attached to</param>
|
||||||
|
/// <param name="transferType"></param>
|
||||||
|
/// <param name="startingValue">Sets an initial value</param>
|
||||||
|
/// <param name="syncOnLoad">automatically syncs data to clients when the class initializes</param>
|
||||||
|
/// <param name="limitToSyncDistance">marking this true only sends data to clients within sync distance</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="entity">MyEntity object this property is attached to</param>
|
||||||
|
/// <param name="transferType"></param>
|
||||||
|
/// <param name="startingValue">Sets an initial value</param>
|
||||||
|
/// <param name="syncOnLoad">automatically syncs data to clients when the class initializes</param>
|
||||||
|
/// <param name="limitToSyncDistance">marking this true only sends data to clients within sync distance</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="logic">MyGameLogicComponent object this property is attached to</param>
|
||||||
|
/// <param name="transferType"></param>
|
||||||
|
/// <param name="startingValue">Sets an initial value</param>
|
||||||
|
/// <param name="syncOnLoad">automatically syncs data to clients when the class initializes</param>
|
||||||
|
/// <param name="limitToSyncDistance">marking this true only sends data to clients within sync distance</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="logic">MySessionComponentBase object this property is attached to</param>
|
||||||
|
/// <param name="transferType"></param>
|
||||||
|
/// <param name="startingValue">Sets an initial value</param>
|
||||||
|
/// <param name="syncOnLoad">automatically syncs data to clients when the class initializes</param>
|
||||||
|
/// <param name="limitToSyncDistance">marking this true only sends data to clients within sync distance</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// this property syncs across the network when changed
|
||||||
|
/// </summary>
|
||||||
|
public T Value
|
||||||
|
{
|
||||||
|
get => _value;
|
||||||
|
set => SetValue(value, SyncType.Broadcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This funtion is called by the constructer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="transferType"></param>
|
||||||
|
/// <param name="startingValue">Sets an initial value</param>
|
||||||
|
/// <param name="syncOnLoad">automatically syncs data to clients when the class initializes</param>
|
||||||
|
/// <param name="limitToSyncDistance">marking this true only sends data to clients within sync distance</param>
|
||||||
|
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<NetSync> { 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows you to change how syncing works when setting the value this way
|
||||||
|
/// </summary>
|
||||||
|
public void SetValue(T val, SyncType syncType = SyncType.None)
|
||||||
|
{
|
||||||
|
var oldval = _value;
|
||||||
|
lock (_value)
|
||||||
|
{
|
||||||
|
_value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendValue(syncType);
|
||||||
|
ValueChanged?.Invoke(oldval, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the data received over the network
|
||||||
|
/// </summary>
|
||||||
|
internal override void SetNetworkValue(byte[] data, ulong sender)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var oldval = _value;
|
||||||
|
lock (_value)
|
||||||
|
{
|
||||||
|
_value = MyAPIGateway.Utilities.SerializeFromBinary<T>(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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// sends the value across the network
|
||||||
|
/// </summary>
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Receives and redirects all property traffic
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pack">this hold the path to the property and the data to sync</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request the lastest value from the server
|
||||||
|
/// Servers are not allowed to fetch from clients
|
||||||
|
/// </summary>
|
||||||
|
public override void Fetch()
|
||||||
|
{
|
||||||
|
if (!MyAPIGateway.Multiplayer.IsServer) SendValue(SyncType.Fetch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send data now
|
||||||
|
/// </summary>
|
||||||
|
public void Push()
|
||||||
|
{
|
||||||
|
SendValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send data to single user
|
||||||
|
/// </summary>
|
||||||
|
public void Push(ulong sendTo)
|
||||||
|
{
|
||||||
|
SendValue(SyncType.Post, sendTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send data across the network now
|
||||||
|
/// </summary>
|
||||||
|
internal override void Push(SyncType type, ulong sendTo = ulong.MinValue)
|
||||||
|
{
|
||||||
|
SendValue(type, sendTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifier for logging readability
|
||||||
|
/// </summary>
|
||||||
|
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}>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
323
GlobalTorch/API/Libraries/SENetworkAPI/Network.cs
Normal file
323
GlobalTorch/API/Libraries/SENetworkAPI/Network.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the diffrence between now and a given timestamp in frames (60 fps)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static readonly double frames = 1000d / 60d;
|
||||||
|
|
||||||
|
public readonly ushort ComId;
|
||||||
|
public readonly string Keyword;
|
||||||
|
public readonly string ModName;
|
||||||
|
internal Dictionary<string, Action<string>> ChatCommands = new Dictionary<string, Action<string>>();
|
||||||
|
|
||||||
|
internal Dictionary<string, Action<ulong, string, byte[], DateTime>> NetworkCommands =
|
||||||
|
new Dictionary<string, Action<ulong, string, byte[], DateTime>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event driven client, server syncing API.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="comId">The communication channel this mod will listen on</param>
|
||||||
|
/// <param name="modName">The title use for displaying chat messages</param>
|
||||||
|
/// <param name="keyward">The string identifying a chat command</param>
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggers apon reciveing data over the network
|
||||||
|
/// steamId, command, data
|
||||||
|
/// </summary>
|
||||||
|
public event Action<ulong, string, byte[], DateTime> OnCommandRecived;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes chat command events
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageText">Chat message string</param>
|
||||||
|
/// <param name="sendToOthers">should be shown normally in global chat</param>
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unpacks commands and handles arguments
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">Data chunck recived from the network</param>
|
||||||
|
private void HandleIncomingPacket(byte[] msg)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cmd = MyAPIGateway.Utilities.SerializeFromBinary<Command>(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<object>.RouteMessage(MyAPIGateway.Utilities.SerializeFromBinary<SyncData>(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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a callback that will fire when the command string is sent
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command">The command that triggers the callback</param>
|
||||||
|
/// <param name="callback">The function that runs when a command is recived</param>
|
||||||
|
public void RegisterNetworkCommand(string command, Action<ulong, string, byte[], DateTime> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters a command
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command"></param>
|
||||||
|
public void UnregisterNetworkCommand(string command)
|
||||||
|
{
|
||||||
|
if (NetworkCommands.ContainsKey(command)) NetworkCommands.Remove(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// will trigger when you type
|
||||||
|
/// <keyword>
|
||||||
|
/// <command>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command">this is the text command that will be typed into chat</param>
|
||||||
|
/// <param name="callback">this is the function that will be called when the keyword is typed</param>
|
||||||
|
public void RegisterChatCommand(string command, Action<string> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters a chat command
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command">the chat command to unregister</param>
|
||||||
|
public void UnregisterChatCommand(string command)
|
||||||
|
{
|
||||||
|
if (ChatCommands.ContainsKey(command)) ChatCommands.Remove(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet across the network
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandString">The command word and any arguments delimidated with spaces</param>
|
||||||
|
/// <param name="message">Text to be writen in chat</param>
|
||||||
|
/// <param name="data">A serialized object used to send game information</param>
|
||||||
|
/// <param name="sent">The date timestamp this command was sent</param>
|
||||||
|
/// <param name="steamId">A players steam id</param>
|
||||||
|
/// <param name="isReliable">Makes sure the data gets to the target</param>
|
||||||
|
public abstract void SendCommand(string commandString, string message = null, byte[] data = null,
|
||||||
|
DateTime? sent = null, ulong steamId = ulong.MinValue, bool isReliable = true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet across the network
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandString">The command word and any arguments delimidated with spaces</param>
|
||||||
|
/// <param name="point"></param>
|
||||||
|
/// <param name="radius"></param>
|
||||||
|
/// <param name="message">Text to be writen in chat</param>
|
||||||
|
/// <param name="data">A serialized object used to send game information</param>
|
||||||
|
/// <param name="sent">The date timestamp this command was sent</param>
|
||||||
|
/// <param name="steamId">A players steam id</param>
|
||||||
|
/// <param name="isReliable">Makes sure the data gets to the target</param>
|
||||||
|
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);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet to the server / client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cmd">The object to be sent across the network</param>
|
||||||
|
/// <param name="steamId">the id of the user this is being sent to. 0 sends it to all users in range</param>
|
||||||
|
/// <param name="isReliable">make sure the packet reaches its destination</param>
|
||||||
|
internal abstract void SendCommand(Command cmd, ulong steamId = ulong.MinValue, bool isReliable = true);
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet to the server / client if in range
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cmd">The object to be sent across the network</param>
|
||||||
|
/// <param name="point">the center of the sending sphere</param>
|
||||||
|
/// <param name="range">the radius of the sending sphere</param>
|
||||||
|
/// <param name="steamId">the id of the user this is being sent to. 0 sends it to all users in range</param>
|
||||||
|
/// <param name="isReliable">make sure the packet reaches its destination</param>
|
||||||
|
internal abstract void SendCommand(Command cmd, Vector3D point, double range = 0,
|
||||||
|
ulong steamId = ulong.MinValue, bool isReliable = true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Posts text into the ingame chat.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
public abstract void Say(string message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters listeners
|
||||||
|
/// </summary>
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calls Instance.Close()
|
||||||
|
/// </summary>
|
||||||
|
[ObsoleteAttribute("This property is obsolete. Dispose is no longer required", false)]
|
||||||
|
public static void Dispose()
|
||||||
|
{
|
||||||
|
if (IsInitialized) Instance.Close();
|
||||||
|
|
||||||
|
Instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the default instance of the NetworkAPI
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the diffrence between now and a given timestamp in milliseconds
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
158
GlobalTorch/API/Libraries/SENetworkAPI/Server.cs
Normal file
158
GlobalTorch/API/Libraries/SENetworkAPI/Server.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Server class contains a few server only feature beond what is inharited from the NetworkAPI
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="comId">Identifies the channel to pass information to and from this mod</param>
|
||||||
|
/// <param name="keyword">identifies what chat entries should be captured and sent to the server</param>
|
||||||
|
public Server(ushort comId, string modName, string keyword = null) : base(comId, modName, keyword)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet to the client(s)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandString">The command to be executed</param>
|
||||||
|
/// <param name="message">Text that will be displayed in client chat</param>
|
||||||
|
/// <param name="data">A serialized object to be sent across the network</param>
|
||||||
|
/// <param name="sent">The date timestamp this command was sent</param>
|
||||||
|
/// <param name="steamId">The client reciving this packet (if 0 it sends to all clients)</param>
|
||||||
|
/// <param name="isReliable">Enture delivery of the packet</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandString">Sends a command packet to the client(s)</param>
|
||||||
|
/// <param name="point">the center of the sync location</param>
|
||||||
|
/// <param name="radius">the distance the message reaches (defaults to sync distance)</param>
|
||||||
|
/// <param name="message">Text that will be displayed in client chat</param>
|
||||||
|
/// <param name="data">A serialized object to be sent across the network</param>
|
||||||
|
/// <param name="sent">The date timestamp this command was sent</param>
|
||||||
|
/// <param name="steamId">The client reciving this packet (if 0 it sends to all clients)</param>
|
||||||
|
/// <param name="isReliable">Enture delivery of the packet</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet to a list of clients
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamIds"></param>
|
||||||
|
/// <param name="commandString">The command to be executed</param>
|
||||||
|
/// <param name="message">Text that will be displayed in client chat</param>
|
||||||
|
/// <param name="data">A serialized object to be sent across the network</param>
|
||||||
|
/// <param name="sent">The date timestamp this command was sent</param>
|
||||||
|
/// <param name="isReliable">Enture delivery of the packet</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet to the client(s)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cmd">The object to be sent to the client</param>
|
||||||
|
/// <param name="steamId">The players steam id</param>
|
||||||
|
/// <param name="isReliable">Make sure the data arrives</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a command packet to the client(s)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cmd">The object to be sent to the client</param>
|
||||||
|
/// <param name="point">the center of the sync location</param>
|
||||||
|
/// <param name="radius">the distance the message reaches (defaults to sync distance)</param>
|
||||||
|
/// <param name="steamId">The players steam id</param>
|
||||||
|
/// <param name="isReliable">Make sure the data arrives</param>
|
||||||
|
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<IMyPlayer>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
GlobalTorch/API/Libraries/SENetworkAPI/SessionTools.cs
Normal file
13
GlobalTorch/API/Libraries/SENetworkAPI/SessionTools.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using VRage.Game.Components;
|
||||||
|
|
||||||
|
namespace SENetworkAPI
|
||||||
|
{
|
||||||
|
[MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)]
|
||||||
|
public class SessionTools : MySessionComponentBase
|
||||||
|
{
|
||||||
|
protected override void UnloadData()
|
||||||
|
{
|
||||||
|
NetworkAPI.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
GlobalTorch/API/Util/CollectionUtils.cs
Normal file
49
GlobalTorch/API/Util/CollectionUtils.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Global.API.Util
|
||||||
|
{
|
||||||
|
public static class CollectionUtils
|
||||||
|
{
|
||||||
|
public static TV ComputeIfAbsent<TK, TV>(this Dictionary<TK, TV> self, TK key, Func<TK, TV> valueCreator)
|
||||||
|
{
|
||||||
|
if (self.ContainsKey(key))
|
||||||
|
return self[key];
|
||||||
|
var val = valueCreator(key);
|
||||||
|
self.Add(key, val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetOrAdd<TK, TV>(this Dictionary<TK, TV> self, TK key, Func<TK, TV> valueCreator,
|
||||||
|
Func<TV, TV> 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<T>(this IReadOnlyList<T> self, int index, out T foundValue)
|
||||||
|
{
|
||||||
|
if (self.Count < index + 1)
|
||||||
|
{
|
||||||
|
foundValue = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foundValue = self[index];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T GetElementAtIndexOrElse<T>(this IReadOnlyList<T> self, int index, T defaultValue)
|
||||||
|
{
|
||||||
|
return self.TryGetElementAt(index, out var e) ? e : defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
GlobalTorch/API/Util/FacUtils.cs
Normal file
45
GlobalTorch/API/Util/FacUtils.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
GlobalTorch/API/Util/FileUtils.cs
Normal file
71
GlobalTorch/API/Util/FileUtils.cs
Normal file
@@ -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<T>(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<T>(string filePath) where T : new()
|
||||||
|
{
|
||||||
|
TextReader reader = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
reader = new StreamReader(filePath);
|
||||||
|
var fileContents = reader.ReadToEnd();
|
||||||
|
return JsonConvert.DeserializeObject<T>(fileContents);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
reader?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteToXmlFile<T>(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<T>(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
GlobalTorch/API/Util/GlobalLogManager.cs
Normal file
74
GlobalTorch/API/Util/GlobalLogManager.cs
Normal file
@@ -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<string, FileTarget> Targets = new Dictionary<string, FileTarget>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
GlobalTorch/App.config
Normal file
23
GlobalTorch/App.config
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
90
GlobalTorch/AudioSendManager.cs
Normal file
90
GlobalTorch/AudioSendManager.cs
Normal file
@@ -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<ulong, Stream> _plays = new Dictionary<ulong, Stream>();
|
||||||
|
private readonly SendBuffer _sendBuffer = new SendBuffer();
|
||||||
|
private readonly Thread _thread;
|
||||||
|
|
||||||
|
private byte[] _buffer = Array.Empty<byte>();
|
||||||
|
|
||||||
|
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<ulong, BitReaderWriter>(_sendVoiceMethod, 0UL,
|
||||||
|
_sendBuffer,
|
||||||
|
new EndpointId(player));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream.Dispose();
|
||||||
|
_plays.Remove(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
183
GlobalTorch/Config/WpfConfig.cs
Normal file
183
GlobalTorch/Config/WpfConfig.cs
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
16
GlobalTorch/DBManager.cs
Normal file
16
GlobalTorch/DBManager.cs
Normal file
@@ -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};";
|
||||||
|
}
|
||||||
|
}
|
38
GlobalTorch/GlobalCommands.cs
Normal file
38
GlobalTorch/GlobalCommands.cs
Normal file
@@ -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<Type, int>();
|
||||||
|
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
220
GlobalTorch/GlobalPlugin.cs
Normal file
220
GlobalTorch/GlobalPlugin.cs
Normal file
@@ -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<WpfConfig> 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<PatchManager>().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<PluginManager>();
|
||||||
|
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<string> { "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<MyEntityThrustComponent>().Field("MAX_DISTANCE_RELATIVE_DAMPENING")
|
||||||
|
.SetValue(LegacyConfig.PatchesConfig.MaxDistanceRelativeDamping);
|
||||||
|
Traverse.Create<MyEntityThrustComponent>().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<WpfConfig>.Load(wpfConfigPath);
|
||||||
|
|
||||||
|
var configPath = $"{StoragePath}\\GlobalTorch.xml";
|
||||||
|
LegacyConfig = File.Exists(configPath)
|
||||||
|
? FileUtils.ReadFromXmlFile<LegacyConfig>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
273
GlobalTorch/GlobalTorch.csproj
Normal file
273
GlobalTorch/GlobalTorch.csproj
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{BB44D53C-70F4-4EDD-9B03-321CDF62BCA4}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>Global</RootNamespace>
|
||||||
|
<AssemblyName>GlobalTorch</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<LangVersion>7.3</LangVersion>
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
<NuGetPackageImportStamp>
|
||||||
|
</NuGetPackageImportStamp>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<PostBuildEvent>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
|
||||||
|
</PostBuildEvent>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="0Harmony">
|
||||||
|
<HintPath>..\..\NexusBinaries\0Harmony.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="mscorlib" />
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Nexus, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>..\..\NexusBinaries\Nexus.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="OpusWrapper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>OpusWrapper.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="PresentationFramework" />
|
||||||
|
<Reference Include="SpaceEngineers.Game">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\SpaceEngineers.Game.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="SpaceEngineers.ObjectBuilders">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\SpaceEngineers.ObjectBuilders.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="NLog">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\NLog.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ProtoBuf.Net">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\ProtoBuf.Net.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ProtoBuf.Net.Core">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\ProtoBuf.Net.Core.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Sandbox.Common">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\Sandbox.Common.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Sandbox.Game">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\Sandbox.Game.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Sandbox.Game.XmlSerializers">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\Sandbox.Game.XmlSerializers.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Sandbox.Graphics">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\Sandbox.Graphics.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Sandbox.RenderDirect">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\Sandbox.RenderDirect.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Steamworks.NET, Version=15.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\Steamworks.NET.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.AppContext" />
|
||||||
|
<Reference Include="System.Collections" />
|
||||||
|
<Reference Include="System.Collections.Concurrent" />
|
||||||
|
<Reference Include="System.Collections.NonGeneric" />
|
||||||
|
<Reference Include="System.Collections.Specialized" />
|
||||||
|
<Reference Include="System.ComponentModel" />
|
||||||
|
<Reference Include="System.ComponentModel.Annotations">
|
||||||
|
<HintPath>..\..\TorchBinaries\System.ComponentModel.Annotations.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.ComponentModel.Composition" />
|
||||||
|
<Reference Include="System.ComponentModel.EventBasedAsync" />
|
||||||
|
<Reference Include="System.ComponentModel.Primitives" />
|
||||||
|
<Reference Include="System.ComponentModel.TypeConverter" />
|
||||||
|
<Reference Include="System.Console" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Data.Common" />
|
||||||
|
<Reference Include="System.Diagnostics.Contracts" />
|
||||||
|
<Reference Include="System.Diagnostics.Debug" />
|
||||||
|
<Reference Include="System.Diagnostics.FileVersionInfo" />
|
||||||
|
<Reference Include="System.Diagnostics.Process" />
|
||||||
|
<Reference Include="System.Diagnostics.StackTrace" />
|
||||||
|
<Reference Include="System.Diagnostics.TextWriterTraceListener" />
|
||||||
|
<Reference Include="System.Diagnostics.Tools" />
|
||||||
|
<Reference Include="System.Diagnostics.TraceSource" />
|
||||||
|
<Reference Include="System.Diagnostics.Tracing" />
|
||||||
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Drawing.Primitives" />
|
||||||
|
<Reference Include="System.Dynamic.Runtime" />
|
||||||
|
<Reference Include="System.Globalization" />
|
||||||
|
<Reference Include="System.Globalization.Calendars" />
|
||||||
|
<Reference Include="System.Globalization.Extensions" />
|
||||||
|
<Reference Include="System.IO.Compression" />
|
||||||
|
<Reference Include="System.IO.Compression.ZipFile" />
|
||||||
|
<Reference Include="System.IO.FileSystem" />
|
||||||
|
<Reference Include="System.IO.FileSystem.DriveInfo" />
|
||||||
|
<Reference Include="System.IO.FileSystem.Primitives" />
|
||||||
|
<Reference Include="System.IO.FileSystem.Watcher" />
|
||||||
|
<Reference Include="System.IO.IsolatedStorage" />
|
||||||
|
<Reference Include="System.IO.MemoryMappedFiles" />
|
||||||
|
<Reference Include="System.IO.Pipes" />
|
||||||
|
<Reference Include="System.IO.UnmanagedMemoryStream" />
|
||||||
|
<Reference Include="System.Linq" />
|
||||||
|
<Reference Include="System.Linq.Expressions" />
|
||||||
|
<Reference Include="System.Linq.Parallel" />
|
||||||
|
<Reference Include="System.Linq.Queryable" />
|
||||||
|
<Reference Include="System.Numerics" />
|
||||||
|
<Reference Include="System.Threading" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
<Reference Include="Torch">
|
||||||
|
<HintPath>..\..\TorchBinaries\Torch.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Torch.API">
|
||||||
|
<HintPath>..\..\TorchBinaries\Torch.API.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Torch.Server">
|
||||||
|
<HintPath>..\..\TorchBinaries\Torch.Server.exe</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\VRage.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Audio">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\VRage.Audio.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Dedicated">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\VRage.Dedicated.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.EOS, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\VRage.EOS.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Game">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\VRage.Game.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Game.XmlSerializers">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\VRage.Game.XmlSerializers.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Input">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\VRage.Input.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Library">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\VRage.Library.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Math">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\VRage.Math.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VRage.Math.XmlSerializers">
|
||||||
|
<HintPath>..\..\TorchBinaries\DedicatedServer64\VRage.Math.XmlSerializers.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="WindowsBase" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="API\Discord\DiscordStructs.cs" />
|
||||||
|
<Compile Include="API\Discord\DiscordUtil.cs" />
|
||||||
|
<Compile Include="API\Discord\DiscordWebhook.cs" />
|
||||||
|
<Compile Include="API\EventResult.cs" />
|
||||||
|
<Compile Include="API\GlobalModApi.cs" />
|
||||||
|
<Compile Include="API\GlobalServerModApi.cs" />
|
||||||
|
<Compile Include="API\Libraries\SENetworkAPI\Client.cs" />
|
||||||
|
<Compile Include="API\Libraries\SENetworkAPI\Command.cs" />
|
||||||
|
<Compile Include="API\Libraries\SENetworkAPI\NetSync.cs" />
|
||||||
|
<Compile Include="API\Libraries\SENetworkAPI\Network.cs" />
|
||||||
|
<Compile Include="API\Libraries\SENetworkAPI\Server.cs" />
|
||||||
|
<Compile Include="API\Libraries\SENetworkAPI\SessionTools.cs" />
|
||||||
|
<Compile Include="API\Util\CollectionUtils.cs" />
|
||||||
|
<Compile Include="API\Util\FacUtils.cs" />
|
||||||
|
<Compile Include="API\Util\FileUtils.cs" />
|
||||||
|
<Compile Include="API\Util\GlobalLogManager.cs" />
|
||||||
|
<Compile Include="AudioSendManager.cs" />
|
||||||
|
<Compile Include="LegacyConfig.cs" />
|
||||||
|
<Compile Include="Config\WpfConfig.cs" />
|
||||||
|
<Compile Include="DbManager.cs" />
|
||||||
|
<Compile Include="GlobalPlugin.cs" />
|
||||||
|
<Compile Include="NexusIntegration.cs" />
|
||||||
|
<Compile Include="Overwrites\MyEntitiesButBetter.cs" />
|
||||||
|
<Compile Include="Patches\MyScriptManagerPatch.cs" />
|
||||||
|
<Compile Include="Patches\Patcher.cs" />
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="GlobalCommands.cs" />
|
||||||
|
<Compile Include="TorchLogger.cs" />
|
||||||
|
<Compile Include="Util\MTObservableCollection.cs" />
|
||||||
|
<Compile Include="Util\SendBuffer.cs" />
|
||||||
|
<Compile Include="Util\SEUtils.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="opus.dll" />
|
||||||
|
<Content Include="manifest.xml">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="App.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Confuser.MSBuild">
|
||||||
|
<Version>1.6.0</Version>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="source-rcon-server">
|
||||||
|
<Version>1.3.1</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="System.IO.Pipelines">
|
||||||
|
<Version>6.0.3</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="System.Memory">
|
||||||
|
<Version>4.5.5</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="System.Runtime.CompilerServices.Unsafe">
|
||||||
|
<Version>6.0.0</Version>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<Import Project="..\GlobalShared\GlobalShared.projitems" Label="Shared" />
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
</Project>
|
9
GlobalTorch/GlobalTorch.csproj.user
Normal file
9
GlobalTorch/GlobalTorch.csproj.user
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectView>ProjectFiles</ProjectView>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<ReferencePath>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\</ReferencePath>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
52
GlobalTorch/LegacyConfig.cs
Normal file
52
GlobalTorch/LegacyConfig.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
28
GlobalTorch/NexusIntegration.cs
Normal file
28
GlobalTorch/NexusIntegration.cs
Normal file
@@ -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<NexusAPI.Sector> GetSectors(BoundingSphereD sphereD)
|
||||||
|
{
|
||||||
|
if (!EnableNexusIntegration) return new List<NexusAPI.Sector>();
|
||||||
|
return (from sector in NexusAPI.GetSectors()
|
||||||
|
let sectorSphere = new BoundingSphereD(sector.Center, sector.Radius)
|
||||||
|
where sectorSphere.Intersects(sphereD)
|
||||||
|
select sector).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
GlobalTorch/OpusWrapper.dll
Normal file
BIN
GlobalTorch/OpusWrapper.dll
Normal file
Binary file not shown.
366
GlobalTorch/Overwrites/MyEntitiesButBetter.cs
Normal file
366
GlobalTorch/Overwrites/MyEntitiesButBetter.cs
Normal file
@@ -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<IMyEntity> _entities = new List<IMyEntity>();
|
||||||
|
private readonly List<long> _entityIds = new List<long>();
|
||||||
|
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<MyObjectBuilder_EntityBase> 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<IMyEntity, bool> match)
|
||||||
|
{
|
||||||
|
return m_entities.GetEntity(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetEntities(HashSet<IMyEntity> entities, Func<IMyEntity, bool> collect = null)
|
||||||
|
{
|
||||||
|
m_entities.GetEntities(entities, collect);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IMyEntity> GetIntersectionWithSphere(ref BoundingSphereD sphere, IMyEntity ignoreEntity0,
|
||||||
|
IMyEntity ignoreEntity1,
|
||||||
|
bool ignoreVoxelMaps, bool volumetricTest)
|
||||||
|
{
|
||||||
|
return m_entities.GetIntersectionWithSphere(ref sphere,
|
||||||
|
ignoreEntity0, ignoreEntity1, ignoreVoxelMaps, volumetricTest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IMyEntity> GetEntitiesInAABB(ref BoundingBoxD boundingBox)
|
||||||
|
{
|
||||||
|
return m_entities.GetEntitiesInAABB(ref boundingBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public List<IMyEntity> 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<IMyEntity> GetElementsInBox(ref BoundingBoxD boundingBox)
|
||||||
|
{
|
||||||
|
return m_entities.GetElementsInBox(ref boundingBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public List<IMyEntity> 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<IMyEntity> 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<IMyEntity> GetEntitiesInSphere(ref BoundingSphereD boundingSphere)
|
||||||
|
{
|
||||||
|
return m_entities.GetEntitiesInSphere(ref boundingSphere);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IMyEntity> GetTopMostEntitiesInSphere(ref BoundingSphereD boundingSphere)
|
||||||
|
{
|
||||||
|
return m_entities.GetTopMostEntitiesInSphere(ref boundingSphere);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IMyEntity> GetTopMostEntitiesInBox(ref BoundingBoxD boundingBox)
|
||||||
|
{
|
||||||
|
return m_entities.GetTopMostEntitiesInBox(ref boundingBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMyEntity CreateFromObjectBuilderParallel(MyObjectBuilder_EntityBase objectBuilder,
|
||||||
|
bool addToScene = false,
|
||||||
|
Action<IMyEntity> completionCallback = null)
|
||||||
|
{
|
||||||
|
return m_entities.CreateFromObjectBuilderParallel(objectBuilder, addToScene, completionCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
event Action<IMyEntity> IMyEntities.OnEntityRemove
|
||||||
|
{
|
||||||
|
add => m_entities.OnEntityRemove += value;
|
||||||
|
remove => m_entities.OnEntityRemove -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
event Action<IMyEntity> 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<IMyEntity, string, string> IMyEntities.OnEntityNameSet
|
||||||
|
{
|
||||||
|
add => customEventDef += value;
|
||||||
|
remove => customEventDef -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static event Action<IMyEntity, string, string> customEventDef;
|
||||||
|
|
||||||
|
public static void CallRename(IMyEntity entity, string oldName, string newName)
|
||||||
|
{
|
||||||
|
customEventDef?.Invoke(entity, oldName, newName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
GlobalTorch/Patches/MyScriptManagerPatch.cs
Normal file
71
GlobalTorch/Patches/MyScriptManagerPatch.cs
Normal file
@@ -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<MyScriptManager>("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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
GlobalTorch/Patches/Patcher.cs
Normal file
131
GlobalTorch/Patches/Patcher.cs
Normal file
@@ -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<T>(string TargetMethodName, BindingFlags Flags)
|
||||||
|
{
|
||||||
|
return MethodGet(typeof(T), TargetMethodName, Flags, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodInfo GetMethod<T>(
|
||||||
|
string TargetMethodName,
|
||||||
|
BindingFlags Flags,
|
||||||
|
Type[] Types)
|
||||||
|
{
|
||||||
|
return MethodGet(typeof(T), TargetMethodName, Flags, Types);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodInfo SuffixPatch<T>(
|
||||||
|
string TargetMethodName,
|
||||||
|
BindingFlags Flags,
|
||||||
|
string ReplaceMentMethodName)
|
||||||
|
{
|
||||||
|
return Patch(false, typeof(T), TargetMethodName, Flags, ReplaceMentMethodName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodInfo SuffixPatch<T>(
|
||||||
|
string TargetMethodName,
|
||||||
|
BindingFlags Flags,
|
||||||
|
Type[] Types,
|
||||||
|
string ReplaceMentMethodName)
|
||||||
|
{
|
||||||
|
return Patch(false, typeof(T), TargetMethodName, Flags, ReplaceMentMethodName, Types);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodInfo PrePatch<T>(
|
||||||
|
string TargetMethodName,
|
||||||
|
BindingFlags Flags,
|
||||||
|
string ReplaceMentMethodName)
|
||||||
|
{
|
||||||
|
return Patch(true, typeof(T), TargetMethodName, Flags, ReplaceMentMethodName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodInfo PrePatch<T>(
|
||||||
|
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<object>());
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Invalid Arguments for " + TargetMethodName + " exsisting in: " + TargetClass.Name,
|
||||||
|
Array.Empty<object>());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Unkown Patch Error!", Array.Empty<object>());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
GlobalTorch/Program.cs
Normal file
44
GlobalTorch/Program.cs
Normal file
@@ -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<IMyButtonPanel>();
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
GlobalTorch/Properties/AssemblyInfo.cs
Normal file
35
GlobalTorch/Properties/AssemblyInfo.cs
Normal file
@@ -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")]
|
114
GlobalTorch/TorchLogger.cs
Normal file
114
GlobalTorch/TorchLogger.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
GlobalTorch/Util/MTObservableCollection.cs
Normal file
34
GlobalTorch/Util/MTObservableCollection.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace Global.Util
|
||||||
|
{
|
||||||
|
public class MtObservableCollection<T> : ObservableCollection<T>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
GlobalTorch/Util/SEUtils.cs
Normal file
60
GlobalTorch/Util/SEUtils.cs
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
GlobalTorch/Util/SendBuffer.cs
Normal file
36
GlobalTorch/Util/SendBuffer.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
GlobalTorch/manifest.xml
Normal file
7
GlobalTorch/manifest.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<PluginManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<Name>GlobalTorch</Name>
|
||||||
|
<Guid>FB668D21-68CE-4037-9564-807E2709836C</Guid>
|
||||||
|
<Repository>None</Repository>
|
||||||
|
<Version>0.1.0</Version>
|
||||||
|
</PluginManifest>
|
BIN
GlobalTorch/opus.dll
Normal file
BIN
GlobalTorch/opus.dll
Normal file
Binary file not shown.
Reference in New Issue
Block a user