This commit is contained in:
/
2024-12-29 21:15:58 +01:00
commit 547655c326
77 changed files with 7313 additions and 0 deletions

View 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);
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Global.API
{
public enum EventResult
{
Allow,
Continue,
Deny
}
}

View 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));
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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; }
}
}

View 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}>";
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View File

@@ -0,0 +1,13 @@
using VRage.Game.Components;
namespace SENetworkAPI
{
[MySessionComponentDescriptor(MyUpdateOrder.NoUpdate)]
public class SessionTools : MySessionComponentBase
{
protected override void UnloadData()
{
NetworkAPI.Dispose();
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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();
}
}
}
}

View 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
View 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>

View 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);
}
}
}
}
}

View 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
View 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};";
}
}

View 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
View 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);
}
}
}

View 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>

View 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>

View 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;
}
}

View 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

Binary file not shown.

View 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);
}
}
}

View 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);
}
}
}

View 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
View 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());
}
}
}
}

View 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
View 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);
}
}
}

View 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);
}
}
}
}

View 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();
}
}
}

View 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
View 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

Binary file not shown.