From 045a5720589a5cfd7d04361f63286f1e0a05e293 Mon Sep 17 00:00:00 2001 From: Brant Martin Date: Sun, 10 Jun 2018 07:31:19 -0400 Subject: [PATCH 01/16] Add Torch client mod. Currently supports dialogs and notifications. Add dialog output to !longhelp Add !notify command Silently inserts mod into session when client connects, server admins don't need to do anything to enable it. --- Torch.Mod/Messages/DialogMessage.cs | 72 ++++++++ Torch.Mod/Messages/MessageBase.cs | 50 ++++++ Torch.Mod/Messages/NotificationMessage.cs | 39 +++++ Torch.Mod/ModCommunication.cs | 198 ++++++++++++++++++++++ Torch.Mod/Torch.Mod.projitems | 18 ++ Torch.Mod/Torch.Mod.shproj | 13 ++ Torch.Mod/TorchModCore.cs | 30 ++++ Torch.Server/TorchServer.cs | 7 +- Torch.sln | 28 ++- Torch/Commands/TorchCommands.cs | 22 ++- Torch/Patches/SessionDownloadPatch.cs | 29 ++++ Torch/Torch.csproj | 2 + 12 files changed, 501 insertions(+), 7 deletions(-) create mode 100644 Torch.Mod/Messages/DialogMessage.cs create mode 100644 Torch.Mod/Messages/MessageBase.cs create mode 100644 Torch.Mod/Messages/NotificationMessage.cs create mode 100644 Torch.Mod/ModCommunication.cs create mode 100644 Torch.Mod/Torch.Mod.projitems create mode 100644 Torch.Mod/Torch.Mod.shproj create mode 100644 Torch.Mod/TorchModCore.cs create mode 100644 Torch/Patches/SessionDownloadPatch.cs diff --git a/Torch.Mod/Messages/DialogMessage.cs b/Torch.Mod/Messages/DialogMessage.cs new file mode 100644 index 0000000..ae54317 --- /dev/null +++ b/Torch.Mod/Messages/DialogMessage.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ProtoBuf; +using Sandbox.ModAPI; + +namespace Torch.Mod.Messages +{ + /// Dialogs are structured as follows + /// + /// _____________________________________ + /// | Title | + /// -------------------------------------- + /// | Prefix Subtitle | + /// -------------------------------------- + /// | ________________________________ | + /// | | Content | | + /// | --------------------------------- | + /// | ____________ | + /// | | ButtonText | | + /// | -------------- | + /// -------------------------------------- + /// + /// Button has a callback on click option, + /// but can't serialize that, so ¯\_(ツ)_/¯ + [ProtoContract] + public class DialogMessage : MessageBase + { + [ProtoMember(201)] + public string Title; + [ProtoMember(202)] + public string Subtitle; + [ProtoMember(203)] + public string Prefix; + [ProtoMember(204)] + public string Content; + [ProtoMember(205)] + public string ButtonText; + + public DialogMessage() + { } + + public DialogMessage(string title, string subtitle, string content) + { + Title = title; + Subtitle = subtitle; + Content = content; + Prefix = String.Empty; + } + + public DialogMessage(string title = null, string prefix = null, string subtitle = null, string content = null, string buttonText = null) + { + Title = title; + Subtitle = subtitle; + Prefix = prefix ?? String.Empty; + Content = content; + ButtonText = buttonText; + } + + public override void ProcessClient() + { + MyAPIGateway.Utilities.ShowMissionScreen(Title, Prefix, Subtitle, Content, null, ButtonText); + } + + public override void ProcessServer() + { + throw new Exception(); + } + } +} diff --git a/Torch.Mod/Messages/MessageBase.cs b/Torch.Mod/Messages/MessageBase.cs new file mode 100644 index 0000000..ce8f1a5 --- /dev/null +++ b/Torch.Mod/Messages/MessageBase.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ProtoBuf; + +namespace Torch.Mod.Messages +{ + #region Includes + [ProtoInclude(1, typeof(DialogMessage))] + [ProtoInclude(2, typeof(NotificationMessage))] + #endregion + + [ProtoContract] + public abstract class MessageBase + { + [ProtoMember(101)] + public ulong SenderId; + + public abstract void ProcessClient(); + public abstract void ProcessServer(); + + //members below not serialized, they're just metadata about the intended target(s) of this message + internal MessageTarget TargetType; + internal ulong Target; + internal ulong[] Ignore; + internal byte[] CompressedData; + } + + public enum MessageTarget + { + /// + /// Send to Target + /// + Single, + /// + /// Send to Server + /// + Server, + /// + /// Send to all Clients (only valid from server) + /// + AllClients, + /// + /// Send to all except those steam ID listed in Ignore + /// + AllExcept, + } +} diff --git a/Torch.Mod/Messages/NotificationMessage.cs b/Torch.Mod/Messages/NotificationMessage.cs new file mode 100644 index 0000000..951f053 --- /dev/null +++ b/Torch.Mod/Messages/NotificationMessage.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ProtoBuf; +using Sandbox.ModAPI; + +namespace Torch.Mod.Messages +{ + [ProtoContract] + public class NotificationMessage : MessageBase + { + [ProtoMember(201)] + public string Message; + [ProtoMember(202)] + public string Font; + [ProtoMember(203)] + public int DisappearTimeMs; + + public NotificationMessage() + { } + + public NotificationMessage(string message, int disappearTimeMs, string font) + { + Message = message; + DisappearTimeMs = disappearTimeMs; + Font = font; + } + + public override void ProcessClient() + { + MyAPIGateway.Utilities.ShowNotification(Message, DisappearTimeMs, Font); + } + + public override void ProcessServer() + { + throw new Exception(); + } + } +} diff --git a/Torch.Mod/ModCommunication.cs b/Torch.Mod/ModCommunication.cs new file mode 100644 index 0000000..0295e26 --- /dev/null +++ b/Torch.Mod/ModCommunication.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Sandbox.ModAPI; +using Torch.Mod.Messages; +using VRage; +using VRage.Game.ModAPI; +using VRage.Utils; +using Task = ParallelTasks.Task; + +namespace Torch.Mod +{ + public static class ModCommunication + { + public const ushort NET_ID = 4352; + private static bool _closing; + private static ConcurrentQueue _outgoing; + private static ConcurrentQueue _incoming; + private static List _playerCache; + private static FastResourceLock _lock; + private static Task _task; + + public static void Register() + { + MyLog.Default.WriteLineAndConsole("TORCH MOD: Registering mod communication."); + _outgoing = new ConcurrentQueue(); + _incoming = new ConcurrentQueue(); + _playerCache = new List(); + _lock = new FastResourceLock(); + + + MyAPIGateway.Multiplayer.RegisterMessageHandler(NET_ID, MessageHandler); + //background thread to handle de/compression and processing + _task = MyAPIGateway.Parallel.StartBackground(DoProcessing); + MyLog.Default.WriteLineAndConsole("TORCH MOD: Mod communication registered successfully."); + } + + public static void Unregister() + { + MyLog.Default.WriteLineAndConsole("TORCH MOD: Unregistering mod communication."); + MyAPIGateway.Multiplayer.UnregisterMessageHandler(NET_ID, MessageHandler); + _closing = true; + ReleaseLock(); + _task.Wait(); + } + + private static void MessageHandler(byte[] bytes) + { + _incoming.Enqueue(bytes); + ReleaseLock(); + } + + public static void DoProcessing() + { + while (!_closing) + { + try + { + byte[] incoming; + while (_incoming.TryDequeue(out incoming)) + { + MessageBase m; + try + { + var o = MyCompression.Decompress(incoming); + m = MyAPIGateway.Utilities.SerializeFromBinary(o); + } + catch (Exception ex) + { + MyLog.Default.WriteLineAndConsole($"TORCH MOD: Failed to deserialize message! {ex}"); + continue; + } + if (MyAPIGateway.Multiplayer.IsServer) + m.ProcessServer(); + else + m.ProcessClient(); + } + + if (!_outgoing.IsEmpty) + { + List tosend = new List(_outgoing.Count); + MessageBase outMessage; + while (_outgoing.TryDequeue(out outMessage)) + { + var b = MyAPIGateway.Utilities.SerializeToBinary(outMessage); + outMessage.CompressedData = MyCompression.Compress(b); + tosend.Add(outMessage); + } + + MyAPIGateway.Utilities.InvokeOnGameThread(() => + { + MyAPIGateway.Players.GetPlayers(_playerCache); + foreach (var outgoing in tosend) + { + switch (outgoing.TargetType) + { + case MessageTarget.Single: + MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, outgoing.CompressedData, outgoing.Target); + break; + case MessageTarget.Server: + MyAPIGateway.Multiplayer.SendMessageToServer(NET_ID, outgoing.CompressedData); + break; + case MessageTarget.AllClients: + foreach (var p in _playerCache) + { + if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId) + continue; + MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, outgoing.CompressedData, p.SteamUserId); + } + break; + case MessageTarget.AllExcept: + foreach (var p in _playerCache) + { + if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId || outgoing.Ignore.Contains(p.SteamUserId)) + continue; + MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, outgoing.CompressedData, p.SteamUserId); + } + break; + default: + throw new Exception(); + } + } + _playerCache.Clear(); + }); + } + + AcquireLock(); + } + catch (Exception ex) + { + MyLog.Default.WriteLineAndConsole($"TORCH MOD: Exception occurred in communication thread! {ex}"); + } + } + + MyLog.Default.WriteLineAndConsole("TORCH MOD: COMMUNICATION THREAD: EXIT SIGNAL RECIEVED!"); + //exit signal received. Clean everything and GTFO + _outgoing = null; + _incoming = null; + _playerCache = null; + _lock = null; + } + + public static void SendMessageTo(MessageBase message, ulong target) + { + if (!MyAPIGateway.Multiplayer.IsServer) + throw new Exception("Only server can send targeted messages"); + message.Target = target; + message.TargetType = MessageTarget.Single; + MyLog.Default.WriteLineAndConsole($"Sending message of type {message.GetType().FullName}"); + _outgoing.Enqueue(message); + ReleaseLock(); + } + + public static void SendMessageToClients(MessageBase message) + { + if (!MyAPIGateway.Multiplayer.IsServer) + throw new Exception("Only server can send targeted messages"); + message.TargetType = MessageTarget.AllClients; + _outgoing.Enqueue(message); + ReleaseLock(); + } + + public static void SendMessageExcept(MessageBase message, params ulong[] ignoredUsers) + { + if (MyAPIGateway.Multiplayer.IsServer) + throw new Exception("Only server can send targeted messages"); + message.TargetType = MessageTarget.AllExcept; + message.Ignore = ignoredUsers; + _outgoing.Enqueue(message); + ReleaseLock(); + } + + public static void SendMessageToServer(MessageBase message) + { + message.TargetType = MessageTarget.Server; + _outgoing.Enqueue(message); + ReleaseLock(); + } + + private static void ReleaseLock() + { + while(!_lock.TryAcquireExclusive()) + _lock.ReleaseExclusive(); + _lock.ReleaseExclusive(); + } + + private static void AcquireLock() + { + ReleaseLock(); + _lock.AcquireExclusive(); + _lock.AcquireExclusive(); + } + } +} diff --git a/Torch.Mod/Torch.Mod.projitems b/Torch.Mod/Torch.Mod.projitems new file mode 100644 index 0000000..a6b330f --- /dev/null +++ b/Torch.Mod/Torch.Mod.projitems @@ -0,0 +1,18 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 3ce4d2e9-b461-4f19-8233-f87e0dfddd74 + + + Torch.Mod + + + + + + + + + \ No newline at end of file diff --git a/Torch.Mod/Torch.Mod.shproj b/Torch.Mod/Torch.Mod.shproj new file mode 100644 index 0000000..bc7cbf1 --- /dev/null +++ b/Torch.Mod/Torch.Mod.shproj @@ -0,0 +1,13 @@ + + + + 3ce4d2e9-b461-4f19-8233-f87e0dfddd74 + 14.0 + + + + + + + + diff --git a/Torch.Mod/TorchModCore.cs b/Torch.Mod/TorchModCore.cs new file mode 100644 index 0000000..b058636 --- /dev/null +++ b/Torch.Mod/TorchModCore.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VRage.Game.Components; + +namespace Torch.Mod +{ + [MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)] + public class TorchModCore : MySessionComponentBase + { + public const long MOD_ID = 1406994352; + private static bool _init; + + public override void UpdateAfterSimulation() + { + if (_init) + return; + + _init = true; + ModCommunication.Register(); + } + + protected override void UnloadData() + { + ModCommunication.Unregister(); + } + } +} diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index 21aa84e..42d1aa0 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -18,6 +18,7 @@ using Torch.API; using Torch.API.Managers; using Torch.API.Session; using Torch.Commands; +using Torch.Mod; using Torch.Server.Commands; using Torch.Server.Managers; using Torch.Utils; @@ -45,7 +46,7 @@ namespace Torch.Server private Timer _watchdog; /// - public TorchServer(TorchConfig config = null) + public TorchServer(TorchConfig config = null) { DedicatedInstance = new InstanceManager(this); AddManager(DedicatedInstance); @@ -174,10 +175,14 @@ namespace Torch.Server { _watchdog?.Dispose(); _watchdog = null; + ModCommunication.Unregister(); } if (newState == TorchSessionState.Loaded) + { CurrentSession.Managers.GetManager().RegisterCommandModule(typeof(WhitelistCommands)); + ModCommunication.Register(); + } } /// diff --git a/Torch.sln b/Torch.sln index e78e377..9f2f29b 100644 --- a/Torch.sln +++ b/Torch.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27004.2010 +VisualStudioVersion = 15.0.26430.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch", "Torch\Torch.csproj", "{7E01635C-3B67-472E-BCD6-C5539564F214}" EndProject @@ -27,41 +27,60 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Versioning", "Versioning", Versioning\AssemblyVersion.cs = Versioning\AssemblyVersion.cs EndProjectSection EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Torch.Mod", "Torch.Mod\Torch.Mod.shproj", "{3CE4D2E9-B461-4F19-8233-F87E0DFDDD74}" +EndProject Global - GlobalSection(Performance) = preSolution - HasPerformanceSessions = true + GlobalSection(SharedMSBuildProjectFiles) = preSolution + Torch.Mod\Torch.Mod.projitems*{3ce4d2e9-b461-4f19-8233-f87e0dfddd74}*SharedItemsImports = 13 + Torch.Mod\Torch.Mod.projitems*{7e01635c-3b67-472e-bcd6-c5539564f214}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|Any CPU.ActiveCfg = Debug|x64 {7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|x64.ActiveCfg = Debug|x64 {7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|x64.Build.0 = Debug|x64 + {7E01635C-3B67-472E-BCD6-C5539564F214}.Release|Any CPU.ActiveCfg = Release|x64 {7E01635C-3B67-472E-BCD6-C5539564F214}.Release|x64.ActiveCfg = Release|x64 {7E01635C-3B67-472E-BCD6-C5539564F214}.Release|x64.Build.0 = Release|x64 + {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|Any CPU.ActiveCfg = Debug|x64 {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|x64.ActiveCfg = Debug|x64 {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|x64.Build.0 = Debug|x64 + {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|Any CPU.ActiveCfg = Release|x64 {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|x64.ActiveCfg = Release|x64 {FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|x64.Build.0 = Release|x64 + {E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|Any CPU.ActiveCfg = Debug|x64 {E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.ActiveCfg = Debug|x64 {E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.Build.0 = Debug|x64 + {E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|Any CPU.ActiveCfg = Release|x64 {E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.ActiveCfg = Release|x64 {E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.Build.0 = Release|x64 + {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|Any CPU.ActiveCfg = Debug|x64 {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.ActiveCfg = Debug|x64 {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.Build.0 = Debug|x64 + {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|Any CPU.ActiveCfg = Release|x64 {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.ActiveCfg = Release|x64 {CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.Build.0 = Release|x64 + {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|Any CPU.ActiveCfg = Debug|x64 {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|x64.ActiveCfg = Debug|x64 {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|x64.Build.0 = Debug|x64 + {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|Any CPU.ActiveCfg = Release|x64 {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|x64.ActiveCfg = Release|x64 {C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|x64.Build.0 = Release|x64 + {9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Debug|Any CPU.ActiveCfg = Debug|x64 {9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Debug|x64.ActiveCfg = Debug|x64 {9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Debug|x64.Build.0 = Debug|x64 + {9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|Any CPU.ActiveCfg = Release|x64 {9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.ActiveCfg = Release|x64 {9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.Build.0 = Release|x64 + {632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|Any CPU.ActiveCfg = Debug|x64 {632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|x64.ActiveCfg = Debug|x64 {632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|x64.Build.0 = Debug|x64 + {632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|Any CPU.ActiveCfg = Release|x64 {632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.ActiveCfg = Release|x64 {632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.Build.0 = Release|x64 EndGlobalSection @@ -74,4 +93,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB51D91F-958D-4B63-A897-3C40642ACD3E} EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal diff --git a/Torch/Commands/TorchCommands.cs b/Torch/Commands/TorchCommands.cs index e3affc8..1879bae 100644 --- a/Torch/Commands/TorchCommands.cs +++ b/Torch/Commands/TorchCommands.cs @@ -17,6 +17,8 @@ using Torch.API.Managers; using Torch.API.Session; using Torch.Commands.Permissions; using Torch.Managers; +using Torch.Mod; +using Torch.Mod.Messages; using VRage.Game.ModAPI; namespace Torch.Commands @@ -75,7 +77,7 @@ namespace Torch.Commands } } - [Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")] + [Command("longhelp", "Get verbose help. Will send a long message in a dialog window.")] [Permission(MyPromoteLevel.None)] public void LongHelp() { @@ -107,13 +109,20 @@ namespace Torch.Commands } else { - var sb = new StringBuilder("Available commands:\n"); + var sb = new StringBuilder(); foreach (var command in commandManager.Commands.WalkTree()) { if (command.IsCommand) sb.AppendLine($"{command.Command.SyntaxHelp}\n {command.Command.HelpText}"); } - Context.Respond(sb.ToString()); + + if (!Context.SentBySelf) + { + var m = new DialogMessage("Torch Help", subtitle: "Available commands:", content: sb.ToString()); + ModCommunication.SendMessageTo(m, Context.Player.SteamUserId); + } + else + Context.Respond($"Available commands: {sb}"); } } @@ -169,6 +178,13 @@ namespace Torch.Commands }); } + [Command("notify", "Shows a message as a notification in the middle of all players' screens.")] + [Permission(MyPromoteLevel.Admin)] + public void Notify(string message, int disappearTimeMs = 2000, string font = "White") + { + ModCommunication.SendMessageToClients(new NotificationMessage(message, disappearTimeMs, font)); + } + [Command("restart cancel", "Cancel a pending restart.")] public void CancelRestart() { diff --git a/Torch/Patches/SessionDownloadPatch.cs b/Torch/Patches/SessionDownloadPatch.cs new file mode 100644 index 0000000..fc81ad5 --- /dev/null +++ b/Torch/Patches/SessionDownloadPatch.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Sandbox.Game.World; +using Torch.Managers.PatchManager; +using Torch.Mod; +using VRage.Game; + +namespace Torch.Patches +{ + [PatchShim] + internal class SessionDownloadPatch + { + internal static void Patch(PatchContext context) + { + context.GetPattern(typeof(MySession).GetMethod(nameof(MySession.GetWorld))).Suffixes.Add(typeof(SessionDownloadPatch).GetMethod(nameof(SuffixGetWorld), BindingFlags.Static | BindingFlags.NonPublic)); + } + + // ReSharper disable once InconsistentNaming + private static void SuffixGetWorld(ref MyObjectBuilder_World __result) + { + if (!__result.Checkpoint.Mods.Any(m => m.PublishedFileId == TorchModCore.MOD_ID)) + __result.Checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(TorchModCore.MOD_ID)); + } + } +} diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index e9791ac..b10ede3 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -209,6 +209,7 @@ + @@ -318,6 +319,7 @@ + \ No newline at end of file From 9286f2e5595c93a7fba077aa88d4fc8fd030aa8c Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Sun, 10 Jun 2018 21:46:00 -0700 Subject: [PATCH 02/16] Patch persistent (#232) Makes it save properly. Untested push to master :shipit: --- Torch/Persistent.cs | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/Torch/Persistent.cs b/Torch/Persistent.cs index 670d2ba..cddc182 100644 --- a/Torch/Persistent.cs +++ b/Torch/Persistent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; @@ -18,7 +18,19 @@ namespace Torch public sealed class Persistent : IDisposable where T : new() { public string Path { get; set; } - public T Data { get; private set; } + private T _data; + public T Data + { + get => _data; + private set + { + if (_data is INotifyPropertyChanged npc1) + npc1.PropertyChanged -= OnPropertyChanged; + _data = value; + if (_data is INotifyPropertyChanged npc2) + npc2.PropertyChanged += OnPropertyChanged; + } + } ~Persistent() { @@ -29,13 +41,23 @@ namespace Torch { Path = path; Data = data; - if (Data is INotifyPropertyChanged npc) - npc.PropertyChanged += OnPropertyChanged; + } + + private Timer _saveConfigTimer; + + private void SaveAsync() + { + if (_saveConfigTimer == null) + { + _saveConfigTimer = new Timer((x) => Save()); + } + + _saveConfigTimer.Change(1000, -1); } private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { - Save(); + SaveAsync(); } public void Save(string path = null) @@ -52,20 +74,20 @@ namespace Torch public static Persistent Load(string path, bool saveIfNew = true) { - var config = new Persistent(path, new T()); + Persistent config = null; if (File.Exists(path)) { var ser = new XmlSerializer(typeof(T)); using (var f = File.OpenText(path)) { - config.Data = (T)ser.Deserialize(f); + config = new Persistent(path, (T)ser.Deserialize(f)); } } - else if (saveIfNew) - { - config.Save(path); - } + if (config == null) + config = new Persistent(path, new T()); + if (!File.Exists(path) && saveIfNew) + config.Save(); return config; } @@ -76,6 +98,7 @@ namespace Torch { if (Data is INotifyPropertyChanged npc) npc.PropertyChanged -= OnPropertyChanged; + _saveConfigTimer?.Dispose(); Save(); } catch From 4db83e6f65053dfa677ea845c2a0c5afb4ecf16e Mon Sep 17 00:00:00 2001 From: Brant Martin Date: Mon, 11 Jun 2018 07:57:12 -0400 Subject: [PATCH 03/16] Add voxel reset message --- Torch.Mod/Messages/MessageBase.cs | 1 + Torch.Mod/Messages/VoxelResetMessage.cs | 46 +++++++++++++++++++++++++ Torch.Mod/Torch.Mod.projitems | 1 + Torch/Commands/TorchCommands.cs | 1 + 4 files changed, 49 insertions(+) create mode 100644 Torch.Mod/Messages/VoxelResetMessage.cs diff --git a/Torch.Mod/Messages/MessageBase.cs b/Torch.Mod/Messages/MessageBase.cs index ce8f1a5..3a00738 100644 --- a/Torch.Mod/Messages/MessageBase.cs +++ b/Torch.Mod/Messages/MessageBase.cs @@ -10,6 +10,7 @@ namespace Torch.Mod.Messages #region Includes [ProtoInclude(1, typeof(DialogMessage))] [ProtoInclude(2, typeof(NotificationMessage))] + [ProtoInclude(3, typeof(VoxelResetMessage))] #endregion [ProtoContract] diff --git a/Torch.Mod/Messages/VoxelResetMessage.cs b/Torch.Mod/Messages/VoxelResetMessage.cs new file mode 100644 index 0000000..e5cd66b --- /dev/null +++ b/Torch.Mod/Messages/VoxelResetMessage.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ProtoBuf; +using Sandbox.ModAPI; +using VRage.ModAPI; +using VRage.Voxels; + +namespace Torch.Mod.Messages +{ + [ProtoContract] + public class VoxelResetMessage : MessageBase + { + [ProtoMember(201)] + public long[] EntityId; + + public VoxelResetMessage() + { } + + public VoxelResetMessage(long[] entityId) + { + EntityId = entityId; + } + + public override void ProcessClient() + { + MyAPIGateway.Parallel.ForEach(EntityId, id => + { + IMyEntity e; + if (!MyAPIGateway.Entities.TryGetEntityById(id, out e)) + return; + + var v = e as IMyVoxelBase; + if (v == null) + return; + + v.Storage.Reset(MyStorageDataTypeFlags.All); + }); + } + + public override void ProcessServer() + { + throw new Exception(); + } + } +} diff --git a/Torch.Mod/Torch.Mod.projitems b/Torch.Mod/Torch.Mod.projitems index a6b330f..f05b7b6 100644 --- a/Torch.Mod/Torch.Mod.projitems +++ b/Torch.Mod/Torch.Mod.projitems @@ -12,6 +12,7 @@ + diff --git a/Torch/Commands/TorchCommands.cs b/Torch/Commands/TorchCommands.cs index 1879bae..d8144ff 100644 --- a/Torch/Commands/TorchCommands.cs +++ b/Torch/Commands/TorchCommands.cs @@ -19,6 +19,7 @@ using Torch.Commands.Permissions; using Torch.Managers; using Torch.Mod; using Torch.Mod.Messages; +using VRage.Game; using VRage.Game.ModAPI; namespace Torch.Commands From 8989ae94a73925b0feaffabd0046c854b1a1fb22 Mon Sep 17 00:00:00 2001 From: Brant Martin Date: Thu, 14 Jun 2018 11:08:09 -0400 Subject: [PATCH 04/16] Debug output for plugin loading --- Torch/Plugins/PluginManager.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Torch/Plugins/PluginManager.cs b/Torch/Plugins/PluginManager.cs index e34852c..b2531ea 100644 --- a/Torch/Plugins/PluginManager.cs +++ b/Torch/Plugins/PluginManager.cs @@ -377,6 +377,7 @@ namespace Torch.Managers private void InstantiatePlugin(PluginManifest manifest, IEnumerable assemblies) { Type pluginType = null; + bool mult = false; foreach (var asm in assemblies) { foreach (var type in asm.GetExportedTypes()) @@ -384,16 +385,26 @@ namespace Torch.Managers if (!type.GetInterfaces().Contains(typeof(ITorchPlugin))) continue; + _log.Info($"Loading plugin at {type.FullName}"); + if (pluginType != null) { - _log.Error($"The plugin '{manifest.Name}' has multiple implementations of {nameof(ITorchPlugin)}, not loading."); - return; + //_log.Error($"The plugin '{manifest.Name}' has multiple implementations of {nameof(ITorchPlugin)}, not loading."); + //return; + mult = true; + continue; } pluginType = type; } } + if (mult) + { + _log.Error($"The plugin '{manifest.Name}' has multiple implementations of {nameof(ITorchPlugin)}, not loading."); + return; + } + if (pluginType == null) { _log.Error($"The plugin '{manifest.Name}' does not have an implementation of {nameof(ITorchPlugin)}, not loading."); From dbea9d83f4b8d90e9b4debb471f004a0bf07be2a Mon Sep 17 00:00:00 2001 From: Brant Martin Date: Thu, 14 Jun 2018 14:15:51 -0400 Subject: [PATCH 05/16] Fix crash on exit --- Torch.Mod/ModCommunication.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Torch.Mod/ModCommunication.cs b/Torch.Mod/ModCommunication.cs index 0295e26..3c1a882 100644 --- a/Torch.Mod/ModCommunication.cs +++ b/Torch.Mod/ModCommunication.cs @@ -42,10 +42,10 @@ namespace Torch.Mod public static void Unregister() { MyLog.Default.WriteLineAndConsole("TORCH MOD: Unregistering mod communication."); - MyAPIGateway.Multiplayer.UnregisterMessageHandler(NET_ID, MessageHandler); - _closing = true; + MyAPIGateway.Multiplayer?.UnregisterMessageHandler(NET_ID, MessageHandler); ReleaseLock(); - _task.Wait(); + _closing = true; + //_task.Wait(); } private static void MessageHandler(byte[] bytes) @@ -183,6 +183,8 @@ namespace Torch.Mod private static void ReleaseLock() { + if(_lock==null) + return; while(!_lock.TryAcquireExclusive()) _lock.ReleaseExclusive(); _lock.ReleaseExclusive(); @@ -190,6 +192,8 @@ namespace Torch.Mod private static void AcquireLock() { + if (_lock == null) + return; ReleaseLock(); _lock.AcquireExclusive(); _lock.AcquireExclusive(); From 8b08f2b7479edbc8c6b5def4df7c2fa5c86ac83c Mon Sep 17 00:00:00 2001 From: Brant Martin Date: Sat, 23 Jun 2018 03:21:45 -0400 Subject: [PATCH 06/16] Enable multiline editing of string values in propertygrids --- Torch/Views/DisplayAttribute.cs | 1 + Torch/Views/PropertyGrid.xaml.cs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Torch/Views/DisplayAttribute.cs b/Torch/Views/DisplayAttribute.cs index 9dda943..ba9c043 100644 --- a/Torch/Views/DisplayAttribute.cs +++ b/Torch/Views/DisplayAttribute.cs @@ -16,6 +16,7 @@ namespace Torch.Views public bool Enabled = true; public bool Visible = true; public bool ReadOnly = false; + public Type EditorType = null; public DisplayAttribute() { } diff --git a/Torch/Views/PropertyGrid.xaml.cs b/Torch/Views/PropertyGrid.xaml.cs index 9e0bd39..4549c58 100644 --- a/Torch/Views/PropertyGrid.xaml.cs +++ b/Torch/Views/PropertyGrid.xaml.cs @@ -145,6 +145,11 @@ namespace Torch.Views grid.Children.Add(text); FrameworkElement valueControl; + if (descriptor?.EditorType != null) + { + valueControl = (FrameworkElement)Activator.CreateInstance(descriptor.EditorType); + valueControl.SetBinding(FrameworkElement.DataContextProperty, property.Name); + } if (property.GetSetMethod() == null || descriptor?.ReadOnly == true) { valueControl = new TextBlock(); @@ -211,11 +216,21 @@ namespace Torch.Views valueControl = button; } - else if (propertyType.IsPrimitive || propertyType == typeof(string)) + else if (propertyType.IsPrimitive) { valueControl = new TextBox(); valueControl.SetBinding(TextBox.TextProperty, property.Name); } + else if (propertyType == typeof(string)) + { + var tb = new TextBox(); + tb.TextWrapping = TextWrapping.Wrap; + tb.AcceptsReturn = true; + tb.AcceptsTab = true; + tb.SpellCheck.IsEnabled = true; + tb.SetBinding(TextBox.TextProperty, property.Name); + valueControl = tb; + } else { var button = new Button From 17514c89adb3172ee647b52d123c54381af910c7 Mon Sep 17 00:00:00 2001 From: Trent Monahan Date: Sun, 1 Jul 2018 15:46:07 +1000 Subject: [PATCH 07/16] Disable ImportWorldConfig when there are no worlds to import --- Torch.Server/Views/ConfigControl.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Torch.Server/Views/ConfigControl.xaml b/Torch.Server/Views/ConfigControl.xaml index 0f4462e..43ac44e 100644 --- a/Torch.Server/Views/ConfigControl.xaml +++ b/Torch.Server/Views/ConfigControl.xaml @@ -26,8 +26,8 @@