Get command system working, permissions system and ModAPI extensions in progress

This commit is contained in:
John Gross
2017-01-11 00:19:21 -08:00
parent 4949982fa8
commit 10836cbbb2
27 changed files with 625 additions and 149 deletions

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using VRage.Game;
using VRage.Game.ModAPI;
namespace Torch.API
{
@@ -12,7 +14,9 @@ namespace Torch.API
Dictionary<ulong, IPlayer> Players { get; }
List<IChatItem> Chat { get; }
void SendMessage(string message);
void SendMessage(string message, long playerId, string author = "Server", string font = MyFontEnum.Blue);
void KickPlayer(ulong id);
void BanPlayer(ulong id, bool banned = true);
IMyPlayer GetPlayerBySteamId(ulong id);
}
}

View File

@@ -8,7 +8,7 @@ namespace Torch.API
public interface IPluginManager : IEnumerable<ITorchPlugin>
{
void UpdatePlugins();
void LoadPlugins();
void Init();
void UnloadPlugins();
}
}

View File

@@ -11,6 +11,7 @@ namespace Torch.API
event Action SessionLoaded;
IMultiplayer Multiplayer { get; }
IPluginManager Plugins { get; }
Version Version { get; }
void Invoke(Action action);
void InvokeBlocking(Action action);
Task InvokeAsync(Action action);

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Game.Entities.Blocks;
using Sandbox.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame;
namespace Torch.API.ModAPI.Ingame
{
public static class GridExtensions
{
}
public static class PistonExtensions
{
public static IMyCubeGrid GetConnectedGrid(this IMyPistonBase pistonBase)
{
if (!pistonBase.IsAttached)
return null;
return ((Sandbox.ModAPI.IMyPistonBase)pistonBase).TopGrid;
}
}
public static class RotorExtensions
{
public static IMyCubeGrid GetConnectedGrid(this IMyMotorStator rotorBase)
{
if (!rotorBase.IsAttached)
return null;
return ((Sandbox.ModAPI.IMyMotorStator)rotorBase).RotorGrid;
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.API.ModAPI
{
public static class TorchAPI
{
}
}

View File

@@ -38,6 +38,24 @@
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="Sandbox.Common">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\Sandbox.Common.dll</HintPath>
</Reference>
<Reference Include="Sandbox.Game">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\Sandbox.Game.dll</HintPath>
</Reference>
<Reference Include="Sandbox.Graphics">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\Sandbox.Graphics.dll</HintPath>
</Reference>
<Reference Include="SpaceEngineers.Game">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\SpaceEngineers.Game.dll</HintPath>
</Reference>
<Reference Include="SpaceEngineers.ObjectBuilders">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\SpaceEngineers.ObjectBuilders.dll</HintPath>
</Reference>
<Reference Include="SpaceEngineers.ObjectBuilders.XmlSerializers">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\SpaceEngineers.ObjectBuilders.XmlSerializers.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
@@ -55,11 +73,41 @@
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Audio">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Audio.dll</HintPath>
</Reference>
<Reference Include="VRage.Game">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Game.dll</HintPath>
</Reference>
<Reference Include="VRage.Game.XmlSerializers">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Game.XmlSerializers.dll</HintPath>
</Reference>
<Reference Include="VRage.Input">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Input.dll</HintPath>
</Reference>
<Reference Include="VRage.Library, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Library.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Math">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Math.dll</HintPath>
</Reference>
<Reference Include="VRage.Native">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Native.dll</HintPath>
</Reference>
<Reference Include="VRage.OpenVRWrapper">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.OpenVRWrapper.dll</HintPath>
</Reference>
<Reference Include="VRage.Render">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Render.dll</HintPath>
</Reference>
<Reference Include="VRage.Render11">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Render11.dll</HintPath>
</Reference>
<Reference Include="VRage.Scripting">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.Scripting.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ConnectionState.cs" />
@@ -70,6 +118,8 @@
<Compile Include="ITorchPlugin.cs" />
<Compile Include="IServerControls.cs" />
<Compile Include="ITorchBase.cs" />
<Compile Include="ModAPI\Ingame\GridExtensions.cs" />
<Compile Include="ModAPI\TorchAPI.cs" />
<Compile Include="PluginAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Linq;
@@ -34,6 +36,18 @@ namespace Torch.Server
if (args.FirstOrDefault() == "-svcinstall")
{
/* Working on installing the service properly instead of with sc.exe
_log.Info("Installing service");
var installer = new TorchServiceInstaller();
installer.Context = new InstallContext(Path.Combine(Directory.GetCurrentDirectory(), "svclog.log"), null);
installer.Context.Parameters.Add("name", "Torch DS");
installer.Install(new Hashtable
{
{"name", "Torch DS"}
});
_log.Info("Service Installed");*/
var runArgs = string.Join(" ", args.Skip(1));
_log.Info($"Installing Torch as a service with arguments '{runArgs}'");
var startInfo = new ProcessStartInfo

View File

@@ -27,14 +27,22 @@ namespace Torch.Server
{
public Thread GameThread { get; private set; }
public bool IsRunning { get; private set; }
public bool IsService { get; set; }
public bool IsService { get; }
public string InstancePath { get; private set; }
public string InstanceName { get; }
public event Action SessionLoading;
private readonly AutoResetEvent _stopHandle = new AutoResetEvent(false);
internal TorchServer()
internal TorchServer(string instanceName = null)
{
if (instanceName != null)
{
IsService = true;
InstanceName = instanceName;
}
MySession.OnLoading += OnSessionLoading;
}
@@ -59,6 +67,8 @@ namespace Torch.Server
};
var gameVersion = MyPerGameSettings.BasicGameInfo.GameVersion;
MyFinalBuildConstants.APP_VERSION = gameVersion ?? 0;
InstancePath = InstanceName != null ? GetInstancePath(true, InstanceName) : GetInstancePath();
}
private void OnSessionLoading()
@@ -87,7 +97,7 @@ namespace Torch.Server
Environment.SetEnvironmentVariable("SteamAppId", MyPerServerSettings.AppId.ToString());
Log.Trace("Invoking RunMain");
try { Reflection.InvokeStaticMethod(typeof(DedicatedServer), "RunMain", "Torch", null, IsService, true); }
try { Reflection.InvokeStaticMethod(typeof(DedicatedServer), "RunMain", InstanceName, InstancePath, false, true); }
catch (Exception e)
{
Log.Error(e);
@@ -131,6 +141,14 @@ namespace Torch.Server
IsRunning = false;
}
private string GetInstancePath(bool isService = false, string instanceName = "Torch")
{
if (isService)
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), MyPerServerSettings.GameDSName, instanceName);
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), MyPerServerSettings.GameDSName);
}
private void CleanupProfilers()
{
typeof(MyRenderProfiler).GetField("m_threadProfiler", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, null);

View File

@@ -12,7 +12,7 @@ namespace Torch.Server
class TorchService : ServiceBase
{
public const string Name = "Torch (SEDS)";
private readonly TorchServer _server = new TorchServer();
private TorchServer _server;
private static Logger _log = LogManager.GetLogger("Torch");
public TorchService()
@@ -30,9 +30,9 @@ namespace Torch.Server
protected override void OnStart(string[] args)
{
base.OnStart(args);
_server = new TorchServer("Torch");
_server.Init();
_server.RunArgs = args;
_server.IsService = true;
Task.Run(() => _server.Start());
}

View File

@@ -11,7 +11,7 @@ using System.Threading.Tasks;
namespace Torch.Server
{
[RunInstaller(true)]
class TorchServiceInstaller : Installer
public class TorchServiceInstaller : Installer
{
private ServiceInstaller _serviceInstaller;

View File

@@ -8,7 +8,7 @@ namespace Torch.Commands
{
public class CategoryAttribute : Attribute
{
public string[] Path { get; }
public List<string> Path { get; }
/// <summary>
/// Specifies where to add the class's commands in the command tree.
@@ -16,7 +16,7 @@ namespace Torch.Commands
/// <param name="path">Command path, e.g. "/admin config" -> "admin, config"</param>
public CategoryAttribute(params string[] path)
{
Path = path;
Path = path.Select(i => i.ToLower()).ToList();
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Torch.API;
@@ -12,7 +13,7 @@ namespace Torch.Commands
public string Description { get; }
public string HelpText { get; }
public Type Module { get; }
public string[] Path { get; }
public List<string> Path { get; } = new List<string>();
public ITorchPlugin Plugin { get; }
private readonly MethodInfo _method;
@@ -31,16 +32,13 @@ namespace Torch.Commands
_method = commandMethod;
Module = commandMethod.DeclaringType;
var path = commandAttribute.Path;
if (moduleAttribute != null)
{
var modPath = moduleAttribute.Path;
var comPath = commandAttribute.Path;
path = new string[modPath.Length + comPath.Length];
modPath.CopyTo(path, 0);
comPath.CopyTo(path, modPath.Length);
Path.AddRange(moduleAttribute.Path);
}
Path = path;
Path.AddRange(commandAttribute.Path);
Name = commandAttribute.Name;
Description = commandAttribute.Description;
HelpText = commandAttribute.HelpText;
@@ -50,7 +48,7 @@ namespace Torch.Commands
{
var moduleInstance = (CommandModule)Activator.CreateInstance(Module);
moduleInstance.Context = context;
_method.Invoke(moduleInstance, new object[0]);
_method.Invoke(moduleInstance, null);
}
}
}

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Torch.Commands
{
@@ -7,14 +9,16 @@ namespace Torch.Commands
public string Name { get; }
public string Description { get; }
public string HelpText { get; }
public string[] Path { get; }
public List<string> Path { get; } = new List<string>();
public CommandAttribute(string name, string description = "", string helpText = "", params string[] path)
public CommandAttribute(string name, string description = "", string helpText = null, params string[] path)
{
Name = name;
Description = description;
HelpText = helpText;
Path = path.Length > 0 ? path : new [] {name.ToLower()};
HelpText = helpText ?? description;
Path.AddRange(path.Select(x => x.ToLower()));
Path.Add(name.ToLower());
}
}
}

View File

@@ -1,36 +1,34 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Torch.API;
using VRage.Game;
using VRage.Game.ModAPI;
namespace Torch.Commands
{
public struct CommandContext
public class CommandContext
{
public ITorchPlugin Plugin { get; }
public ITorchBase Torch { get; }
public IMyPlayer Player { get; }
public string[] Args { get; }
public List<string> Args { get; }
/// <summary>
/// Splits the argument by single words and quoted blocks.
/// </summary>
/// <returns></returns>
public CommandContext(ITorchBase torch, ITorchPlugin plugin, IMyPlayer player, params string[] args)
public CommandContext(ITorchBase torch, ITorchPlugin plugin, IMyPlayer player, List<string> args = null)
{
Torch = torch;
Plugin = plugin;
Player = player;
Args = args;
Args = args ?? new List<string>();
}
/*
var split = Regex.Split(Args, "(\"[^\"]+\"|\\S+)");
for (var i = 0; i < split.Length; i++)
public void Respond(string message, string sender = "Server", string font = MyFontEnum.Blue)
{
split[i] = Regex.Replace(split[i], "\"", "");
}
return split;*/
Torch.Multiplayer.SendMessage(message, Player.PlayerID, sender, font);
}
}
}

View File

@@ -1,47 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using NLog;
using Torch.API;
using Torch.Managers;
using VRage.Game.ModAPI;
using VRage.Network;
namespace Torch.Commands
{
public class PermissionGroup
{
public List<ulong> Members { get; }
public List<Permission> Permissions { get; }
}
public class PermissionUser
{
public ulong SteamID { get; }
public List<PermissionGroup> Groups { get; }
public List<Permission> Permissions { get; }
}
public class Permission
{
public string[] Path { get; }
public bool Allow { get; }
}
public class CommandManager
{
public ITorchBase Torch { get; }
public char Prefix { get; set; }
public Dictionary<string, Command> Commands { get; } = new Dictionary<string, Command>();
public CommandTree Commands { get; set; } = new CommandTree();
private Logger _log = LogManager.GetLogger(nameof(CommandManager));
private readonly ITorchBase _torch;
private readonly ChatManager _chatManager = ChatManager.Instance;
public CommandManager(ITorchBase torch, char prefix = '/')
{
Torch = torch;
_torch = torch;
Prefix = prefix;
_chatManager.MessageRecieved += HandleCommand;
RegisterCommandModule(typeof(TorchCommands));
}
public bool HasPermission(ulong steamId, Command command)
{
_log.Warn("Command permissions not implemented");
return true;
}
@@ -50,39 +39,72 @@ namespace Torch.Commands
return command.Length > 1 && command[0] == Prefix;
}
public void RegisterPluginCommands(ITorchPlugin plugin)
public void RegisterCommandModule(Type moduleType, ITorchPlugin plugin = null)
{
var assembly = plugin.GetType().Assembly;
foreach (var type in assembly.ExportedTypes)
{
if (!type.IsSubclassOf(typeof(CommandModule)))
continue;
if (!moduleType.IsSubclassOf(typeof(CommandModule)))
return;
foreach (var method in type.GetMethods())
foreach (var method in moduleType.GetMethods())
{
var commandAttrib = method.GetCustomAttribute<CommandAttribute>();
if (commandAttrib == null)
continue;
var command = new Command(plugin, method);
_log.Info($"Registering command '{string.Join(".", command.Path)}' from plugin '{plugin.Name}'");
}
var cmdPath = string.Join(".", command.Path);
_log.Info($"Registering command '{cmdPath}'");
if (!Commands.AddCommand(command))
_log.Error($"Command path {cmdPath} is already registered.");
}
}
public void HandleCommand(string command, ulong steamId = 0)
public void RegisterPluginCommands(ITorchPlugin plugin)
{
if (!IsCommand(command))
var assembly = plugin.GetType().Assembly;
foreach (var type in assembly.ExportedTypes)
{
RegisterCommandModule(type, plugin);
}
}
public void HandleCommand(ChatMsg msg, ref bool sendToOthers)
{
if (msg.Text.Length < 1 || msg.Text[0] != Prefix)
return;
var cmdNameEnd = command.Length - command.IndexOf(" ", StringComparison.InvariantCultureIgnoreCase);
var cmdName = command.Substring(1, cmdNameEnd);
if (!Commands.ContainsKey(cmdName))
sendToOthers = false;
var player = _torch.Multiplayer.GetPlayerBySteamId(msg.Author);
if (player == null)
{
_log.Error($"Command {msg.Text} invoked by nonexistant player");
return;
}
var cmdText = new string(msg.Text.Skip(1).ToArray());
var split = Regex.Matches(cmdText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();
if (split.Count == 0)
return;
string arg = "";
if (command.Length > cmdNameEnd + 1)
arg = command.Substring(cmdNameEnd + 1);
var command = Commands.ParseCommand(split, out List<string> args);
if (command != null)
{
var cmdPath = string.Join(".", command.Path);
if (!HasPermission(msg.Author, command))
{
_log.Info($"{player.DisplayName} tried to use command {cmdPath} without permission");
return;
}
_log.Trace($"Invoking {cmdPath} for player {player.DisplayName}");
var context = new CommandContext(_torch, command.Plugin, player, args);
command.Invoke(context);
_log.Info($"Player {player.DisplayName} ran command '{msg.Text}'");
}
}
}
}

View File

@@ -3,29 +3,42 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.Collections;
using VRage.Library.Collections;
namespace Torch.Commands
{
public class CommandTree
{
private Dictionary<string, Node> RootNodes { get; } = new Dictionary<string, Node>();
public DictionaryReader<string, CommandNode> Root { get; }
private readonly Dictionary<string, CommandNode> _root = new Dictionary<string, CommandNode>();
public CommandTree()
{
Root = new DictionaryReader<string, CommandNode>(_root);
}
public bool AddCommand(Command command)
{
var root = command.Path.First();
var node = RootNodes.ContainsKey(root) ? RootNodes[root] : new Node(root);
for (var i = 1; i < command.Path.Length; i++)
if (!_root.ContainsKey(root))
{
_root.Add(root, new CommandNode(root));
}
var node = _root[root];
for (var i = 1; i < command.Path.Count; i++)
{
var current = command.Path[i];
if (node.Nodes.ContainsKey(current))
if (node.Subcommands.ContainsKey(current))
{
node = node.Nodes[current];
node = node.Subcommands[current];
continue;
}
var newNode = new Node(current);
var newNode = new CommandNode(current);
node.AddNode(newNode);
node = newNode;
}
@@ -37,59 +50,119 @@ namespace Torch.Commands
return true;
}
public Command ParseCommand(ulong steamId, string[] command, out string[] args)
/// <summary>
/// Get a command node from the tree.
/// </summary>
/// <param name="path">Path to the command node.</param>
/// <param name="commandNode"></param>
/// <returns>The index of the first argument in the path or -1 if the node doesn't exist.</returns>
public int GetNode(List<string> path, out CommandNode commandNode)
{
args = new string[0];
var root = command.First();
if (!RootNodes.ContainsKey(root))
return null;
commandNode = null;
var root = path.FirstOrDefault();
if (root == null)
return -1;
var node = RootNodes[root];
for (var i = 1; i < command.Length; i++)
if (!_root.ContainsKey(root))
return -1;
var node = _root[root];
var i = 1;
for (; i < path.Count; i++)
{
var current = command[i];
if (node.Nodes.ContainsKey(current))
var current = path[i];
if (node.Subcommands.ContainsKey(current))
{
node = node.Nodes[current];
continue;
node = node.Subcommands[current];
}
}
args = new string[command.Length - i];
Array.Copy(command, i, args, 0, args.Length);
commandNode = node;
return path.Count - i + 1;
}
if (!node.IsCommand)
return null;
public Command ParseCommand(List<string> path, out List<string> args)
{
args = new List<string>();
var skip = GetNode(path, out CommandNode node);
args.AddRange(path.Skip(skip));
return node.Command;
}
private class Node
public string GetTreeString()
{
public Dictionary<string, Node> Nodes { get; } = new Dictionary<string, Node>();
var indent = 0;
var sb = new StringBuilder();
foreach (var node in _root)
{
DebugNode(node.Value, sb, ref indent);
}
return sb.ToString();
}
private void DebugNode(CommandNode commandNode, StringBuilder sb, ref int indent)
{
sb.AppendLine(new string(' ', indent) + commandNode.Name);
indent += 2;
foreach (var n in commandNode.Subcommands)
{
DebugNode(n.Value, sb, ref indent);
}
indent -= 2;
}
public class CommandNode
{
public CommandNode Parent { get; private set; }
public DictionaryReader<string, CommandNode> Subcommands { get; }
private readonly Dictionary<string, CommandNode> _subcommands = new Dictionary<string, CommandNode>();
public string Name { get; }
public Command Command { get; set; }
public bool IsCommand => Command != null;
public bool IsEmpty => !IsCommand && Nodes.Count == 0;
public bool IsEmpty => !IsCommand && _subcommands.Count == 0;
public Node(string name, Command command = null)
public CommandNode(string name)
{
Subcommands = new DictionaryReader<string, CommandNode>(_subcommands);
Name = name;
}
public CommandNode(Command command)
{
Name = command.Name;
Command = command;
}
public void AddNode(Node node)
public bool TryGetChild(string name, out CommandNode node)
{
Nodes.Add(node.Name, node);
}
return Subcommands.TryGetValue(name, out node);
}
public enum InvokeResult
public List<string> GetPath()
{
Success,
NoCommand,
NoPermission
var path = new List<string> {Name};
if (Parent != null)
path.InsertRange(0, Parent.GetPath());
return path;
}
public void AddNode(CommandNode commandNode)
{
commandNode.Parent = this;
_subcommands.Add(commandNode.Name, commandNode);
}
public void RemoveNode(CommandNode commandNode)
{
commandNode.Parent = null;
_subcommands.Remove(commandNode.Name);
}
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.Game.ModAPI;
namespace Torch.Commands
{
public class DefaultPermissionAttribute : Attribute
{
public MyPromoteLevel Level { get; }
public DefaultPermissionAttribute(MyPromoteLevel level)
{
Level = level;
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Game.World;
using VRage.Game.ModAPI;
namespace Torch.Commands.Permissions
{
public class PermissionGroup
{
public List<ulong> Members { get; }
public List<Permission> Permissions { get; }
public void AddMember(PermissionUser user)
{
Members.Add(user.SteamID);
user.Groups.Add(this);
}
public void RemoveMember(PermissionUser user)
{
user.Groups.Remove(this);
Members.Remove(user.SteamID);
}
}
public class PermissionUser
{
public ulong SteamID { get; }
public List<PermissionGroup> Groups { get; }
public List<Permission> Permissions { get; }
public void AddToGroup(PermissionGroup group)
{
group.Members.Add(SteamID);
Groups.Add(group);
}
public void RemoveFromGroup(PermissionGroup group)
{
group.Members.Remove(SteamID);
Groups.Remove(group);
}
public void HasPermission()
{
var promoteLevel = MySession.Static.GetUserPromoteLevel(SteamID);
}
}
public class Permission
{
public string[] Path { get; }
public bool Allow { get; }
public Dictionary<string, Permission> Children { get; } = new Dictionary<string, Permission>();
}
public class PermissonsSystem
{
public Dictionary<string, PermissionGroup> Groups { get; } = new Dictionary<string, PermissionGroup>();
public Dictionary<long, PermissionUser> Users { get; } = new Dictionary<long, PermissionUser>();
public void GenerateDefaultGroups()
{
foreach (var name in Enum.GetNames(typeof(MyPromoteLevel)))
{
Groups.Add(name, new PermissionGroup());
}
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Torch.Managers;
namespace Torch.Commands
{
public class TorchCommands : CommandModule
{
[Command("dbgcmd")]
public void Dbgcmd()
{
var commandManager = ((PluginManager)Context.Torch.Plugins).Commands;
Console.WriteLine(commandManager.Commands.GetTreeString());
}
[Command("help", "Displays help for a command")]
public void Help()
{
var commandManager = ((PluginManager)Context.Torch.Plugins).Commands;
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
if (node != null)
{
var command = node.Command;
var children = node.Subcommands.Select(x => x.Key);
var sb = new StringBuilder();
if (command != null)
sb.AppendLine(command.HelpText);
if (children.Any())
sb.AppendLine($"Subcommands: {string.Join(", ", children)}");
Context.Respond(sb.ToString());
}
else
{
var topNodeNames = commandManager.Commands.Root.Select(x => x.Key);
Context.Respond($"Top level commands: {string.Join(", ", topNodeNames)}");
}
}
[Command("ver", "Shows the running Torch version.")]
public void Version()
{
var ver = Context.Torch.Version;
Context.Respond($"Torch version: {ver}");
}
[Command("plugins", "Lists the currently loaded plugins.")]
public void Plugins()
{
var plugins = Context.Torch.Plugins.Select(p => p.Name);
Context.Respond($"Loaded plugins: {string.Join(", ", plugins)}");
}
[Command("stop", "Stops the server.")]
public void Stop()
{
Context.Respond("Stopping server.");
Context.Torch.Stop();
}
}
}

View File

@@ -11,7 +11,7 @@ namespace Torch.Managers
{
public class ChatManager
{
public ChatManager()
private ChatManager()
{
NetworkManager.Instance.RegisterNetworkHandlers(new ChatIntercept());
}

View File

@@ -27,7 +27,7 @@ using VRage.Library.Collections;
using VRage.Network;
using VRage.Utils;
namespace Torch
namespace Torch.Managers
{
/// <summary>
/// Provides a proxy to the game's multiplayer-related functions.
@@ -38,13 +38,11 @@ namespace Torch
public event Action<IPlayer> PlayerLeft;
public event Action<IChatItem> MessageReceived;
//public MTObservableCollection<MyPlayer> PlayersView { get; } = new MTObservableCollection<MyPlayer>();
//public MTObservableCollection<ChatItem> ChatView { get; } = new MTObservableCollection<ChatItem>();
public List<IChatItem> Chat { get; } = new List<IChatItem>();
public Dictionary<ulong, IPlayer> Players { get; } = new Dictionary<ulong, IPlayer>();
public Player LocalPlayer { get; private set; }
private readonly ITorchBase _torch;
private static Logger _log = LogManager.GetLogger("Torch");
private static Logger _log = LogManager.GetLogger(nameof(MultiplayerManager));
private Dictionary<MyPlayer.PlayerId, MyPlayer> _onlinePlayers;
internal MultiplayerManager(ITorchBase torch)
@@ -53,11 +51,11 @@ namespace Torch
_torch.SessionLoaded += OnSessionLoaded;
}
public void KickPlayer(ulong steamId) => _torch.InvokeAsync(() => MyMultiplayer.Static.KickClient(steamId));
public void KickPlayer(ulong steamId) => _torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
public void BanPlayer(ulong steamId, bool banned = true)
{
_torch.InvokeAsync(() =>
_torch.Invoke(() =>
{
MyMultiplayer.Static.BanClient(steamId, banned);
if (_gameOwnerIds.ContainsKey(steamId))
@@ -65,19 +63,29 @@ namespace Torch
});
}
public IMyPlayer GetPlayerByDisplayName(string displayName)
{
return _onlinePlayers.FirstOrDefault(x => x.Value.DisplayName == displayName).Value;
}
public IMyPlayer GetPlayerBySteamId(ulong steamId)
{
_onlinePlayers.TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p);
return p;
}
public void SendMessage(string message)
{
SendMessage(message, 0);
}
/// <summary>
/// Send a message in chat.
/// </summary>
public void SendMessage(string message)
public void SendMessage(string message, long playerId, string author = "Server", string font = MyFontEnum.Blue)
{
MyMultiplayer.Static.SendChatMessage(message);
//ChatView.Add(new ChatItem(LocalPlayer, message));
var msg = new ScriptedChatMsg {Author = author, Font = font, Target = playerId, Text = message};
MyMultiplayerBase.SendScriptedChatMessage(ref msg);
}
private void OnSessionLoaded()
@@ -156,6 +164,8 @@ namespace Torch
private HashSet<ulong> _waitingForGroup;
private HashSet<ulong> _waitingForFriends;
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
private IMultiplayer _multiplayerImplementation;
/// <summary>
/// Removes Keen's hooks into some Steam events so we have full control over client authentication
/// </summary>

View File

@@ -14,13 +14,13 @@ namespace Torch.Managers
{
public class NetworkManager
{
public NetworkManager()
private NetworkManager()
{
if (ReflectionUnitTest())
InitNetworkIntercept();
}
private static Logger _log = LogManager.GetLogger("Torch");
private static Logger _log = LogManager.GetLogger(nameof(NetworkManager));
private static NetworkManager _instance;
public static NetworkManager Instance => _instance ?? (_instance = new NetworkManager());
@@ -77,7 +77,7 @@ namespace Torch.Managers
//PrintDebug();
//ApplicationLog.Info("Initialized network intercept!");
_log.Debug("Initialized network intercept");
}
#region Network Intercept
@@ -100,8 +100,8 @@ namespace Torch.Managers
}
catch (Exception ex)
{
_log.Error(ex);
//ApplicationLog.Error(ex, "~Error processing event!");
_log.Fatal(ex);
_log.Fatal(ex, "~Error processing event!");
//crash after logging, bad things could happen if we continue on with bad data
throw;
}
@@ -175,8 +175,8 @@ namespace Torch.Managers
}
catch (Exception ex)
{
_log.Error(ex);
//ApplicationLog.Error(ex, "Error when returning control to game server!");
_log.Fatal(ex);
_log.Fatal(ex, "Error when returning control to game server!");
//crash after logging, bad things could happen if we continue on with bad data
throw;
}
@@ -191,7 +191,7 @@ namespace Torch.Managers
if (item.GetType().FullName == handlerType)
{
//if (ExtenderOptions.IsDebugging)
// ApplicationLog.BaseLog.Error("Network handler already registered! " + handlerType);
_log.Error("Network handler already registered! " + handlerType);
toRemove.Add(item);
}
}

View File

@@ -14,11 +14,12 @@ using Sandbox;
using Sandbox.ModAPI;
using Torch.API;
using Torch.Commands;
using Torch.Managers;
using VRage.Plugins;
using VRage.Collections;
using VRage.Library.Collections;
namespace Torch
namespace Torch.Managers
{
public class PluginManager : IPluginManager
{
@@ -28,13 +29,15 @@ namespace Torch
private readonly List<ITorchPlugin> _plugins = new List<ITorchPlugin>();
private readonly PluginUpdater _updater;
private readonly CommandManager _commands;
public CommandManager Commands { get; private set; }
public float LastUpdateMs => _lastUpdateMs;
private volatile float _lastUpdateMs;
public PluginManager(ITorchBase torch)
{
_torch = torch;
_updater = new PluginUpdater(this);
_commands = new CommandManager(_torch);
if (!Directory.Exists(PluginDir))
Directory.CreateDirectory(PluginDir);
@@ -42,6 +45,9 @@ namespace Torch
InitUpdater();
}
/// <summary>
/// Adds the plugin updater plugin to VRage's plugin system.
/// </summary>
private void InitUpdater()
{
var fieldName = "m_plugins";
@@ -52,11 +58,20 @@ namespace Torch
pluginList.Add(_updater);
}
/// <summary>
/// Updates loaded plugins in parallel.
/// </summary>
public void UpdatePlugins()
{
var s = Stopwatch.StartNew();
Parallel.ForEach(_plugins, p => p.Update());
s.Stop();
_lastUpdateMs = (float)s.Elapsed.TotalMilliseconds;
}
/// <summary>
/// Unloads all plugins.
/// </summary>
public void UnloadPlugins()
{
foreach (var plugin in _plugins)
@@ -66,10 +81,13 @@ namespace Torch
}
/// <summary>
/// Load and create instances of all plugins in the <see cref="PluginDir"/> folder.
/// Loads and creates instances of all plugins in the <see cref="PluginDir"/> folder.
/// </summary>
public void LoadPlugins()
public void Init()
{
var network = NetworkManager.Instance;
Commands = new CommandManager(_torch);
_log.Info("Loading plugins");
var pluginsPath = Path.Combine(Directory.GetCurrentDirectory(), PluginDir);
var dlls = Directory.GetFiles(pluginsPath, "*.dll", SearchOption.AllDirectories);
@@ -91,7 +109,7 @@ namespace Torch
_log.Info($"Loading plugin {plugin.Name} ({plugin.Version})");
_plugins.Add(plugin);
_commands.RegisterPluginCommands(plugin);
Commands.RegisterPluginCommands(plugin);
}
catch (Exception e)
{
@@ -132,6 +150,7 @@ namespace Torch
private class PluginUpdater : IPlugin
{
private readonly IPluginManager _manager;
public PluginUpdater(IPluginManager manager)
{
_manager = manager;
@@ -139,7 +158,7 @@ namespace Torch
public void Init(object obj)
{
_manager.LoadPlugins();
_manager.Init();
}
public void Update()

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Torch.API;
using Torch.API.ModAPI.Ingame;
using VRage.Scripting;
namespace Torch.Managers
{
public class ScriptingManager
{
private MyScriptWhitelist _whitelist;
public void Init()
{
_whitelist = MyScriptCompiler.Static.Whitelist;
MyScriptCompiler.Static.AddConditionalCompilationSymbols("TORCH");
MyScriptCompiler.Static.AddReferencedAssemblies(typeof(ITorchBase).Assembly.Location);
MyScriptCompiler.Static.AddImplicitIngameNamespacesFromTypes(typeof(GridExtensions));
/*
//dump whitelist
var whitelist = new StringBuilder();
foreach (var pair in MyScriptCompiler.Static.Whitelist.GetWhitelist())
{
var split = pair.Key.Split(',');
whitelist.AppendLine("|-");
whitelist.AppendLine($"|{pair.Value} || {split[0]} || {split[1]}");
}
Log.Info(whitelist);*/
}
public void UnwhitelistType(Type t)
{
throw new NotImplementedException();
}
}
}

View File

@@ -16,20 +16,27 @@ namespace Torch
{
public static class SteamHelper
{
private static Thread _callbackThread;
private static Logger _log = LogManager.GetLogger("Torch");
private static CancellationTokenSource _tokenSource = new CancellationTokenSource();
private static CancellationToken _cancelToken;
private static Logger _log = LogManager.GetLogger(nameof(SteamHelper));
public static void Init()
{
_callbackThread = new Thread(() =>
_cancelToken = _tokenSource.Token;
Task.Run(() =>
{
while (true)
while (!_cancelToken.IsCancellationRequested)
{
SteamAPI.Instance.RunCallbacks();
Thread.Sleep(100);
}
}) {Name = "SteamAPICallbacks"};
_callbackThread.Start();
});
}
public static void StopCallbackLoop()
{
_tokenSource.Cancel();
}
public static MySteamWorkshop.SubscribedItem GetItemInfo(ulong itemId)
@@ -55,7 +62,7 @@ namespace Torch
}
else
{
_log.Warn($"Failed to get item info for {itemId}");
_log.Error($"Failed to get item info for {itemId}");
}
mre.Set();
@@ -78,7 +85,7 @@ namespace Torch
if (!b && result.Details.Result == Result.OK)
details = result.Details;
else
_log.Warn($"Failed to get item details for {itemId}");
_log.Error($"Failed to get item details for {itemId}");
re.Set();
});

View File

@@ -152,11 +152,15 @@
<Compile Include="Commands\CommandContext.cs" />
<Compile Include="Commands\CommandManager.cs" />
<Compile Include="Commands\CommandTree.cs" />
<Compile Include="Commands\DefaultPermissionAttribute.cs" />
<Compile Include="Commands\Permissions\PermissonsSystem.cs" />
<Compile Include="Commands\TorchCommands.cs" />
<Compile Include="Managers\ChatManager.cs" />
<Compile Include="Managers\NetworkManager\NetworkHandlerBase.cs" />
<Compile Include="Managers\NetworkManager\NetworkManager.cs" />
<Compile Include="MultiplayerManager.cs" />
<Compile Include="Managers\MultiplayerManager.cs" />
<Compile Include="Reflection.cs" />
<Compile Include="Managers\ScriptingManager.cs" />
<Compile Include="TorchBase.cs" />
<Compile Include="Player.cs" />
<Compile Include="Collections\PlayerInfoCache.cs" />
@@ -169,7 +173,7 @@
<Compile Include="SteamHelper.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="ViewModels\ViewModel.cs" />
<Compile Include="PluginManager.cs" />
<Compile Include="Managers\PluginManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewModels\PluginViewModel.cs" />
</ItemGroup>

View File

@@ -3,12 +3,15 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using Sandbox;
using Sandbox.ModAPI;
using Torch.API;
using Torch.Managers;
using VRage.Scripting;
namespace Torch
@@ -22,6 +25,7 @@ namespace Torch
[Obsolete]
public static ITorchBase Instance { get; private set; }
protected static Logger Log = LogManager.GetLogger("Torch");
public Version Version { get; protected set; }
public string[] RunArgs { get; set; }
public IPluginManager Plugins { get; protected set; }
public IMultiplayer Multiplayer { get; protected set; }
@@ -41,6 +45,7 @@ namespace Torch
Instance = this;
Version = Assembly.GetExecutingAssembly().GetName().Version;
RunArgs = new string[0];
Plugins = new PluginManager(this);
Multiplayer = new MultiplayerManager(this);
@@ -116,9 +121,6 @@ namespace Torch
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
_init = true;
MyScriptCompiler.Static.AddConditionalCompilationSymbols("TORCH");
MyScriptCompiler.Static.AddReferencedAssemblies(typeof(ITorchBase).Assembly.Location);
MyScriptCompiler.Static.AddReferencedAssemblies(typeof(TorchBase).Assembly.Location);
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)