Add support for WPF controls in plugins and method parameters in plugin commands

This commit is contained in:
John Gross
2017-04-04 16:22:13 -07:00
parent 8bf7cd2c1a
commit 2858c35c38
32 changed files with 472 additions and 19 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Torch.API.Plugins;
using VRage.Collections;
using VRage.Plugins;
@@ -7,6 +8,8 @@ namespace Torch.API
{
public interface IPluginManager : IEnumerable<ITorchPlugin>
{
event Action<List<ITorchPlugin>> PluginsLoaded;
List<ITorchPlugin> Plugins { get; }
void UpdatePlugins();
void Init();
void DisposePlugins();

View File

@@ -5,7 +5,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
namespace Torch.API
namespace Torch.API.Plugins
{
public interface ITorchPlugin : IDisposable
{

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace Torch.API.Plugins
{
public interface IWpfPlugin : ITorchPlugin
{
/// <summary>
/// Used by the server's WPF interface to load custom plugin controls.
/// Do not instantiate your plugin control outside of this method! It will throw an exception.
/// </summary>
UserControl GetControl();
}
}

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.API
namespace Torch.API.Plugins
{
public class PluginAttribute : Attribute
{

View File

@@ -114,12 +114,13 @@
<Compile Include="IChatMessage.cs" />
<Compile Include="IMultiplayer.cs" />
<Compile Include="IPluginManager.cs" />
<Compile Include="ITorchPlugin.cs" />
<Compile Include="Plugins\ITorchPlugin.cs" />
<Compile Include="IServerControls.cs" />
<Compile Include="ITorchBase.cs" />
<Compile Include="Plugins\IWpfPlugin.cs" />
<Compile Include="ModAPI\Ingame\GridExtensions.cs" />
<Compile Include="ModAPI\TorchAPI.cs" />
<Compile Include="PluginAttribute.cs" />
<Compile Include="Plugins\PluginAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>

View File

@@ -12,5 +12,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: AssemblyVersion("1.0.89.507")]
[assembly: AssemblyFileVersion("1.0.89.507")]
[assembly: AssemblyVersion("1.0.89.540")]
[assembly: AssemblyFileVersion("1.0.89.540")]

View File

@@ -165,7 +165,13 @@
<Compile Include="TorchServiceInstaller.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="ViewModels\CharacterViewModel.cs" />
<Compile Include="ViewModels\ConfigDedicatedViewModel.cs" />
<Compile Include="ViewModels\EntityTreeViewModel.cs" />
<Compile Include="ViewModels\EntityViewModel.cs" />
<Compile Include="ViewModels\GridViewModel.cs" />
<Compile Include="ViewModels\PluginManagerViewModel.cs" />
<Compile Include="ViewModels\PluginViewModel.cs" />
<Compile Include="ViewModels\SessionSettingsViewModel.cs" />
<Compile Include="Views\AddWorkshopItemsDialog.xaml.cs">
<DependentUpon>AddWorkshopItemsDialog.xaml</DependentUpon>
@@ -179,9 +185,15 @@
<Compile Include="Views\ConfigControl.xaml.cs">
<DependentUpon>ConfigControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\EntitiesControl.xaml.cs">
<DependentUpon>EntitiesControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\ModsControl.xaml.cs">
<DependentUpon>ModsControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\PluginsControl.xaml.cs">
<DependentUpon>PluginsControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\TorchUI.xaml.cs">
<DependentUpon>TorchUI.xaml</DependentUpon>
</Compile>
@@ -242,10 +254,18 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\EntitiesControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\ModsControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\PluginsControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\TorchUI.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>

View File

@@ -25,7 +25,7 @@ namespace Torch.Server
public TorchConfig(string instanceName = "Torch", string instancePath = null, int autosaveInterval = 5, bool autoRestart = false)
{
InstanceName = instanceName;
InstancePath = instancePath ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Torch", InstanceName);
InstancePath = instancePath ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SpaceEngineersDedicated", InstanceName);
Autosave = autosaveInterval;
AutoRestart = autoRestart;
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Server.ViewModels
{
/*
public class CharacterViewModel : EntityViewModel
{
}*/
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Server.ViewModels
{
/*
public class EntityTreeViewModel : ViewModel
{
public string GridsHeader => null;
public MTObservableCollection<>
public void Refresh()
{
}
}*/
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.ModAPI;
using VRageMath;
namespace Torch.Server.ViewModels
{
/*
public class EntityViewModel : ViewModel
{
public IMyEntity Entity { get; }
public string Name
{
get { return Entity.DisplayName; }
set { TorchBase.Instance.i}
}
public string Position { get; }
public EntityViewModel(IMyEntity entity)
{
Entity = entity;
Name = entity.DisplayName;
Position
}
}*/
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Server.ViewModels
{
/*
public class GridViewModel : EntityViewModel
{
}*/
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Torch.API;
using Torch.API.Plugins;
namespace Torch.Server.ViewModels
{
public class PluginManagerViewModel : ViewModel
{
public MTObservableCollection<PluginViewModel> Plugins { get; } = new MTObservableCollection<PluginViewModel>();
private PluginViewModel _selectedPlugin;
public PluginViewModel SelectedPlugin
{
get { return _selectedPlugin; }
set { _selectedPlugin = value; OnPropertyChanged(); }
}
public PluginManagerViewModel() { }
public PluginManagerViewModel(IPluginManager pluginManager)
{
pluginManager.PluginsLoaded += PluginManager_PluginsLoaded;
}
private void PluginManager_PluginsLoaded(List<ITorchPlugin> obj)
{
Plugins.Clear();
foreach (var plugin in obj)
{
Plugins.Add(new PluginViewModel(plugin));
}
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using Torch.API;
using Torch.API.Plugins;
namespace Torch.Server.ViewModels
{
public class PluginViewModel
{
public UserControl Control
{
get
{
if (Plugin is IWpfPlugin p)
return p.GetControl();
return null;
}
}
public string Name { get; }
public ITorchPlugin Plugin { get; }
public PluginViewModel(ITorchPlugin plugin)
{
Plugin = plugin;
Name = $"{plugin.Name} ({plugin.Version})";
}
}
}

View File

@@ -14,9 +14,9 @@
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Time}"/>
<TextBlock Text="{Binding Timestamp}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding Player.Name}" FontWeight="Bold"/>
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Message}"/>
</WrapPanel>

View File

@@ -0,0 +1,22 @@
<UserControl x:Class="Torch.Server.Views.EntitiesControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Torch.Server.Views"
mc:Ignorable="d">
<DockPanel>
<DockPanel DockPanel.Dock="Left">
<StackPanel DockPanel.Dock="Bottom">
<Button Content="Refresh" Margin="3"></Button>
<Button Content="Delete Selected" Margin="3"></Button>
</StackPanel>
<TreeView Width="300" Margin="3" DockPanel.Dock="Top">
<TreeViewItem Header="Grids"/>
<TreeViewItem Header="Characters"/>
<TreeViewItem Header="Floating Objects"/>
</TreeView>
</DockPanel>
<Frame Margin="3"></Frame>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Torch.Server.Views
{
/// <summary>
/// Interaction logic for EntitiesControl.xaml
/// </summary>
public partial class EntitiesControl : UserControl
{
public EntitiesControl()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,27 @@
<UserControl x:Class="Torch.Server.Views.PluginsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Torch.Server.Views"
xmlns:viewModels="clr-namespace:Torch.Server.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<viewModels:PluginManagerViewModel/>
</UserControl.DataContext>
<DockPanel>
<ListView Width="150" ItemsSource="{Binding Plugins}" SelectedItem="{Binding SelectedPlugin}" Margin="3">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Margin="3">
<Label Content="{Binding SelectedPlugin.Name}" FontSize="16"/>
<Frame Content="{Binding SelectedPlugin.Control}"/>
</StackPanel>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using NLog;
using Torch.API;
using Torch.Server.ViewModels;
namespace Torch.Server.Views
{
/// <summary>
/// Interaction logic for PluginsControl.xaml
/// </summary>
public partial class PluginsControl : UserControl
{
public PluginsControl()
{
InitializeComponent();
}
public void BindServer(ITorchServer server)
{
var pluginManager = new PluginManagerViewModel(server.Plugins);
DataContext = pluginManager;
}
}
}

View File

@@ -6,7 +6,7 @@
xmlns:local="clr-namespace:Torch.Server"
xmlns:views="clr-namespace:Torch.Server.Views"
mc:Ignorable="d"
Title="Torch" Height="900" Width="600">
Title="Torch" Height="600" Width="800">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Margin="5,5,5,5" Orientation="Horizontal">
<Button x:Name="BtnStart" Content="Start" Height="24" Width="75" Margin="5,0,5,0" HorizontalAlignment="Left" Click="BtnStart_Click" IsDefault="True"/>
@@ -32,8 +32,10 @@
</DockPanel>
</TabItem>
<TabItem Header="Entity Manager">
<views:EntitiesControl/>
</TabItem>
<TabItem Header="Plugins">
<views:PluginsControl x:Name="Plugins"/>
</TabItem>
</TabControl>
</DockPanel>

View File

@@ -38,6 +38,7 @@ namespace Torch.Server
public TorchUI(TorchServer server)
{
_config = new TorchConfig();
_server = server;
InitializeComponent();
_startTime = DateTime.Now;
@@ -45,6 +46,7 @@ namespace Torch.Server
Chat.BindServer(server);
PlayerList.BindServer(server);
Plugins.BindServer(server);
}
public void LoadConfig(TorchConfig config)

View File

@@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using NLog;
using Torch.API;
using Torch.API.Plugins;
using Torch.Commands.Permissions;
using VRage.Game.ModAPI;
@@ -18,6 +22,9 @@ namespace Torch.Commands
public List<string> Path { get; } = new List<string>();
public ITorchPlugin Plugin { get; }
private readonly MethodInfo _method;
private ParameterInfo[] _parameters;
private int? _requiredParamCount;
public string SyntaxHelp { get; }
public Command(ITorchPlugin plugin, MethodInfo commandMethod)
{
@@ -47,6 +54,60 @@ namespace Torch.Commands
Name = commandAttribute.Name;
Description = commandAttribute.Description;
HelpText = commandAttribute.HelpText;
//parameters
_parameters = commandMethod.GetParameters();
var sb = new StringBuilder();
sb.Append($"/{string.Join(" ", Path)} ");
for (var i = 0; i < _parameters.Length; i++)
{
var param = _parameters[i];
if (param.HasDefaultValue)
{
_requiredParamCount = _requiredParamCount ?? i;
sb.Append($"[{param.ParameterType.Name} {param.Name}] ");
}
else
{
sb.Append($"<{param.ParameterType.Name} {param.Name}> ");
}
}
_requiredParamCount = _requiredParamCount ?? 0;
LogManager.GetLogger(nameof(Command)).Debug($"Params: {_parameters.Length} ({_requiredParamCount} required)");
SyntaxHelp = sb.ToString();
}
public bool TryInvoke(CommandContext context)
{
var parameters = new object[_parameters.Length];
if (context.Args.Count < _requiredParamCount)
return false;
//Convert args from string
for (var i = 0; i < _parameters.Length && i < context.Args.Count; i++)
{
if (context.Args[i].TryConvert(_parameters[i].ParameterType, out object obj))
parameters[i] = obj;
else
return false;
}
//Fill omitted default parameters
for (var i = 0; i < parameters.Length; i++)
{
if (parameters[i] == null)
parameters[i] = _parameters[i].DefaultValue;
}
var moduleInstance = (CommandModule)Activator.CreateInstance(Module);
moduleInstance.Context = context;
_method.Invoke(moduleInstance, parameters);
return true;
}
public void Invoke(CommandContext context)
@@ -56,4 +117,38 @@ namespace Torch.Commands
_method.Invoke(moduleInstance, null);
}
}
public static class Extensions
{
public static bool TryConvert(this string str, Type toType, out object val)
{
try
{
var converter = TypeDescriptor.GetConverter(toType);
val = converter.ConvertFromString(str);
return true;
}
catch (NotSupportedException)
{
val = null;
return false;
}
}
public static bool TryConvert<T>(this string str, out T val)
{
try
{
var converter = TypeDescriptor.GetConverter(typeof(T));
val = (T)converter.ConvertFromString(str);
return true;
}
catch (NotSupportedException)
{
val = default(T);
return false;
}
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Linq;
using System.Text.RegularExpressions;
using Torch.API;
using Torch.API.Plugins;
using VRage.Game;
using VRage.Game.ModAPI;

View File

@@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
using NLog;
using Sandbox.Game.World;
using Torch.API;
using Torch.API.Plugins;
using Torch.Managers;
using VRage.Game.ModAPI;
using VRage.Network;
@@ -99,8 +100,11 @@ namespace Torch.Commands
var splitArgs = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();
_log.Trace($"Invoking {cmdPath} for player {player.DisplayName}");
var context = new CommandContext(_torch, command.Plugin, player, argText, splitArgs);
command.Invoke(context);
_log.Info($"Player {player.DisplayName} ran command '{msg.Text}'");
//command.Invoke(context);
if (command.TryInvoke(context))
_log.Info($"Player {player.DisplayName} ran command '{msg.Text}'");
else
context.Respond($"Invalid Syntax: {command.SyntaxHelp}");
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.ModAPI;
using Torch.Commands.Permissions;
using Torch.Managers;
using VRage.Game.ModAPI;
@@ -39,7 +40,10 @@ namespace Torch.Commands
var sb = new StringBuilder();
if (command != null)
{
sb.AppendLine($"Syntax: {command.SyntaxHelp}");
sb.AppendLine(command.HelpText);
}
sb.AppendLine($"Subcommands: {string.Join(", ", children)}");

View File

@@ -129,6 +129,12 @@ namespace Torch.Managers
});
}
public void RevealAll()
{
for (var i = _concealGroups.Count - 1; i >= 0; i--)
RevealGroup(_concealGroups[i]);
}
private void ConcealTimerElapsed(object sender, ElapsedEventArgs e)
{
if (_concealInProgress)
@@ -164,6 +170,8 @@ namespace Torch.Managers
void UnregisterRecursive(IMyEntity e)
{
MyEntities.UnregisterForUpdate((MyEntity)e);
if (e.Hierarchy == null)
return;
foreach (var child in e.Hierarchy.Children)
UnregisterRecursive(child.Entity);
}
@@ -181,6 +189,8 @@ namespace Torch.Managers
void RegisterRecursive(IMyEntity e)
{
MyEntities.RegisterForUpdate((MyEntity)e);
if (e.Hierarchy == null)
return;
foreach (var child in e.Hierarchy.Children)
RegisterRecursive(child.Entity);
}

View File

@@ -13,6 +13,7 @@ using NLog;
using Sandbox;
using Sandbox.ModAPI;
using Torch.API;
using Torch.API.Plugins;
using Torch.Commands;
using Torch.Managers;
using VRage.Plugins;
@@ -27,12 +28,14 @@ namespace Torch.Managers
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
public const string PluginDir = "Plugins";
private readonly List<ITorchPlugin> _plugins = new List<ITorchPlugin>();
public List<ITorchPlugin> Plugins { get; } = new List<ITorchPlugin>();
public CommandManager Commands { get; private set; }
public float LastUpdateMs => _lastUpdateMs;
private volatile float _lastUpdateMs;
public event Action<List<ITorchPlugin>> PluginsLoaded;
public PluginManager(ITorchBase torch)
{
_torch = torch;
@@ -62,7 +65,7 @@ namespace Torch.Managers
public void UpdatePlugins()
{
var s = Stopwatch.StartNew();
Parallel.ForEach(_plugins, p => p.Update());
Parallel.ForEach(Plugins, p => p.Update());
s.Stop();
_lastUpdateMs = (float)s.Elapsed.TotalMilliseconds;
}
@@ -72,10 +75,10 @@ namespace Torch.Managers
/// </summary>
public void DisposePlugins()
{
foreach (var plugin in _plugins)
foreach (var plugin in Plugins)
plugin.Dispose();
_plugins.Clear();
Plugins.Clear();
}
/// <summary>
@@ -105,7 +108,7 @@ namespace Torch.Managers
throw new TypeLoadException($"Plugin '{type.FullName}' is missing a {nameof(PluginAttribute)}");
_log.Info($"Loading plugin {plugin.Name} ({plugin.Version})");
_plugins.Add(plugin);
Plugins.Add(plugin);
Commands.RegisterPluginCommands(plugin);
}
@@ -119,12 +122,13 @@ namespace Torch.Managers
}
}
_plugins.ForEach(p => p.Init(_torch));
Plugins.ForEach(p => p.Init(_torch));
PluginsLoaded?.Invoke(Plugins);
}
public IEnumerator<ITorchPlugin> GetEnumerator()
{
return _plugins.GetEnumerator();
return Plugins.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()

21
Torch/PluginOptions.cs Normal file
View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch
{
public class PluginOptions
{
public virtual string Save()
{
return null;
}
public virtual void Load(string data)
{
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SteamSDK;
using Sandbox;
namespace Torch

View File

@@ -162,6 +162,7 @@
<Compile Include="Managers\NetworkManager\NetworkHandlerBase.cs" />
<Compile Include="Managers\NetworkManager\NetworkManager.cs" />
<Compile Include="Managers\MultiplayerManager.cs" />
<Compile Include="PluginOptions.cs" />
<Compile Include="Reflection.cs" />
<Compile Include="Managers\ScriptingManager.cs" />
<Compile Include="TorchBase.cs" />

View File

@@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using NLog;
using Sandbox.ModAPI.Ingame;
namespace Torch
{

View File

@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using NLog;
using Torch.API;
using Torch.API.Plugins;
namespace Torch
{