diff --git a/Torch/ConnectionState.cs b/Torch.API/ConnectionState.cs
similarity index 93%
rename from Torch/ConnectionState.cs
rename to Torch.API/ConnectionState.cs
index 991fbc4..5565c6c 100644
--- a/Torch/ConnectionState.cs
+++ b/Torch.API/ConnectionState.cs
@@ -1,6 +1,6 @@
using System;
-namespace Torch
+namespace Torch.API
{
///
/// Identifies a player's current connection state.
diff --git a/Torch.API/ITorchServer.cs b/Torch.API/IChatItem.cs
similarity index 55%
rename from Torch.API/ITorchServer.cs
rename to Torch.API/IChatItem.cs
index 4f2aecf..bfab467 100644
--- a/Torch.API/ITorchServer.cs
+++ b/Torch.API/IChatItem.cs
@@ -6,9 +6,10 @@ using System.Threading.Tasks;
namespace Torch.API
{
- public interface ITorchServer
+ public interface IChatItem
{
- void Start();
- void Stop();
+ IPlayer Player { get; }
+ string Message { get; }
+ DateTime Time { get; }
}
}
diff --git a/Torch.API/IEnvironmentInfo.cs b/Torch.API/IEnvironmentInfo.cs
deleted file mode 100644
index d941b2b..0000000
--- a/Torch.API/IEnvironmentInfo.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace Torch.API
-{
- public interface IEnvironmentInfo
- {
- EnvironmentType Type { get; }
- }
-
- public enum EnvironmentType
- {
- DedicatedServer,
- GameClient
- }
-}
\ No newline at end of file
diff --git a/Torch.API/IMultiplayer.cs b/Torch.API/IMultiplayer.cs
new file mode 100644
index 0000000..f7b18f4
--- /dev/null
+++ b/Torch.API/IMultiplayer.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Torch.API
+{
+ public interface IMultiplayer
+ {
+ event Action PlayerJoined;
+ event Action PlayerLeft;
+ event Action MessageReceived;
+ Dictionary Players { get; }
+ List Chat { get; }
+ void SendMessage(string message);
+ void KickPlayer(ulong id);
+ void BanPlayer(ulong id, bool banned = true);
+ }
+}
\ No newline at end of file
diff --git a/Torch.API/IPlayer.cs b/Torch.API/IPlayer.cs
new file mode 100644
index 0000000..9d673e0
--- /dev/null
+++ b/Torch.API/IPlayer.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Torch.API
+{
+ public interface IPlayer
+ {
+ ulong SteamId { get; }
+ List IdentityIds { get; }
+ string Name { get; }
+ ConnectionState State { get; }
+ DateTime LastConnected { get; }
+ void SetConnectionState(ConnectionState state);
+ }
+}
diff --git a/Torch.API/IPluginManager.cs b/Torch.API/IPluginManager.cs
new file mode 100644
index 0000000..142976f
--- /dev/null
+++ b/Torch.API/IPluginManager.cs
@@ -0,0 +1,21 @@
+using System;
+using VRage.Collections;
+using VRage.Plugins;
+
+namespace Torch.API
+{
+ public interface IPluginManager
+ {
+ ListReader Plugins { get; }
+
+ string[] GetPluginFolders();
+ string GetPluginName(Type pluginType);
+ void LoadAllPlugins();
+ void LoadPlugin(IPlugin plugin);
+ void LoadPluginFolder(string folderName);
+ void ReloadAll();
+ void ReloadPlugin(IPlugin plugin, bool forceNonPiston = false);
+ bool UnblockDll(string fileName);
+ void UnloadPlugin(IPlugin plugin);
+ }
+}
\ No newline at end of file
diff --git a/Torch.API/ITorchBase.cs b/Torch.API/ITorchBase.cs
new file mode 100644
index 0000000..193c592
--- /dev/null
+++ b/Torch.API/ITorchBase.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Torch.API
+{
+ public interface ITorchBase
+ {
+ event Action SessionLoaded;
+ IMultiplayer Multiplayer { get; }
+ IPluginManager Plugins { get; }
+ void GameAction(Action action);
+ void BeginGameAction(Action action, Action
public partial class ChatControl : UserControl
{
+ private ITorchServer _server;
+
public ChatControl()
{
InitializeComponent();
- ChatItems.ItemsSource = TorchServer.Multiplayer.ChatView;
+ }
+
+ public void BindServer(ITorchServer server)
+ {
+ _server = server;
+ server.Multiplayer.MessageReceived += Refresh;
+ Refresh();
+ }
+
+ private void Refresh(IChatItem chatItem = null)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ ChatItems.ItemsSource = null;
+ ChatItems.ItemsSource = _server.Multiplayer.Chat;
+ });
}
private void SendButton_Click(object sender, RoutedEventArgs e)
@@ -47,7 +65,7 @@ namespace Torch.Server
{
//Can't use Message.Text directly because of object ownership in WPF.
var text = Message.Text;
- TorchServer.Multiplayer.SendMessage(text);
+ _server.Multiplayer.SendMessage(text);
Message.Text = "";
}
}
diff --git a/Torch.Server/Views/PlayerListControl.xaml.cs b/Torch.Server/Views/PlayerListControl.xaml.cs
index 68c759f..87d30df 100644
--- a/Torch.Server/Views/PlayerListControl.xaml.cs
+++ b/Torch.Server/Views/PlayerListControl.xaml.cs
@@ -18,6 +18,7 @@ using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.World;
using SteamSDK;
+using Torch.API;
namespace Torch.Server
{
@@ -26,27 +27,45 @@ namespace Torch.Server
///
public partial class PlayerListControl : UserControl
{
+ private ITorchServer _server;
+
public PlayerListControl()
{
InitializeComponent();
- PlayerList.ItemsSource = TorchServer.Multiplayer.PlayersView;
+ }
+
+ public void BindServer(ITorchServer server)
+ {
+ _server = server;
+ server.Multiplayer.PlayerJoined += Refresh;
+ server.Multiplayer.PlayerLeft += Refresh;
+ Refresh();
+ }
+
+ private void Refresh(IPlayer player = null)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ PlayerList.ItemsSource = null;
+ PlayerList.ItemsSource = _server.Multiplayer.Players;
+ });
}
private void KickButton_Click(object sender, RoutedEventArgs e)
{
- var player = PlayerList.SelectedItem as PlayerInfo;
+ var player = PlayerList.SelectedItem as Player;
if (player != null)
{
- TorchServer.Multiplayer.KickPlayer(player.SteamId);
+ _server.Multiplayer.KickPlayer(player.SteamId);
}
}
private void BanButton_Click(object sender, RoutedEventArgs e)
{
- var player = PlayerList.SelectedItem as PlayerInfo;
+ var player = PlayerList.SelectedItem as Player;
if (player != null)
{
- TorchServer.Multiplayer.BanPlayer(player.SteamId);
+ _server.Multiplayer.BanPlayer(player.SteamId);
}
}
}
diff --git a/Torch.Server/Views/PistonUI.xaml b/Torch.Server/Views/TorchUI.xaml
similarity index 97%
rename from Torch.Server/Views/PistonUI.xaml
rename to Torch.Server/Views/TorchUI.xaml
index 1afa759..f93e62b 100644
--- a/Torch.Server/Views/PistonUI.xaml
+++ b/Torch.Server/Views/TorchUI.xaml
@@ -1,4 +1,4 @@
-
- /// Interaction logic for PistonUI.xaml
+ /// Interaction logic for TorchUI.xaml
///
- public partial class PistonUI : Window
+ public partial class TorchUI : Window
{
+ private ITorchServer _server;
private DateTime _startTime;
private readonly Timer _uiUpdate = new Timer
{
@@ -31,13 +35,15 @@ namespace Torch.Server
AutoReset = true,
};
- public PistonUI()
+ public TorchUI(ITorchServer server)
{
+ _server = server;
InitializeComponent();
_startTime = DateTime.Now;
_uiUpdate.Elapsed += UiUpdate_Elapsed;
- TabControl.Items.Add(new TabItem());
+ Chat.BindServer(server);
+ PlayerList.BindServer(server);
}
private void UiUpdate_Elapsed(object sender, ElapsedEventArgs e)
@@ -61,7 +67,7 @@ namespace Torch.Server
((Button) sender).IsEnabled = false;
BtnStop.IsEnabled = true;
_uiUpdate.Start();
- TorchServer.Server.StartServerThread();
+ new Thread(() => _server.Start()).Start();
}
private void BtnStop_Click(object sender, RoutedEventArgs e)
@@ -72,12 +78,12 @@ namespace Torch.Server
//HACK: Uncomment when restarting is possible.
//BtnStart.IsEnabled = true;
_uiUpdate.Stop();
- TorchServer.Server.StopServer();
+ _server.Stop();
}
protected override void OnClosing(CancelEventArgs e)
{
- TorchServer.Reset();
+ _server.Stop();
}
private void BtnRestart_Click(object sender, RoutedEventArgs e)
diff --git a/Torch/Collections/MTObservableCollection.cs b/Torch/Collections/MTObservableCollection.cs
index 3c1d1e1..6b89071 100644
--- a/Torch/Collections/MTObservableCollection.cs
+++ b/Torch/Collections/MTObservableCollection.cs
@@ -20,12 +20,11 @@ namespace Torch
{
var dispObj = nh.Target as DispatcherObject;
- Dispatcher dispatcher = dispObj?.Dispatcher;
+ var dispatcher = dispObj?.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
- (Action)(() => nh.Invoke(this,
- new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
+ (Action)(() => nh.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
diff --git a/Torch/Collections/PlayerInfoCache.cs b/Torch/Collections/PlayerInfoCache.cs
index c5e64ef..0a9c0fb 100644
--- a/Torch/Collections/PlayerInfoCache.cs
+++ b/Torch/Collections/PlayerInfoCache.cs
@@ -3,25 +3,25 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Torch.API;
namespace Torch
{
public static class PlayerInfoCache
{
- private static readonly Dictionary _cache = new Dictionary();
+ private static readonly Dictionary _cache = new Dictionary();
- public static PlayerInfo GetOrCreate(ulong steamId)
+ public static Player GetOrCreate(ulong steamId)
{
- PlayerInfo info;
- if (_cache.TryGetValue(steamId, out info))
+ if (_cache.TryGetValue(steamId, out Player info))
return info;
- info = new PlayerInfo(steamId) {State = ConnectionState.Unknown};
+ info = new Player(steamId) {State = ConnectionState.Unknown};
_cache.Add(steamId, info);
return info;
}
- public static void Add(PlayerInfo info)
+ public static void Add(Player info)
{
if (_cache.ContainsKey(info.SteamId))
return;
diff --git a/Torch/Commands/ChatCommandModule.cs b/Torch/Commands/ChatCommandModule.cs
index 84dd0f6..53f9b94 100644
--- a/Torch/Commands/ChatCommandModule.cs
+++ b/Torch/Commands/ChatCommandModule.cs
@@ -5,6 +5,6 @@ namespace Torch.Commands
public class ChatCommandModule
{
public ITorchPlugin Plugin { get; set; }
- public ITorchServer Server { get; set; }
+ public ITorchBase Server { get; set; }
}
}
\ No newline at end of file
diff --git a/Torch/Commands/CommandContext.cs b/Torch/Commands/CommandContext.cs
index e3332d1..f711afd 100644
--- a/Torch/Commands/CommandContext.cs
+++ b/Torch/Commands/CommandContext.cs
@@ -1,8 +1,26 @@
-namespace Torch.Commands
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace Torch.Commands
{
public struct CommandContext
{
public string Argument;
public ulong SteamId;
+
+ ///
+ /// Splits the argument by single words and quoted blocks.
+ ///
+ ///
+ public string[] SplitArgument()
+ {
+ var split = Regex.Split(Argument, "(\"[^\"]+\"|\\S+)");
+ for (var i = 0; i < split.Length; i++)
+ {
+ split[i] = Regex.Replace(split[i], "\"", "");
+ }
+
+ return split;
+ }
}
}
\ No newline at end of file
diff --git a/Torch/Commands/CommandSystem.cs b/Torch/Commands/CommandSystem.cs
index c5e6aa8..5891642 100644
--- a/Torch/Commands/CommandSystem.cs
+++ b/Torch/Commands/CommandSystem.cs
@@ -7,12 +7,12 @@ namespace Torch.Commands
{
public class CommandSystem
{
- public ITorchServer Server { get; }
+ public ITorchBase Server { get; }
public char Prefix { get; set; }
public Dictionary Commands { get; } = new Dictionary();
- public CommandSystem(ITorchServer server, char prefix = '/')
+ public CommandSystem(ITorchBase server, char prefix = '/')
{
Server = server;
Prefix = prefix;
@@ -40,6 +40,12 @@ namespace Torch.Commands
if (commandAttrib == null)
continue;
+ if (Commands.ContainsKey(commandAttrib.Name))
+ {
+ Console.WriteLine($"[ERROR]: Command \"{method.Name}\" is already registered!");
+ continue;
+ }
+
var parameters = method.GetParameters();
if (parameters.Length != 1 || parameters[0].ParameterType != typeof(CommandContext))
{
@@ -82,8 +88,6 @@ namespace Torch.Commands
Commands[cmdName].Invoke(context);
-
- //Regex.Matches(command, "(\"[^\"]+\"|\\S+)");
}
}
}
diff --git a/Torch.Server/MultiplayerManager.cs b/Torch/MultiplayerManager.cs
similarity index 81%
rename from Torch.Server/MultiplayerManager.cs
rename to Torch/MultiplayerManager.cs
index 66d8ffb..22cc2c0 100644
--- a/Torch.Server/MultiplayerManager.cs
+++ b/Torch/MultiplayerManager.cs
@@ -18,40 +18,43 @@ using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.World;
using SteamSDK;
-using Torch.Server.ViewModels;
+using Torch.API;
+using Torch.ViewModels;
using VRage.Game;
using VRage.Library.Collections;
using VRage.Network;
using VRage.Utils;
-namespace Torch.Server
+namespace Torch
{
///
/// Provides a proxy to the game's multiplayer-related functions.
///
- public class MultiplayerManager
+ public class MultiplayerManager : IMultiplayer
{
- public event Action PlayerJoined;
- public event Action PlayerLeft;
- public event Action ChatMessageReceived;
+ public event Action PlayerJoined;
+ public event Action PlayerLeft;
+ public event Action MessageReceived;
- public MTObservableCollection PlayersView { get; } = new MTObservableCollection();
- public MTObservableCollection ChatView { get; } = new MTObservableCollection();
- public PlayerInfo LocalPlayer { get; private set; }
+ //public MTObservableCollection PlayersView { get; } = new MTObservableCollection();
+ //public MTObservableCollection ChatView { get; } = new MTObservableCollection();
+ public List Chat { get; } = new List();
+ public Dictionary Players { get; } = new Dictionary();
+ public Player LocalPlayer { get; private set; }
- private TorchServer _server;
+ private readonly ITorchBase _torch;
- internal MultiplayerManager(TorchServer server)
+ internal MultiplayerManager(ITorchBase torch)
{
- _server = server;
- _server.Server.SessionLoaded += OnSessionLoaded;
+ _torch = torch;
+ _torch.SessionLoaded += OnSessionLoaded;
}
- public void KickPlayer(ulong steamId) => _server.Server.BeginGameAction(() => MyMultiplayer.Static.KickClient(steamId));
+ public void KickPlayer(ulong steamId) => _torch.BeginGameAction(() => MyMultiplayer.Static.KickClient(steamId));
public void BanPlayer(ulong steamId, bool banned = true)
{
- _server.Server.BeginGameAction(() =>
+ _torch.BeginGameAction(() =>
{
MyMultiplayer.Static.BanClient(steamId, banned);
if (_gameOwnerIds.ContainsKey(steamId))
@@ -65,12 +68,12 @@ namespace Torch.Server
public void SendMessage(string message)
{
MyMultiplayer.Static.SendChatMessage(message);
- ChatView.Add(new ChatItemInfo(LocalPlayer, message));
+ //ChatView.Add(new ChatItem(LocalPlayer, message));
}
private void OnSessionLoaded()
{
- LocalPlayer = new PlayerInfo(MyMultiplayer.Static.ServerId) { Name = "Server", State = ConnectionState.Connected };
+ LocalPlayer = new Player(MyMultiplayer.Static.ServerId) { Name = "Server", State = ConnectionState.Connected };
MyMultiplayer.Static.ChatMessageReceived += OnChatMessage;
MyMultiplayer.Static.ClientKicked += OnClientKicked;
@@ -79,21 +82,21 @@ namespace Torch.Server
//TODO: Move these with the methods?
RemoveHandlers();
- SteamSDK.SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse;
- SteamSDK.SteamServerAPI.Instance.GameServer.UserGroupStatus += UserGroupStatus;
+ SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse;
+ SteamServerAPI.Instance.GameServer.UserGroupStatus += UserGroupStatus;
_members = (List)typeof(MyDedicatedServerBase).GetField("m_members", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
_waitingForGroup = (HashSet)typeof(MyDedicatedServerBase).GetField("m_waitingForGroup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
}
private void OnChatMessage(ulong steamId, string message, ChatEntryTypeEnum chatType)
{
- var player = PlayersView.FirstOrDefault(p => p.SteamId == steamId);
- if (player == null || player == LocalPlayer)
+ var player = MyMultiplayer.Static.GetMemberName(steamId);
+ if (string.IsNullOrEmpty(player))
return;
- var info = new ChatItemInfo(player, message);
- ChatView.Add(info);
- ChatMessageReceived?.Invoke(info);
+ var info = new ChatItem(new Player(steamId), message);
+ Chat.Add(info);
+ MessageReceived?.Invoke(info);
}
///
@@ -102,8 +105,20 @@ namespace Torch.Server
private void OnPlayerRequesting(PlayerRequestArgs args)
{
var steamId = args.PlayerId.SteamId;
- var player = new PlayerInfo(steamId) {State = ConnectionState.Connected};
- PlayersView.Add(player);
+
+ IPlayer player;
+ if (!Players.ContainsKey(steamId))
+ {
+ player = new Player(steamId) { State = ConnectionState.Connected };
+ Players.Add(steamId, player);
+ }
+ else
+ {
+ player = Players[steamId];
+ player.SetConnectionState(ConnectionState.Connected);
+ }
+
+ Logger.Write($"{player.Name} connected.");
PlayerJoined?.Invoke(player);
}
@@ -114,13 +129,12 @@ namespace Torch.Server
private void OnClientLeft(ulong steamId, ChatMemberStateChangeEnum stateChange)
{
- var player = PlayersView.FirstOrDefault(p => p.SteamId == steamId);
-
- if (player == null)
+ if (!Players.ContainsKey(steamId))
return;
- player.State = (ConnectionState)stateChange;
- PlayersView.Remove(player);
+ var player = Players[steamId];
+ Logger.Write($"{player.Name} disconnected ({(ConnectionState)stateChange}).");
+ player.SetConnectionState((ConnectionState)stateChange);
PlayerLeft?.Invoke(player);
}
diff --git a/Torch/PlayerInfo.cs b/Torch/Player.cs
similarity index 59%
rename from Torch/PlayerInfo.cs
rename to Torch/Player.cs
index f3e91f5..337a0c8 100644
--- a/Torch/PlayerInfo.cs
+++ b/Torch/Player.cs
@@ -1,11 +1,14 @@
-using Sandbox.Engine.Multiplayer;
+using System;
+using System.Collections.Generic;
+using Sandbox.Engine.Multiplayer;
+using Torch.API;
namespace Torch
{
///
/// Stores player information in an observable format.
///
- public class PlayerInfo : ViewModel
+ public class Player : ViewModel, IPlayer
{
private ulong _steamId;
private string _name;
@@ -23,17 +26,30 @@ namespace Torch
set { _name = value; OnPropertyChanged(); }
}
+ //TODO: track identity history
+ public List IdentityIds { get; } = new List();
+
+ public DateTime LastConnected { get; private set; }
+
public ConnectionState State
{
get { return _state; }
set { _state = value; OnPropertyChanged(); }
}
- public PlayerInfo(ulong steamId)
+ public Player(ulong steamId)
{
_steamId = steamId;
_name = MyMultiplayer.Static.GetMemberName(steamId);
_state = ConnectionState.Unknown;
}
+
+ public void SetConnectionState(ConnectionState state)
+ {
+ if (state == ConnectionState.Connected)
+ LastConnected = DateTime.Now;
+
+ State = state;
+ }
}
}
\ No newline at end of file
diff --git a/Torch/PluginManager.cs b/Torch/PluginManager.cs
index 0035a38..5af9a52 100644
--- a/Torch/PluginManager.cs
+++ b/Torch/PluginManager.cs
@@ -15,7 +15,7 @@ using VRage.Library.Collections;
namespace Torch
{
- public class PluginManager
+ public class PluginManager : IPluginManager
{
//TODO: Disable reloading if the plugin has static elements because they prevent a full reload.
diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj
index 1f70850..9b6085b 100644
--- a/Torch/Torch.csproj
+++ b/Torch/Torch.csproj
@@ -126,12 +126,14 @@
-
+
+
-
+
+
diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs
new file mode 100644
index 0000000..0037a29
--- /dev/null
+++ b/Torch/TorchBase.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Sandbox;
+using Torch.API;
+
+namespace Torch
+{
+ public abstract class TorchBase : ITorchBase
+ {
+ public IPluginManager Plugins { get; protected set; }
+ public IMultiplayer Multiplayer { get; protected set; }
+
+ public event Action SessionLoaded;
+
+ protected void InvokeSessionLoaded()
+ {
+ SessionLoaded?.Invoke();
+ }
+
+ protected TorchBase()
+ {
+ Plugins = new PluginManager();
+ Multiplayer = new MultiplayerManager(this);
+ }
+
+ ///
+ /// Invokes an action on the game thread and blocks until completion
+ ///
+ ///
+ public void GameAction(Action action)
+ {
+ if (action == null)
+ return;
+
+ try
+ {
+ if (Thread.CurrentThread == MySandboxGame.Static.UpdateThread)
+ {
+ action();
+ }
+ else
+ {
+ AutoResetEvent e = new AutoResetEvent(false);
+
+ MySandboxGame.Static.Invoke(() =>
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception ex)
+ {
+ //log
+ }
+ finally
+ {
+ e.Set();
+ }
+ });
+
+ //timeout so we don't accidentally hang the server
+ e.WaitOne(60000);
+ }
+ }
+ catch (Exception ex)
+ {
+ //we need a logger :(
+ }
+ }
+
+ ///
+ /// Queues an action for invocation on the game thread and optionally runs a callback on completion
+ ///
+ ///
+ ///
+ ///
+ public void BeginGameAction(Action action, Action