Compare commits
19 Commits
1.0.180.47
...
1.1.207.7
Author | SHA1 | Date | |
---|---|---|---|
![]() |
76a13dc53a | ||
![]() |
df0f8072a9 | ||
![]() |
87d9825c91 | ||
![]() |
3dba4f744f | ||
![]() |
1fcfe6fb5f | ||
![]() |
3ece4baba6 | ||
![]() |
f49dae2cbf | ||
![]() |
ddf15d756a | ||
![]() |
96d1faddbe | ||
![]() |
17ee96038c | ||
![]() |
e9b432288e | ||
![]() |
b814d1210b | ||
![]() |
c137fb4953 | ||
![]() |
4acce1c9c9 | ||
![]() |
8ab16c3d30 | ||
![]() |
7373dd37a6 | ||
![]() |
1251b945bc | ||
![]() |
79fe6a08ab | ||
![]() |
5e0f69e0e6 |
24
CHANGELOG.md
Normal file
24
CHANGELOG.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Torch 1.1.205.478
|
||||||
|
* Notes
|
||||||
|
- This release makes significant changes to TorchConfig.xml. It has been renamed to Torch.cfg and has different options.
|
||||||
|
* Features
|
||||||
|
- Plugins, Torch, and the DS can now all update automatically
|
||||||
|
- Changed command prefix to !
|
||||||
|
- Added manual save command (thanks to Maldark)
|
||||||
|
- Added restart command
|
||||||
|
- Improved instance creation: now creates an entire skeleton instance with blank config
|
||||||
|
- Added instance name to console title
|
||||||
|
* Fixes
|
||||||
|
- Optimized UI so it's snappier and freezes less often
|
||||||
|
- Fixed NetworkManager.RaiseEvent overload that had an off-by-one bug
|
||||||
|
- Fixed chat window so it automatically scrolls down
|
||||||
|
|
||||||
|
# Torch 1.0.182.329
|
||||||
|
* Improved logging, logs now to go the Logs folder and aren't deleted on start
|
||||||
|
* Fixed chat tab not enabling with -autostart
|
||||||
|
* Fixed player list
|
||||||
|
* Watchdog time-out is now configurable in TorchConfig.xml
|
||||||
|
* Fixed infinario log spam
|
||||||
|
* Fixed crash when sending empty message from chat tab
|
||||||
|
* Fixed permissions on Torch commands
|
||||||
|
* Changed plugin StoragePath to the current instance path (per-instance configs)
|
26
CONTRIBUTING.md
Normal file
26
CONTRIBUTING.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Making a Pull Request
|
||||||
|
* Fork this repository and make sure your local **master** branch is up to date with the main repository.
|
||||||
|
* Create a new branch for your addition with an appropriate name, e.g. **add-restart-command**
|
||||||
|
* PRs work by submitting the *entire* branch, so this allows you to continue work without locking up your whole repository.
|
||||||
|
* Commit your changes to that branch, making sure that you **follow the code guidelines below**.
|
||||||
|
* Submit your branch as a PR to be reviewed.
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
* Types: **PascalCase**
|
||||||
|
* Prefix interfaces with "**I**"
|
||||||
|
* Suffix delegates with "**Del**"
|
||||||
|
* Methods: **PascalCase**
|
||||||
|
* Method names should generally use verbs in the infinitive tense, for example `GetValue()` or `OpenFile()`. Callbacks and events should use present continuous (-ing) or past tense depending on the context.
|
||||||
|
* Non-Private Members: **PascalCase**
|
||||||
|
* Private Members: **_camelCase**
|
||||||
|
|
||||||
|
## Code Design
|
||||||
|
* **One type per file** with the exception of nested types and delegate declarations.
|
||||||
|
* **No public fields** except for consts, use properties instead
|
||||||
|
* **No stateful static types.** These are a pain to clean up, static types should not store any information.
|
||||||
|
* Use **[dependency injection](https://stackoverflow.com/a/130862)** when possible. Most Torch code uses constructor injection.
|
||||||
|
* **Events and actions** should be null checked before calling or invoked with the `action?.Invoke()` syntax.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
* All types and members not marked **private** or **internal** should have XML documentation using the `/// <summary>` tag.
|
||||||
|
* Interface implementations and overridden methods should use the `/// <inheritdoc />` tag unless the summary needs to be changed from the base/interface summary.
|
10
NLog.config
10
NLog.config
@@ -3,13 +3,13 @@
|
|||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
|
||||||
<targets>
|
<targets>
|
||||||
<target name="logfile" layout="${longdate} [${level:uppercase=true}] ${logger}: ${message}" xsi:type="File" fileName="Torch.log" deleteOldFileOnStartup="true"/>
|
<target xsi:type="File" name="main" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" fileName="Logs\Torch-${shortdate}.log" />
|
||||||
<target name="console" layout="${longdate} [${level:uppercase=true}] ${logger}: ${message}" xsi:type="ColoredConsole" />
|
<target xsi:type="File" name="chat" layout="${longdate} ${message}" fileName="Logs\Chat.log" />
|
||||||
|
<target xsi:type="ColoredConsole" name="console" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" />
|
||||||
</targets>
|
</targets>
|
||||||
|
|
||||||
<rules>
|
<rules>
|
||||||
<logger name="*" minlevel="Info" writeTo="logfile" />
|
<logger name="*" minlevel="Info" writeTo="main, console" />
|
||||||
<logger name="*" minlevel="Info" writeTo="console" />
|
<logger name="Chat" minlevel="Info" writeTo="chat" />
|
||||||
</rules>
|
</rules>
|
||||||
|
|
||||||
</nlog>
|
</nlog>
|
@@ -14,7 +14,6 @@ Torch is the successor to SE Server Extender and gives server admins the tools t
|
|||||||
To build Torch you must first have a complete SE Dedicated installation somewhere. Before you open the solution, run the Setup batch file and enter the path of that installation's DedicatedServer64 folder. The script will make a symlink to that folder so the Torch solution can find the DLL references it needs.
|
To build Torch you must first have a complete SE Dedicated installation somewhere. Before you open the solution, run the Setup batch file and enter the path of that installation's DedicatedServer64 folder. The script will make a symlink to that folder so the Torch solution can find the DLL references it needs.
|
||||||
|
|
||||||
# Installation Guide
|
# Installation Guide
|
||||||
Note: Until Torch is in a stable, nearly feature complete state there will not be any binaries available. You'll have to compile the solution yourself.
|
|
||||||
|
|
||||||
### Automatic (recommended)
|
### Automatic (recommended)
|
||||||
* Unzip Torch to its own folder, run Torch.Server.exe and enter 'y' in the prompt for automatic updates. Torch will automatically download the Space Engineers files and generate all of the configs/folders necessary.
|
* Unzip Torch to its own folder, run Torch.Server.exe and enter 'y' in the prompt for automatic updates. Torch will automatically download the Space Engineers files and generate all of the configs/folders necessary.
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -8,32 +8,110 @@ using VRage.Game.ModAPI;
|
|||||||
|
|
||||||
namespace Torch.API
|
namespace Torch.API
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// API for Torch functions shared between client and server.
|
||||||
|
/// </summary>
|
||||||
public interface ITorchBase
|
public interface ITorchBase
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when the session begins loading.
|
||||||
|
/// </summary>
|
||||||
event Action SessionLoading;
|
event Action SessionLoading;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when the session finishes loading.
|
||||||
|
/// </summary>
|
||||||
event Action SessionLoaded;
|
event Action SessionLoaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires when the session begins unloading.
|
||||||
|
/// </summary>
|
||||||
event Action SessionUnloading;
|
event Action SessionUnloading;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when the session finishes unloading.
|
||||||
|
/// </summary>
|
||||||
event Action SessionUnloaded;
|
event Action SessionUnloaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for the current instance.
|
||||||
|
/// </summary>
|
||||||
ITorchConfig Config { get; }
|
ITorchConfig Config { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IMultiplayerManager"/>
|
||||||
IMultiplayerManager Multiplayer { get; }
|
IMultiplayerManager Multiplayer { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IPluginManager"/>
|
||||||
IPluginManager Plugins { get; }
|
IPluginManager Plugins { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The binary version of the current instance.
|
||||||
|
/// </summary>
|
||||||
Version TorchVersion { get; }
|
Version TorchVersion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke an action on the game thread.
|
||||||
|
/// </summary>
|
||||||
void Invoke(Action action);
|
void Invoke(Action action);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke an action on the game thread and block until it has completed.
|
||||||
|
/// If this is called on the game thread the action will execute immediately.
|
||||||
|
/// </summary>
|
||||||
void InvokeBlocking(Action action);
|
void InvokeBlocking(Action action);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoke an action on the game thread asynchronously.
|
||||||
|
/// </summary>
|
||||||
Task InvokeAsync(Action action);
|
Task InvokeAsync(Action action);
|
||||||
string[] RunArgs { get; set; }
|
|
||||||
bool IsOnGameThread();
|
/// <summary>
|
||||||
|
/// Start the Torch instance.
|
||||||
|
/// </summary>
|
||||||
void Start();
|
void Start();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop the Torch instance.
|
||||||
|
/// </summary>
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restart the Torch instance.
|
||||||
|
/// </summary>
|
||||||
|
void Restart();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a save of the game.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callerId">Id of the player who initiated the save.</param>
|
||||||
|
Task Save(long callerId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the Torch instance.
|
||||||
|
/// </summary>
|
||||||
void Init();
|
void Init();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get an <see cref="IManager"/> that is part of the Torch instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Manager type</typeparam>
|
||||||
T GetManager<T>() where T : class, IManager;
|
T GetManager<T>() where T : class, IManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API for the Torch server.
|
||||||
|
/// </summary>
|
||||||
public interface ITorchServer : ITorchBase
|
public interface ITorchServer : ITorchBase
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Path of the dedicated instance folder.
|
||||||
|
/// </summary>
|
||||||
string InstancePath { get; }
|
string InstancePath { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API for the Torch client.
|
||||||
|
/// </summary>
|
||||||
public interface ITorchClient : ITorchBase
|
public interface ITorchClient : ITorchBase
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@@ -1,12 +1,23 @@
|
|||||||
namespace Torch
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Torch
|
||||||
{
|
{
|
||||||
public interface ITorchConfig
|
public interface ITorchConfig
|
||||||
{
|
{
|
||||||
|
bool Autostart { get; set; }
|
||||||
|
bool ForceUpdate { get; set; }
|
||||||
|
bool GetPluginUpdates { get; set; }
|
||||||
|
bool GetTorchUpdates { get; set; }
|
||||||
string InstanceName { get; set; }
|
string InstanceName { get; set; }
|
||||||
string InstancePath { get; set; }
|
string InstancePath { get; set; }
|
||||||
bool RedownloadPlugins { get; set; }
|
bool NoGui { get; set; }
|
||||||
bool AutomaticUpdates { get; set; }
|
bool NoUpdate { get; set; }
|
||||||
|
List<string> Plugins { get; set; }
|
||||||
bool RestartOnCrash { get; set; }
|
bool RestartOnCrash { get; set; }
|
||||||
|
bool ShouldUpdatePlugins { get; }
|
||||||
|
bool ShouldUpdateTorch { get; }
|
||||||
|
int TickTimeout { get; set; }
|
||||||
|
string WaitForPID { get; set; }
|
||||||
|
|
||||||
bool Save(string path = null);
|
bool Save(string path = null);
|
||||||
}
|
}
|
||||||
|
@@ -6,8 +6,14 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Torch.API.Managers
|
namespace Torch.API.Managers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base interface for Torch managers.
|
||||||
|
/// </summary>
|
||||||
public interface IManager
|
public interface IManager
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the manager. Called after Torch is initialized.
|
||||||
|
/// </summary>
|
||||||
void Init();
|
void Init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,17 +6,56 @@ using VRage.Game.ModAPI;
|
|||||||
|
|
||||||
namespace Torch.API.Managers
|
namespace Torch.API.Managers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for received messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">Message data.</param>
|
||||||
|
/// <param name="sendToOthers">Flag to broadcast message to other players.</param>
|
||||||
public delegate void MessageReceivedDel(IChatMessage message, ref bool sendToOthers);
|
public delegate void MessageReceivedDel(IChatMessage message, ref bool sendToOthers);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// API for multiplayer related functions.
|
||||||
|
/// </summary>
|
||||||
public interface IMultiplayerManager : IManager
|
public interface IMultiplayerManager : IManager
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a player joins.
|
||||||
|
/// </summary>
|
||||||
event Action<IPlayer> PlayerJoined;
|
event Action<IPlayer> PlayerJoined;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a player disconnects.
|
||||||
|
/// </summary>
|
||||||
event Action<IPlayer> PlayerLeft;
|
event Action<IPlayer> PlayerLeft;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a chat message is received.
|
||||||
|
/// </summary>
|
||||||
event MessageReceivedDel MessageReceived;
|
event MessageReceivedDel MessageReceived;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a chat message to all or one specific player.
|
||||||
|
/// </summary>
|
||||||
void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Blue);
|
void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Blue);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kicks the player from the game.
|
||||||
|
/// </summary>
|
||||||
void KickPlayer(ulong steamId);
|
void KickPlayer(ulong steamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bans or unbans a player from the game.
|
||||||
|
/// </summary>
|
||||||
void BanPlayer(ulong steamId, bool banned = true);
|
void BanPlayer(ulong steamId, bool banned = true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a player by their Steam64 ID or returns null if the player isn't found.
|
||||||
|
/// </summary>
|
||||||
IMyPlayer GetPlayerBySteamId(ulong id);
|
IMyPlayer GetPlayerBySteamId(ulong id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a player by their display name or returns null if the player isn't found.
|
||||||
|
/// </summary>
|
||||||
IMyPlayer GetPlayerByName(string name);
|
IMyPlayer GetPlayerByName(string name);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -9,14 +9,30 @@ using VRage.Network;
|
|||||||
|
|
||||||
namespace Torch.API.Managers
|
namespace Torch.API.Managers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// API for the network intercept.
|
||||||
|
/// </summary>
|
||||||
public interface INetworkManager : IManager
|
public interface INetworkManager : IManager
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Register a network handler.
|
||||||
|
/// </summary>
|
||||||
void RegisterNetworkHandler(INetworkHandler handler);
|
void RegisterNetworkHandler(INetworkHandler handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handler for multiplayer network messages.
|
||||||
|
/// </summary>
|
||||||
public interface INetworkHandler
|
public interface INetworkHandler
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns if the handler can process the call site.
|
||||||
|
/// </summary>
|
||||||
bool CanHandle(CallSite callSite);
|
bool CanHandle(CallSite callSite);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes a network message.
|
||||||
|
/// </summary>
|
||||||
bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
|
bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,34 @@ using VRage.Plugins;
|
|||||||
|
|
||||||
namespace Torch.API.Managers
|
namespace Torch.API.Managers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// API for the Torch plugin manager.
|
||||||
|
/// </summary>
|
||||||
public interface IPluginManager : IManager, IEnumerable<ITorchPlugin>
|
public interface IPluginManager : IManager, IEnumerable<ITorchPlugin>
|
||||||
{
|
{
|
||||||
event Action<List<ITorchPlugin>> PluginsLoaded;
|
/// <summary>
|
||||||
List<ITorchPlugin> Plugins { get; }
|
/// Fired when plugins are loaded.
|
||||||
|
/// </summary>
|
||||||
|
event Action<IList<ITorchPlugin>> PluginsLoaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Collection of loaded plugins.
|
||||||
|
/// </summary>
|
||||||
|
IList<ITorchPlugin> Plugins { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates all loaded plugins.
|
||||||
|
/// </summary>
|
||||||
void UpdatePlugins();
|
void UpdatePlugins();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes all loaded plugins.
|
||||||
|
/// </summary>
|
||||||
void DisposePlugins();
|
void DisposePlugins();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load plugins.
|
||||||
|
/// </summary>
|
||||||
|
void LoadPlugins();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -6,11 +6,29 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Torch.API
|
namespace Torch.API
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to indicate the state of the dedicated server.
|
||||||
|
/// </summary>
|
||||||
public enum ServerState
|
public enum ServerState
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The server is not running.
|
||||||
|
/// </summary>
|
||||||
Stopped,
|
Stopped,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The server is starting/loading the session.
|
||||||
|
/// </summary>
|
||||||
Starting,
|
Starting,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The server is running.
|
||||||
|
/// </summary>
|
||||||
Running,
|
Running,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The server encountered an error.
|
||||||
|
/// </summary>
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -158,12 +158,12 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="ConnectionState.cs" />
|
<Compile Include="ConnectionState.cs" />
|
||||||
<Compile Include="IChatMessage.cs" />
|
<Compile Include="IChatMessage.cs" />
|
||||||
|
<Compile Include="ITorchConfig.cs" />
|
||||||
<Compile Include="Managers\IManager.cs" />
|
<Compile Include="Managers\IManager.cs" />
|
||||||
<Compile Include="Managers\IMultiplayerManager.cs" />
|
<Compile Include="Managers\IMultiplayerManager.cs" />
|
||||||
<Compile Include="IPlayer.cs" />
|
<Compile Include="IPlayer.cs" />
|
||||||
<Compile Include="Managers\INetworkManager.cs" />
|
<Compile Include="Managers\INetworkManager.cs" />
|
||||||
<Compile Include="Managers\IPluginManager.cs" />
|
<Compile Include="Managers\IPluginManager.cs" />
|
||||||
<Compile Include="ITorchConfig.cs" />
|
|
||||||
<Compile Include="Plugins\ITorchPlugin.cs" />
|
<Compile Include="Plugins\ITorchPlugin.cs" />
|
||||||
<Compile Include="IServerControls.cs" />
|
<Compile Include="IServerControls.cs" />
|
||||||
<Compile Include="ITorchBase.cs" />
|
<Compile Include="ITorchBase.cs" />
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("1.0.169.376")]
|
[assembly: AssemblyVersion("1.0.207.7")]
|
||||||
[assembly: AssemblyFileVersion("1.0.169.376")]
|
[assembly: AssemblyFileVersion("1.0.207.7")]
|
@@ -45,6 +45,10 @@
|
|||||||
<HintPath>..\packages\NLog.4.4.1\lib\net45\NLog.dll</HintPath>
|
<HintPath>..\packages\NLog.4.4.1\lib\net45\NLog.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
|
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Sandbox.Game">
|
<Reference Include="Sandbox.Game">
|
||||||
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
|
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
@@ -9,6 +9,7 @@ using Sandbox;
|
|||||||
using Sandbox.Engine.Platform;
|
using Sandbox.Engine.Platform;
|
||||||
using Sandbox.Engine.Utils;
|
using Sandbox.Engine.Utils;
|
||||||
using Sandbox.Game;
|
using Sandbox.Game;
|
||||||
|
using Sandbox.ModAPI;
|
||||||
using SpaceEngineers.Game;
|
using SpaceEngineers.Game;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using VRage.FileSystem;
|
using VRage.FileSystem;
|
||||||
|
@@ -1,62 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Sandbox.Engine.Utils;
|
|
||||||
using Torch.API;
|
|
||||||
using Torch.API.Managers;
|
|
||||||
using Torch.Managers;
|
|
||||||
using Torch.Server.ViewModels;
|
|
||||||
using VRage.Game;
|
|
||||||
|
|
||||||
namespace Torch.Server.Managers
|
|
||||||
{
|
|
||||||
//TODO
|
|
||||||
public class ConfigManager : Manager
|
|
||||||
{
|
|
||||||
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
|
|
||||||
public ConfigDedicatedViewModel DedicatedConfig { get; set; }
|
|
||||||
public TorchConfig TorchConfig { get; set; }
|
|
||||||
|
|
||||||
public ConfigManager(ITorchBase torchInstance) : base(torchInstance)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Init()
|
|
||||||
{
|
|
||||||
LoadInstance(Torch.Config.InstancePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadInstance(string path)
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(path))
|
|
||||||
throw new FileNotFoundException($"Instance directory not found at '{path}'");
|
|
||||||
|
|
||||||
var configPath = Path.Combine(path, CONFIG_NAME);
|
|
||||||
var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
|
|
||||||
config.Load();
|
|
||||||
DedicatedConfig = new ConfigDedicatedViewModel(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a skeleton of a DS instance folder at the given directory.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path"></param>
|
|
||||||
public void CreateInstance(string path)
|
|
||||||
{
|
|
||||||
if (Directory.Exists(path))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Directory.CreateDirectory(path);
|
|
||||||
var savesPath = Path.Combine(path, "Saves");
|
|
||||||
Directory.CreateDirectory(savesPath);
|
|
||||||
var modsPath = Path.Combine(path, "Mods");
|
|
||||||
Directory.CreateDirectory(modsPath);
|
|
||||||
LoadInstance(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
158
Torch.Server/Managers/InstanceManager.cs
Normal file
158
Torch.Server/Managers/InstanceManager.cs
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Havok;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
|
using Sandbox.Engine.Utils;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers;
|
||||||
|
using Torch.Server.ViewModels;
|
||||||
|
using VRage.FileSystem;
|
||||||
|
using VRage.Game;
|
||||||
|
using VRage.ObjectBuilders;
|
||||||
|
|
||||||
|
namespace Torch.Server.Managers
|
||||||
|
{
|
||||||
|
public class InstanceManager : Manager
|
||||||
|
{
|
||||||
|
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
|
||||||
|
public ConfigDedicatedViewModel DedicatedConfig { get; set; }
|
||||||
|
private static readonly Logger Log = LogManager.GetLogger(nameof(InstanceManager));
|
||||||
|
|
||||||
|
public InstanceManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
LoadInstance(Torch.Config.InstancePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadInstance(string path, bool validate = true)
|
||||||
|
{
|
||||||
|
if (validate)
|
||||||
|
ValidateInstance(path);
|
||||||
|
|
||||||
|
MyFileSystem.Reset();
|
||||||
|
MyFileSystem.ExePath = Path.Combine(Torch.GetManager<FilesystemManager>().TorchDirectory, "DedicatedServer64");
|
||||||
|
MyFileSystem.Init("Content", path);
|
||||||
|
|
||||||
|
var configPath = Path.Combine(path, CONFIG_NAME);
|
||||||
|
if (!File.Exists(configPath))
|
||||||
|
{
|
||||||
|
Log.Error($"Failed to load dedicated config at {path}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
|
||||||
|
config.Load(configPath);
|
||||||
|
|
||||||
|
DedicatedConfig = new ConfigDedicatedViewModel(config);
|
||||||
|
var worldFolders = Directory.EnumerateDirectories(Path.Combine(Torch.Config.InstancePath, "Saves"));
|
||||||
|
|
||||||
|
foreach (var f in worldFolders)
|
||||||
|
DedicatedConfig.WorldPaths.Add(f);
|
||||||
|
|
||||||
|
if (DedicatedConfig.WorldPaths.Count == 0)
|
||||||
|
{
|
||||||
|
Log.Warn($"No worlds found in the current instance {path}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (string.IsNullOrEmpty(DedicatedConfig.LoadWorld))
|
||||||
|
{
|
||||||
|
Log.Warn("No world specified, importing first available world.");
|
||||||
|
SelectWorld(DedicatedConfig.WorldPaths[0], false);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectWorld(string worldPath, bool modsOnly = true)
|
||||||
|
{
|
||||||
|
DedicatedConfig.LoadWorld = worldPath;
|
||||||
|
LoadWorldMods(modsOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void LoadWorldMods(bool modsOnly = true)
|
||||||
|
{
|
||||||
|
if (DedicatedConfig.LoadWorld == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var sandboxPath = Path.Combine(DedicatedConfig.LoadWorld, "Sandbox.sbc");
|
||||||
|
|
||||||
|
if (!File.Exists(sandboxPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
||||||
|
if (checkpoint == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var mod in checkpoint.Mods)
|
||||||
|
sb.AppendLine(mod.PublishedFileId.ToString());
|
||||||
|
|
||||||
|
DedicatedConfig.Mods = sb.ToString();
|
||||||
|
|
||||||
|
Log.Info("Loaded mod list from world");
|
||||||
|
|
||||||
|
if (!modsOnly)
|
||||||
|
DedicatedConfig.SessionSettings = new SessionSettingsViewModel(checkpoint.Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveConfig()
|
||||||
|
{
|
||||||
|
DedicatedConfig.Model.Save();
|
||||||
|
Log.Info("Saved dedicated config.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MyObjectBuilderSerializer.DeserializeXML(Path.Combine(DedicatedConfig.LoadWorld, "Sandbox.sbc"), out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
||||||
|
if (checkpoint == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checkpoint.Settings = DedicatedConfig.SessionSettings;
|
||||||
|
checkpoint.Mods.Clear();
|
||||||
|
foreach (var modId in DedicatedConfig.Model.Mods)
|
||||||
|
checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId));
|
||||||
|
|
||||||
|
MyLocalCache.SaveCheckpoint(checkpoint, DedicatedConfig.LoadWorld);
|
||||||
|
Log.Info("Saved world config.");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to write sandbox config, changes will not appear on server");
|
||||||
|
Log.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that the given path is a valid server instance.
|
||||||
|
/// </summary>
|
||||||
|
private void ValidateInstance(string path)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path.Combine(path, "Saves"));
|
||||||
|
Directory.CreateDirectory(Path.Combine(path, "Mods"));
|
||||||
|
var configPath = Path.Combine(path, CONFIG_NAME);
|
||||||
|
if (File.Exists(configPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
|
||||||
|
config.Save(configPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -23,6 +23,7 @@ using Torch.Server.Views;
|
|||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Security.Policy;
|
||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
using VRage.FileSystem;
|
using VRage.FileSystem;
|
||||||
using VRageRender;
|
using VRageRender;
|
||||||
@@ -34,8 +35,8 @@ namespace Torch.Server
|
|||||||
private static ITorchServer _server;
|
private static ITorchServer _server;
|
||||||
private static Logger _log = LogManager.GetLogger("Torch");
|
private static Logger _log = LogManager.GetLogger("Torch");
|
||||||
private static bool _restartOnCrash;
|
private static bool _restartOnCrash;
|
||||||
public static bool IsManualInstall;
|
private static TorchConfig _config;
|
||||||
private static TorchCli _cli;
|
private static bool _steamCmdDone;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method must *NOT* load any types/assemblies from the vanilla game, otherwise automatic updates will fail.
|
/// This method must *NOT* load any types/assemblies from the vanilla game, otherwise automatic updates will fail.
|
||||||
@@ -46,10 +47,10 @@ namespace Torch.Server
|
|||||||
//Ensures that all the files are downloaded in the Torch directory.
|
//Ensures that all the files are downloaded in the Torch directory.
|
||||||
Directory.SetCurrentDirectory(new FileInfo(typeof(Program).Assembly.Location).Directory.ToString());
|
Directory.SetCurrentDirectory(new FileInfo(typeof(Program).Assembly.Location).Directory.ToString());
|
||||||
|
|
||||||
IsManualInstall = File.Exists("SpaceEngineersDedicated.exe");
|
foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.old"))
|
||||||
if (!IsManualInstall)
|
File.Delete(file);
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
|
||||||
|
|
||||||
|
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||||
|
|
||||||
if (!Environment.UserInteractive)
|
if (!Environment.UserInteractive)
|
||||||
@@ -61,55 +62,32 @@ namespace Torch.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var configName = "TorchConfig.xml";
|
//CommandLine reflection triggers assembly loading, so DS update must be completely separated.
|
||||||
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
|
if (!args.Contains("-noupdate"))
|
||||||
TorchConfig options;
|
|
||||||
if (File.Exists(configName))
|
|
||||||
{
|
{
|
||||||
_log.Info($"Loading config {configPath}");
|
if (!Directory.Exists("DedicatedServer64"))
|
||||||
options = TorchConfig.LoadFrom(configPath);
|
{
|
||||||
|
_log.Error("Game libraries not found. Press the Enter key to install the dedicated server.");
|
||||||
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_log.Info($"Generating default config at {configPath}");
|
|
||||||
options = new TorchConfig();
|
|
||||||
|
|
||||||
if (!IsManualInstall)
|
|
||||||
{
|
|
||||||
//new ConfigManager().CreateInstance("Instance");
|
|
||||||
options.InstancePath = Path.GetFullPath("Instance");
|
|
||||||
|
|
||||||
_log.Warn("Would you like to enable automatic updates? (Y/n):");
|
|
||||||
|
|
||||||
var input = Console.ReadLine() ?? "";
|
|
||||||
var autoUpdate = !input.Equals("n", StringComparison.InvariantCultureIgnoreCase);
|
|
||||||
options.AutomaticUpdates = autoUpdate;
|
|
||||||
if (autoUpdate)
|
|
||||||
{
|
|
||||||
_log.Info("Automatic updates enabled, updating server.");
|
|
||||||
RunSteamCmd();
|
RunSteamCmd();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//var setupDialog = new FirstTimeSetup { DataContext = options };
|
InitConfig();
|
||||||
//setupDialog.ShowDialog();
|
|
||||||
options.Save(configPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
_cli = new TorchCli { Config = options };
|
if (!_config.Parse(args))
|
||||||
if (!_cli.Parse(args))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_log.Debug(_cli.ToString());
|
if (!string.IsNullOrEmpty(_config.WaitForPID))
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_cli.WaitForPID))
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pid = int.Parse(_cli.WaitForPID);
|
var pid = int.Parse(_config.WaitForPID);
|
||||||
var waitProc = Process.GetProcessById(pid);
|
var waitProc = Process.GetProcessById(pid);
|
||||||
_log.Warn($"Waiting for process {pid} to exit.");
|
_log.Warn($"Waiting for process {pid} to exit.");
|
||||||
waitProc.WaitForExit();
|
waitProc.WaitForExit();
|
||||||
|
_log.Info("Continuing in 5 seconds.");
|
||||||
|
Thread.Sleep(5000);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -117,19 +95,26 @@ namespace Torch.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_restartOnCrash = _cli.RestartOnCrash;
|
_restartOnCrash = _config.RestartOnCrash;
|
||||||
|
RunServer(_config);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.AutomaticUpdates || _cli.Update)
|
public static void InitConfig()
|
||||||
{
|
{
|
||||||
if (IsManualInstall)
|
var configName = "Torch.cfg";
|
||||||
_log.Warn("Detected manual install, won't attempt to update DS");
|
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
|
||||||
|
if (File.Exists(configName))
|
||||||
|
{
|
||||||
|
_log.Info($"Loading config {configPath}");
|
||||||
|
_config = TorchConfig.LoadFrom(configPath);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RunSteamCmd();
|
_log.Info($"Generating default config at {configPath}");
|
||||||
|
_config = new TorchConfig { InstancePath = Path.GetFullPath("Instance") };
|
||||||
|
_config.Save(configPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RunServer(options, _cli);
|
|
||||||
}
|
|
||||||
|
|
||||||
private const string STEAMCMD_DIR = "steamcmd";
|
private const string STEAMCMD_DIR = "steamcmd";
|
||||||
private const string STEAMCMD_ZIP = "temp.zip";
|
private const string STEAMCMD_ZIP = "temp.zip";
|
||||||
@@ -142,6 +127,9 @@ quit";
|
|||||||
|
|
||||||
public static void RunSteamCmd()
|
public static void RunSteamCmd()
|
||||||
{
|
{
|
||||||
|
if (_steamCmdDone)
|
||||||
|
return;
|
||||||
|
|
||||||
var log = LogManager.GetLogger("SteamCMD");
|
var log = LogManager.GetLogger("SteamCMD");
|
||||||
|
|
||||||
if (!Directory.Exists(STEAMCMD_DIR))
|
if (!Directory.Exists(STEAMCMD_DIR))
|
||||||
@@ -187,38 +175,40 @@ quit";
|
|||||||
log.Info(cmd.StandardOutput.ReadLine());
|
log.Info(cmd.StandardOutput.ReadLine());
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_steamCmdDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RunServer(TorchConfig options, TorchCli cli)
|
public static void RunServer(TorchConfig config)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (!parser.ParseArguments(args, options))
|
if (!parser.ParseArguments(args, config))
|
||||||
{
|
{
|
||||||
_log.Error($"Parsing arguments failed: {string.Join(" ", args)}");
|
_log.Error($"Parsing arguments failed: {string.Join(" ", args)}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(options.Config) && File.Exists(options.Config))
|
if (!string.IsNullOrEmpty(config.Config) && File.Exists(config.Config))
|
||||||
{
|
{
|
||||||
options = ServerConfig.LoadFrom(options.Config);
|
config = ServerConfig.LoadFrom(config.Config);
|
||||||
parser.ParseArguments(args, options);
|
parser.ParseArguments(args, config);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
//RestartOnCrash autostart autosave=15
|
//RestartOnCrash autostart autosave=15
|
||||||
//gamepath ="C:\Program Files\Space Engineers DS" instance="Hydro Survival" instancepath="C:\ProgramData\SpaceEngineersDedicated\Hydro Survival"
|
//gamepath ="C:\Program Files\Space Engineers DS" instance="Hydro Survival" instancepath="C:\ProgramData\SpaceEngineersDedicated\Hydro Survival"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (options.InstallService)
|
if (config.InstallService)
|
||||||
{
|
{
|
||||||
var serviceName = $"\"Torch - {options.InstanceName}\"";
|
var serviceName = $"\"Torch - {config.InstanceName}\"";
|
||||||
// Working on installing the service properly instead of with sc.exe
|
// Working on installing the service properly instead of with sc.exe
|
||||||
_log.Info($"Installing service '{serviceName}");
|
_log.Info($"Installing service '{serviceName}");
|
||||||
var exePath = $"\"{Assembly.GetExecutingAssembly().Location}\"";
|
var exePath = $"\"{Assembly.GetExecutingAssembly().Location}\"";
|
||||||
var createInfo = new ServiceCreateInfo
|
var createInfo = new ServiceCreateInfo
|
||||||
{
|
{
|
||||||
Name = options.InstanceName,
|
Name = config.InstanceName,
|
||||||
BinaryPath = exePath,
|
BinaryPath = exePath,
|
||||||
};
|
};
|
||||||
_log.Info("Service Installed");
|
_log.Info("Service Installed");
|
||||||
@@ -238,7 +228,7 @@ quit";
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.UninstallService)
|
if (config.UninstallService)
|
||||||
{
|
{
|
||||||
_log.Info("Uninstalling Torch service");
|
_log.Info("Uninstalling Torch service");
|
||||||
var startInfo = new ProcessStartInfo
|
var startInfo = new ProcessStartInfo
|
||||||
@@ -254,18 +244,17 @@ quit";
|
|||||||
return;
|
return;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
_server = new TorchServer(options);
|
_server = new TorchServer(config);
|
||||||
_server.Init();
|
|
||||||
|
|
||||||
if (cli.NoGui || cli.Autostart)
|
_server.Init();
|
||||||
|
if (config.NoGui || config.Autostart)
|
||||||
{
|
{
|
||||||
new Thread(() => _server.Start()).Start();
|
new Thread(() => _server.Start()).Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cli.NoGui)
|
if (!config.NoGui)
|
||||||
{
|
{
|
||||||
var ui = new TorchUI((TorchServer)_server);
|
var ui = new TorchUI((TorchServer)_server);
|
||||||
ui.LoadConfig(options);
|
|
||||||
ui.ShowDialog();
|
ui.ShowDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,19 +280,13 @@ quit";
|
|||||||
{
|
{
|
||||||
var ex = (Exception)e.ExceptionObject;
|
var ex = (Exception)e.ExceptionObject;
|
||||||
_log.Fatal(ex);
|
_log.Fatal(ex);
|
||||||
|
Console.WriteLine("Exiting in 5 seconds.");
|
||||||
|
Thread.Sleep(5000);
|
||||||
if (_restartOnCrash)
|
if (_restartOnCrash)
|
||||||
{
|
{
|
||||||
/* Throws an exception somehow and I'm too lazy to debug it.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (MySession.Static != null && MySession.Static.AutoSaveInMinutes > 0)
|
|
||||||
MySession.Static.Save();
|
|
||||||
}
|
|
||||||
catch { }*/
|
|
||||||
|
|
||||||
var exe = typeof(Program).Assembly.Location;
|
var exe = typeof(Program).Assembly.Location;
|
||||||
_cli.WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
_config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
||||||
Process.Start(exe, _cli.ToString());
|
Process.Start(exe, _config.ToString());
|
||||||
}
|
}
|
||||||
//1627 = Function failed during execution.
|
//1627 = Function failed during execution.
|
||||||
Environment.Exit(1627);
|
Environment.Exit(1627);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("1.0.169.376")]
|
[assembly: AssemblyVersion("1.1.207.7")]
|
||||||
[assembly: AssemblyFileVersion("1.0.169.376")]
|
[assembly: AssemblyFileVersion("1.1.207.7")]
|
@@ -8,7 +8,7 @@ using System.Reflection;
|
|||||||
|
|
||||||
<# var dt = DateTime.Now;
|
<# var dt = DateTime.Now;
|
||||||
int major = 1;
|
int major = 1;
|
||||||
int minor = 0;
|
int minor = 1;
|
||||||
int build = dt.DayOfYear;
|
int build = dt.DayOfYear;
|
||||||
int rev = (int)dt.TimeOfDay.TotalMinutes / 2;
|
int rev = (int)dt.TimeOfDay.TotalMinutes / 2;
|
||||||
#>
|
#>
|
||||||
|
@@ -59,9 +59,11 @@
|
|||||||
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
|
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\NLog.4.4.1\lib\net45\NLog.dll</HintPath>
|
<HintPath>..\packages\NLog.4.4.11\lib\net45\NLog.dll</HintPath>
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
@@ -183,8 +185,7 @@
|
|||||||
<Reference Include="PresentationFramework" />
|
<Reference Include="PresentationFramework" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Managers\ConfigManager.cs" />
|
<Compile Include="Managers\InstanceManager.cs" />
|
||||||
<Compile Include="TorchCli.cs" />
|
|
||||||
<Compile Include="NativeMethods.cs" />
|
<Compile Include="NativeMethods.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs">
|
<Compile Include="Properties\AssemblyInfo.cs">
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
@@ -365,7 +366,9 @@
|
|||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"</PostBuildEvent>
|
<PostBuildEvent>cd "$(TargetDir)"
|
||||||
|
copy "$(SolutionDir)NLog.config" "$(TargetDir)"
|
||||||
|
"Torch Server Release.bat"</PostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
@@ -1,41 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Torch.Server
|
|
||||||
{
|
|
||||||
public class TorchCli : CommandLine
|
|
||||||
{
|
|
||||||
public TorchConfig Config { get; set; }
|
|
||||||
|
|
||||||
[Arg("instancepath", "Server data folder where saves and mods are stored.")]
|
|
||||||
public string InstancePath { get => Config.InstancePath; set => Config.InstancePath = value; }
|
|
||||||
|
|
||||||
[Arg("noupdate", "Disable automatically downloading game and plugin updates.")]
|
|
||||||
public bool NoUpdate { get => !Config.AutomaticUpdates; set => Config.AutomaticUpdates = !value; }
|
|
||||||
|
|
||||||
[Arg("update", "Manually check for and install updates.")]
|
|
||||||
public bool Update { get; set; }
|
|
||||||
|
|
||||||
//TODO: backend code for this
|
|
||||||
//[Arg("worldpath", "Path to the game world folder to load.")]
|
|
||||||
public string WorldPath { get; set; }
|
|
||||||
|
|
||||||
[Arg("autostart", "Start the server immediately.")]
|
|
||||||
public bool Autostart { get; set; }
|
|
||||||
|
|
||||||
[Arg("restartoncrash", "Automatically restart the server if it crashes.")]
|
|
||||||
public bool RestartOnCrash { get => Config.RestartOnCrash; set => Config.RestartOnCrash = value; }
|
|
||||||
|
|
||||||
[Arg("nogui", "Do not show the Torch UI.")]
|
|
||||||
public bool NoGui { get; set; }
|
|
||||||
|
|
||||||
[Arg("silent", "Do not show the Torch UI or the command line.")]
|
|
||||||
public bool Silent { get; set; }
|
|
||||||
|
|
||||||
[Arg("waitforpid", "Makes Torch wait for another process to exit.")]
|
|
||||||
public string WaitForPID { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,53 +3,87 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
|
||||||
namespace Torch.Server
|
namespace Torch.Server
|
||||||
{
|
{
|
||||||
public class TorchConfig : ITorchConfig
|
public class TorchConfig : CommandLine, ITorchConfig
|
||||||
{
|
{
|
||||||
private static Logger _log = LogManager.GetLogger("Config");
|
private static Logger _log = LogManager.GetLogger("Config");
|
||||||
|
|
||||||
public string InstancePath { get; set; }
|
public bool ShouldUpdatePlugins => (GetPluginUpdates && !NoUpdate) || ForceUpdate;
|
||||||
|
public bool ShouldUpdateTorch => (GetTorchUpdates && !NoUpdate) || ForceUpdate;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[Arg("instancename", "The name of the Torch instance.")]
|
||||||
public string InstanceName { get; set; }
|
public string InstanceName { get; set; }
|
||||||
#warning World Path not implemented
|
|
||||||
public string WorldPath { get; set; }
|
/// <inheritdoc />
|
||||||
//public int Autosave { get; set; }
|
[Arg("instancepath", "Server data folder where saves and mods are stored.")]
|
||||||
//public bool AutoRestart { get; set; }
|
public string InstancePath { get; set; }
|
||||||
//public bool LogChat { get; set; }
|
|
||||||
public bool AutomaticUpdates { get; set; } = true;
|
/// <inheritdoc />
|
||||||
public bool RedownloadPlugins { get; set; }
|
[XmlIgnore, Arg("noupdate", "Disable automatically downloading game and plugin updates.")]
|
||||||
|
public bool NoUpdate { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[XmlIgnore, Arg("forceupdate", "Manually check for and install updates.")]
|
||||||
|
public bool ForceUpdate { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[Arg("autostart", "Start the server immediately.")]
|
||||||
|
public bool Autostart { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[Arg("restartoncrash", "Automatically restart the server if it crashes.")]
|
||||||
public bool RestartOnCrash { get; set; }
|
public bool RestartOnCrash { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[Arg("nogui", "Do not show the Torch UI.")]
|
||||||
|
public bool NoGui { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[XmlIgnore, Arg("waitforpid", "Makes Torch wait for another process to exit.")]
|
||||||
|
public string WaitForPID { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool GetTorchUpdates { get; set; } = true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool GetPluginUpdates { get; set; } = true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int TickTimeout { get; set; } = 60;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public List<string> Plugins { get; set; } = new List<string>();
|
public List<string> Plugins { get; set; } = new List<string>();
|
||||||
public Point WindowSize { get; set; } = new Point(800, 600);
|
|
||||||
public Point WindowPosition { get; set; } = new Point();
|
internal Point WindowSize { get; set; } = new Point(800, 600);
|
||||||
[NonSerialized]
|
internal Point WindowPosition { get; set; } = new Point();
|
||||||
|
[XmlIgnore]
|
||||||
private string _path;
|
private string _path;
|
||||||
|
|
||||||
public TorchConfig() : this("Torch") { }
|
public TorchConfig() : this("Torch") { }
|
||||||
|
|
||||||
public TorchConfig(string instanceName = "Torch", string instancePath = null, int autosaveInterval = 5, bool autoRestart = false)
|
public TorchConfig(string instanceName = "Torch", string instancePath = null)
|
||||||
{
|
{
|
||||||
InstanceName = instanceName;
|
InstanceName = instanceName;
|
||||||
InstancePath = instancePath ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SpaceEngineersDedicated");
|
InstancePath = instancePath ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SpaceEngineersDedicated");
|
||||||
//Autosave = autosaveInterval;
|
|
||||||
//AutoRestart = autoRestart;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TorchConfig LoadFrom(string path)
|
public static TorchConfig LoadFrom(string path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var serializer = new XmlSerializer(typeof(TorchConfig));
|
var ser = new XmlSerializer(typeof(TorchConfig));
|
||||||
TorchConfig config;
|
|
||||||
using (var f = File.OpenRead(path))
|
using (var f = File.OpenRead(path))
|
||||||
{
|
{
|
||||||
config = (TorchConfig)serializer.Deserialize(f);
|
var config = (TorchConfig)ser.Deserialize(f);
|
||||||
}
|
|
||||||
config._path = path;
|
config._path = path;
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_log.Error(e);
|
_log.Error(e);
|
||||||
@@ -66,11 +100,9 @@ namespace Torch.Server
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var serializer = new XmlSerializer(typeof(TorchConfig));
|
var ser = new XmlSerializer(typeof(TorchConfig));
|
||||||
using (var f = File.Create(path))
|
using (var f = File.Create(path))
|
||||||
{
|
ser.Serialize(f, this);
|
||||||
serializer.Serialize(f, this);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@@ -10,14 +10,19 @@ using System.Reflection;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Xml.Serialization.GeneratedAssembly;
|
using Microsoft.Xml.Serialization.GeneratedAssembly;
|
||||||
|
using Sandbox.Engine.Analytics;
|
||||||
using Sandbox.Game.Multiplayer;
|
using Sandbox.Game.Multiplayer;
|
||||||
using Sandbox.ModAPI;
|
using Sandbox.ModAPI;
|
||||||
using SteamSDK;
|
using SteamSDK;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using Torch.Managers;
|
||||||
|
using Torch.Server.Managers;
|
||||||
using VRage.Dedicated;
|
using VRage.Dedicated;
|
||||||
using VRage.FileSystem;
|
using VRage.FileSystem;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
using VRage.Game.ObjectBuilder;
|
using VRage.Game.ObjectBuilder;
|
||||||
using VRage.Game.SessionComponents;
|
using VRage.Game.SessionComponents;
|
||||||
using VRage.Library;
|
using VRage.Library;
|
||||||
@@ -35,27 +40,35 @@ namespace Torch.Server
|
|||||||
public TimeSpan ElapsedPlayTime { get => _elapsedPlayTime; set { _elapsedPlayTime = value; OnPropertyChanged(); } }
|
public TimeSpan ElapsedPlayTime { get => _elapsedPlayTime; set { _elapsedPlayTime = value; OnPropertyChanged(); } }
|
||||||
public Thread GameThread { get; private set; }
|
public Thread GameThread { get; private set; }
|
||||||
public ServerState State { get => _state; private set { _state = value; OnPropertyChanged(); } }
|
public ServerState State { get => _state; private set { _state = value; OnPropertyChanged(); } }
|
||||||
|
public bool IsRunning { get => _isRunning; set { _isRunning = value; OnPropertyChanged(); } }
|
||||||
|
public InstanceManager DedicatedInstance { get; }
|
||||||
|
/// <inheritdoc />
|
||||||
public string InstanceName => Config?.InstanceName;
|
public string InstanceName => Config?.InstanceName;
|
||||||
|
/// <inheritdoc />
|
||||||
public string InstancePath => Config?.InstancePath;
|
public string InstancePath => Config?.InstancePath;
|
||||||
|
|
||||||
|
private bool _isRunning;
|
||||||
private ServerState _state;
|
private ServerState _state;
|
||||||
private TimeSpan _elapsedPlayTime;
|
private TimeSpan _elapsedPlayTime;
|
||||||
private float _simRatio;
|
private float _simRatio;
|
||||||
private readonly AutoResetEvent _stopHandle = new AutoResetEvent(false);
|
private readonly AutoResetEvent _stopHandle = new AutoResetEvent(false);
|
||||||
private Timer _watchdog;
|
private Timer _watchdog;
|
||||||
|
private Stopwatch _uptime;
|
||||||
|
|
||||||
public TorchServer(TorchConfig config = null)
|
public TorchServer(TorchConfig config = null)
|
||||||
{
|
{
|
||||||
|
DedicatedInstance = new InstanceManager(this);
|
||||||
|
AddManager(DedicatedInstance);
|
||||||
Config = config ?? new TorchConfig();
|
Config = config ?? new TorchConfig();
|
||||||
|
MyFakes.ENABLE_INFINARIO = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public override void Init()
|
public override void Init()
|
||||||
{
|
{
|
||||||
|
Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'");
|
||||||
base.Init();
|
base.Init();
|
||||||
|
|
||||||
Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'");
|
|
||||||
|
|
||||||
MyFakes.ENABLE_INFINARIO = false;
|
|
||||||
MyPerGameSettings.SendLogToKeen = false;
|
MyPerGameSettings.SendLogToKeen = false;
|
||||||
MyPerServerSettings.GameName = MyPerGameSettings.GameName;
|
MyPerServerSettings.GameName = MyPerGameSettings.GameName;
|
||||||
MyPerServerSettings.GameNameSafe = MyPerGameSettings.GameNameSafe;
|
MyPerServerSettings.GameNameSafe = MyPerGameSettings.GameNameSafe;
|
||||||
@@ -66,7 +79,6 @@ namespace Torch.Server
|
|||||||
MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion;
|
MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion;
|
||||||
|
|
||||||
MyObjectBuilderSerializer.RegisterFromAssembly(typeof(MyObjectBuilder_CheckpointSerializer).Assembly);
|
MyObjectBuilderSerializer.RegisterFromAssembly(typeof(MyObjectBuilder_CheckpointSerializer).Assembly);
|
||||||
|
|
||||||
InvokeBeforeRun();
|
InvokeBeforeRun();
|
||||||
|
|
||||||
MyPlugins.RegisterGameAssemblyFile(MyPerGameSettings.GameModAssembly);
|
MyPlugins.RegisterGameAssemblyFile(MyPerGameSettings.GameModAssembly);
|
||||||
@@ -75,32 +87,12 @@ namespace Torch.Server
|
|||||||
MyPlugins.RegisterSandboxGameAssemblyFile(MyPerGameSettings.SandboxGameAssembly);
|
MyPlugins.RegisterSandboxGameAssemblyFile(MyPerGameSettings.SandboxGameAssembly);
|
||||||
MyPlugins.Load();
|
MyPlugins.Load();
|
||||||
MyGlobalTypeMetadata.Static.Init();
|
MyGlobalTypeMetadata.Static.Init();
|
||||||
RuntimeHelpers.RunClassConstructor(typeof(MyObjectBuilder_Base).TypeHandle);
|
|
||||||
|
Plugins.LoadPlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvokeBeforeRun()
|
private void InvokeBeforeRun()
|
||||||
{
|
{
|
||||||
|
|
||||||
var contentPath = "Content";
|
|
||||||
|
|
||||||
var privateContentPath = typeof(MyFileSystem).GetField("m_contentPath", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as string;
|
|
||||||
if (privateContentPath != null)
|
|
||||||
Log.Debug("MyFileSystem already initialized");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (Program.IsManualInstall)
|
|
||||||
{
|
|
||||||
var rootPath = new FileInfo(MyFileSystem.ExePath).Directory.FullName;
|
|
||||||
contentPath = Path.Combine(rootPath, "Content");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MyFileSystem.ExePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DedicatedServer64");
|
|
||||||
}
|
|
||||||
|
|
||||||
MyFileSystem.Init(contentPath, InstancePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
MySandboxGame.Log.Init("SpaceEngineers-Dedicated.log", MyFinalBuildConstants.APP_VERSION_STRING);
|
MySandboxGame.Log.Init("SpaceEngineers-Dedicated.log", MyFinalBuildConstants.APP_VERSION_STRING);
|
||||||
MySandboxGame.Log.WriteLine("Steam build: Always true");
|
MySandboxGame.Log.WriteLine("Steam build: Always true");
|
||||||
MySandboxGame.Log.WriteLine("Environment.ProcessorCount: " + MyEnvironment.ProcessorCount);
|
MySandboxGame.Log.WriteLine("Environment.ProcessorCount: " + MyEnvironment.ProcessorCount);
|
||||||
@@ -129,14 +121,15 @@ namespace Torch.Server
|
|||||||
MySandboxGame.Config.Load();
|
MySandboxGame.Config.Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Start server on the current thread.
|
|
||||||
/// </summary>
|
|
||||||
public override void Start()
|
public override void Start()
|
||||||
{
|
{
|
||||||
if (State != ServerState.Stopped)
|
if (State != ServerState.Stopped)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
DedicatedInstance.SaveConfig();
|
||||||
|
_uptime = Stopwatch.StartNew();
|
||||||
|
IsRunning = true;
|
||||||
GameThread = Thread.CurrentThread;
|
GameThread = Thread.CurrentThread;
|
||||||
Config.Save();
|
Config.Save();
|
||||||
State = ServerState.Starting;
|
State = ServerState.Starting;
|
||||||
@@ -169,12 +162,13 @@ namespace Torch.Server
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
SimulationRatio = Sync.ServerSimulationRatio;
|
SimulationRatio = Sync.ServerSimulationRatio;
|
||||||
ElapsedPlayTime = MySession.Static?.ElapsedPlayTime ?? default(TimeSpan);
|
var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds));
|
||||||
|
ElapsedPlayTime = elapsed;
|
||||||
|
|
||||||
if (_watchdog == null)
|
if (_watchdog == null && Config.TickTimeout > 0)
|
||||||
{
|
{
|
||||||
Log.Info("Starting server watchdog.");
|
Log.Info("Starting server watchdog.");
|
||||||
_watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero, TimeSpan.FromSeconds(30));
|
_watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero, TimeSpan.FromSeconds(Config.TickTimeout));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,20 +176,18 @@ namespace Torch.Server
|
|||||||
{
|
{
|
||||||
var mre = new ManualResetEvent(false);
|
var mre = new ManualResetEvent(false);
|
||||||
((TorchServer)state).Invoke(() => mre.Set());
|
((TorchServer)state).Invoke(() => mre.Set());
|
||||||
if (!mre.WaitOne(TimeSpan.FromSeconds(30)))
|
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
|
||||||
{
|
{
|
||||||
var mainThread = MySandboxGame.Static.UpdateThread;
|
var mainThread = MySandboxGame.Static.UpdateThread;
|
||||||
mainThread.Suspend();
|
mainThread.Suspend();
|
||||||
var stackTrace = new StackTrace(mainThread, true);
|
var stackTrace = new StackTrace(mainThread, true);
|
||||||
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least 30 seconds.\n{stackTrace}");
|
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.\n{stackTrace}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Debug("Server watchdog responded");
|
Log.Debug("Server watchdog responded");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Stop the server.
|
|
||||||
/// </summary>
|
|
||||||
public override void Stop()
|
public override void Stop()
|
||||||
{
|
{
|
||||||
if (State == ServerState.Stopped)
|
if (State == ServerState.Stopped)
|
||||||
@@ -217,6 +209,54 @@ namespace Torch.Server
|
|||||||
Log.Info("Server stopped.");
|
Log.Info("Server stopped.");
|
||||||
_stopHandle.Set();
|
_stopHandle.Set();
|
||||||
State = ServerState.Stopped;
|
State = ServerState.Stopped;
|
||||||
|
IsRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restart the program. DOES NOT SAVE!
|
||||||
|
/// </summary>
|
||||||
|
public override void Restart()
|
||||||
|
{
|
||||||
|
var exe = Assembly.GetExecutingAssembly().Location;
|
||||||
|
((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
||||||
|
Process.Start(exe, Config.ToString());
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override Task Save(long callerId)
|
||||||
|
{
|
||||||
|
return SaveGameAsync(statusCode => SaveCompleted(statusCode, callerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback for when save has finished.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="statusCode">Return code of the save operation</param>
|
||||||
|
/// <param name="callerId">Caller of the save operation</param>
|
||||||
|
private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0)
|
||||||
|
{
|
||||||
|
switch (statusCode)
|
||||||
|
{
|
||||||
|
case SaveGameStatus.Success:
|
||||||
|
Log.Info("Save completed.");
|
||||||
|
Multiplayer.SendMessage("Saved game.", playerId: callerId);
|
||||||
|
break;
|
||||||
|
case SaveGameStatus.SaveInProgress:
|
||||||
|
Log.Error("Save failed, a save is already in progress.");
|
||||||
|
Multiplayer.SendMessage("Save failed, a save is already in progress.", playerId: callerId, font: MyFontEnum.Red);
|
||||||
|
break;
|
||||||
|
case SaveGameStatus.GameNotReady:
|
||||||
|
Log.Error("Save failed, game was not ready.");
|
||||||
|
Multiplayer.SendMessage("Save failed, game was not ready.", playerId: callerId, font: MyFontEnum.Red);
|
||||||
|
break;
|
||||||
|
case SaveGameStatus.TimedOut:
|
||||||
|
Log.Error("Save failed, save timed out.");
|
||||||
|
Multiplayer.SendMessage("Save failed, save timed out.", playerId: callerId, font: MyFontEnum.Red);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ namespace Torch.Server.ViewModels
|
|||||||
{
|
{
|
||||||
private static readonly Logger Log = LogManager.GetLogger("Config");
|
private static readonly Logger Log = LogManager.GetLogger("Config");
|
||||||
private MyConfigDedicated<MyObjectBuilder_SessionSettings> _config;
|
private MyConfigDedicated<MyObjectBuilder_SessionSettings> _config;
|
||||||
|
public MyConfigDedicated<MyObjectBuilder_SessionSettings> Model => _config;
|
||||||
|
|
||||||
public ConfigDedicatedViewModel() : this(new MyConfigDedicated<MyObjectBuilder_SessionSettings>(""))
|
public ConfigDedicatedViewModel() : this(new MyConfigDedicated<MyObjectBuilder_SessionSettings>(""))
|
||||||
{
|
{
|
||||||
@@ -54,9 +55,10 @@ namespace Torch.Server.ViewModels
|
|||||||
_config.Save(path);
|
_config.Save(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SessionSettingsViewModel SessionSettings { get; }
|
private SessionSettingsViewModel _sessionSettings;
|
||||||
|
public SessionSettingsViewModel SessionSettings { get => _sessionSettings; set { _sessionSettings = value; OnPropertyChanged(); } }
|
||||||
|
|
||||||
public ObservableCollection<string> WorldPaths { get; } = new ObservableCollection<string>();
|
public ObservableList<string> WorldPaths { get; } = new ObservableList<string>();
|
||||||
private string _administrators;
|
private string _administrators;
|
||||||
public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } }
|
public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } }
|
||||||
private string _banned;
|
private string _banned;
|
||||||
|
@@ -15,7 +15,7 @@ namespace Torch.Server.ViewModels.Blocks
|
|||||||
public class BlockViewModel : EntityViewModel
|
public class BlockViewModel : EntityViewModel
|
||||||
{
|
{
|
||||||
public IMyTerminalBlock Block { get; }
|
public IMyTerminalBlock Block { get; }
|
||||||
public MTObservableCollection<PropertyViewModel> Properties { get; } = new MTObservableCollection<PropertyViewModel>();
|
public ObservableList<PropertyViewModel> Properties { get; } = new ObservableList<PropertyViewModel>();
|
||||||
|
|
||||||
public string FullName => $"{Block.CubeGrid.CustomName} - {Block.CustomName}";
|
public string FullName => $"{Block.CubeGrid.CustomName} - {Block.CustomName}";
|
||||||
|
|
||||||
@@ -24,8 +24,11 @@ namespace Torch.Server.ViewModels.Blocks
|
|||||||
get => Block?.CustomName ?? "null";
|
get => Block?.CustomName ?? "null";
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
TorchBase.Instance.InvokeBlocking(() => Block.CustomName = value);
|
TorchBase.Instance.Invoke(() =>
|
||||||
|
{
|
||||||
|
Block.CustomName = value;
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,13 +40,22 @@ namespace Torch.Server.ViewModels.Blocks
|
|||||||
get => ((MySlimBlock)Block.SlimBlock).BuiltBy;
|
get => ((MySlimBlock)Block.SlimBlock).BuiltBy;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
TorchBase.Instance.InvokeBlocking(() => ((MySlimBlock)Block.SlimBlock).TransferAuthorship(value));
|
TorchBase.Instance.Invoke(() =>
|
||||||
|
{
|
||||||
|
((MySlimBlock)Block.SlimBlock).TransferAuthorship(value);
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanStop => false;
|
public override bool CanStop => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Delete()
|
||||||
|
{
|
||||||
|
Block.CubeGrid.RazeBlock(Block.Position);
|
||||||
|
}
|
||||||
|
|
||||||
public BlockViewModel(IMyTerminalBlock block, EntityTreeViewModel tree) : base(block, tree)
|
public BlockViewModel(IMyTerminalBlock block, EntityTreeViewModel tree) : base(block, tree)
|
||||||
{
|
{
|
||||||
Block = block;
|
Block = block;
|
||||||
|
@@ -16,17 +16,15 @@ namespace Torch.Server.ViewModels.Blocks
|
|||||||
|
|
||||||
public T Value
|
public T Value
|
||||||
{
|
{
|
||||||
get
|
get => _prop.GetValue(Block.Block);
|
||||||
{
|
|
||||||
var val = default(T);
|
|
||||||
TorchBase.Instance.InvokeBlocking(() => val = _prop.GetValue(Block.Block));
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
TorchBase.Instance.InvokeBlocking(() => _prop.SetValue(Block.Block, value));
|
TorchBase.Instance.Invoke(() =>
|
||||||
|
{
|
||||||
|
_prop.SetValue(Block.Block, value);
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
Block.RefreshModel();
|
Block.RefreshModel();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,9 +37,15 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
|
|
||||||
public virtual bool CanDelete => !(Entity is IMyCharacter);
|
public virtual bool CanDelete => !(Entity is IMyCharacter);
|
||||||
|
|
||||||
|
public virtual void Delete()
|
||||||
|
{
|
||||||
|
Entity.Close();
|
||||||
|
}
|
||||||
|
|
||||||
public EntityViewModel(IMyEntity entity, EntityTreeViewModel tree)
|
public EntityViewModel(IMyEntity entity, EntityTreeViewModel tree)
|
||||||
{
|
{
|
||||||
Entity = entity;
|
Entity = entity;
|
||||||
|
Tree = tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntityViewModel()
|
public EntityViewModel()
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
using NLog;
|
using System.Linq;
|
||||||
using Sandbox.Game.Entities;
|
using Sandbox.Game.Entities;
|
||||||
using Sandbox.ModAPI;
|
using Sandbox.ModAPI;
|
||||||
using Torch.Server.ViewModels.Blocks;
|
using Torch.Server.ViewModels.Blocks;
|
||||||
@@ -9,17 +9,16 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
public class GridViewModel : EntityViewModel, ILazyLoad
|
public class GridViewModel : EntityViewModel, ILazyLoad
|
||||||
{
|
{
|
||||||
private MyCubeGrid Grid => (MyCubeGrid)Entity;
|
private MyCubeGrid Grid => (MyCubeGrid)Entity;
|
||||||
public MTObservableCollection<BlockViewModel> Blocks { get; } = new MTObservableCollection<BlockViewModel>();
|
public ObservableList<BlockViewModel> Blocks { get; } = new ObservableList<BlockViewModel>();
|
||||||
private static readonly Logger Log = LogManager.GetLogger(nameof(GridViewModel));
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string DescriptiveName => $"{Name} ({Grid.BlocksCount} blocks)";
|
public string DescriptiveName { get; }
|
||||||
|
|
||||||
public GridViewModel() { }
|
public GridViewModel() { }
|
||||||
|
|
||||||
public GridViewModel(MyCubeGrid grid, EntityTreeViewModel tree) : base(grid, tree)
|
public GridViewModel(MyCubeGrid grid, EntityTreeViewModel tree) : base(grid, tree)
|
||||||
{
|
{
|
||||||
Log.Debug($"Creating model {Grid.DisplayName}");
|
DescriptiveName = $"{grid.DisplayName} ({grid.BlocksCount} blocks)";
|
||||||
Blocks.Add(new BlockViewModel(null, Tree));
|
Blocks.Add(new BlockViewModel(null, Tree));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +27,6 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
if (obj.FatBlock != null)
|
if (obj.FatBlock != null)
|
||||||
Blocks.RemoveWhere(b => b.Block.EntityId == obj.FatBlock?.EntityId);
|
Blocks.RemoveWhere(b => b.Block.EntityId == obj.FatBlock?.EntityId);
|
||||||
|
|
||||||
Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName);
|
|
||||||
OnPropertyChanged(nameof(Name));
|
OnPropertyChanged(nameof(Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,9 +34,8 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
{
|
{
|
||||||
var block = obj.FatBlock as IMyTerminalBlock;
|
var block = obj.FatBlock as IMyTerminalBlock;
|
||||||
if (block != null)
|
if (block != null)
|
||||||
Blocks.Add(new BlockViewModel(block, Tree));
|
Blocks.Insert(new BlockViewModel(block, Tree), b => b.Name);
|
||||||
|
|
||||||
Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName);
|
|
||||||
OnPropertyChanged(nameof(Name));
|
OnPropertyChanged(nameof(Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,20 +45,23 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
if (_load)
|
if (_load)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Log.Debug($"Loading model {Grid.DisplayName}");
|
|
||||||
_load = true;
|
_load = true;
|
||||||
Blocks.Clear();
|
Blocks.Clear();
|
||||||
TorchBase.Instance.InvokeBlocking(() =>
|
TorchBase.Instance.Invoke(() =>
|
||||||
{
|
{
|
||||||
foreach (var block in Grid.GetFatBlocks().Where(b => b is IMyTerminalBlock))
|
foreach (var block in Grid.GetFatBlocks().Where(b => b is IMyTerminalBlock))
|
||||||
{
|
{
|
||||||
Blocks.Add(new BlockViewModel((IMyTerminalBlock)block, Tree));
|
Blocks.Add(new BlockViewModel((IMyTerminalBlock)block, Tree));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName);
|
|
||||||
|
|
||||||
Grid.OnBlockAdded += Grid_OnBlockAdded;
|
Grid.OnBlockAdded += Grid_OnBlockAdded;
|
||||||
Grid.OnBlockRemoved += Grid_OnBlockRemoved;
|
Grid.OnBlockRemoved += Grid_OnBlockRemoved;
|
||||||
|
|
||||||
|
Tree.ControlDispatcher.BeginInvoke(() =>
|
||||||
|
{
|
||||||
|
Blocks.Sort(b => b.Block.CustomName);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
|
|
||||||
public override bool CanStop => false;
|
public override bool CanStop => false;
|
||||||
|
|
||||||
public MTObservableCollection<GridViewModel> AttachedGrids { get; } = new MTObservableCollection<GridViewModel>();
|
public ObservableList<GridViewModel> AttachedGrids { get; } = new ObservableList<GridViewModel>();
|
||||||
|
|
||||||
public async Task UpdateAttachedGrids()
|
public async Task UpdateAttachedGrids()
|
||||||
{
|
{
|
||||||
|
@@ -3,22 +3,28 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Controls;
|
||||||
using Sandbox.Game.Entities;
|
using Sandbox.Game.Entities;
|
||||||
using Sandbox.Game.Entities.Character;
|
using Sandbox.Game.Entities.Character;
|
||||||
using Torch.Server.ViewModels.Entities;
|
using Torch.Server.ViewModels.Entities;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
using VRage.ModAPI;
|
using VRage.ModAPI;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
namespace Torch.Server.ViewModels
|
namespace Torch.Server.ViewModels
|
||||||
{
|
{
|
||||||
public class EntityTreeViewModel : ViewModel
|
public class EntityTreeViewModel : ViewModel
|
||||||
{
|
{
|
||||||
public MTObservableCollection<GridViewModel> Grids { get; set; } = new MTObservableCollection<GridViewModel>();
|
//TODO: these should be sorted sets for speed
|
||||||
public MTObservableCollection<CharacterViewModel> Characters { get; set; } = new MTObservableCollection<CharacterViewModel>();
|
public ObservableList<GridViewModel> Grids { get; set; } = new ObservableList<GridViewModel>();
|
||||||
public MTObservableCollection<EntityViewModel> FloatingObjects { get; set; } = new MTObservableCollection<EntityViewModel>();
|
public ObservableList<CharacterViewModel> Characters { get; set; } = new ObservableList<CharacterViewModel>();
|
||||||
public MTObservableCollection<VoxelMapViewModel> VoxelMaps { get; set; } = new MTObservableCollection<VoxelMapViewModel>();
|
public ObservableList<EntityViewModel> FloatingObjects { get; set; } = new ObservableList<EntityViewModel>();
|
||||||
|
public ObservableList<VoxelMapViewModel> VoxelMaps { get; set; } = new ObservableList<VoxelMapViewModel>();
|
||||||
|
public Dispatcher ControlDispatcher => _control.Dispatcher;
|
||||||
|
|
||||||
private EntityViewModel _currentEntity;
|
private EntityViewModel _currentEntity;
|
||||||
|
private UserControl _control;
|
||||||
|
|
||||||
public EntityViewModel CurrentEntity
|
public EntityViewModel CurrentEntity
|
||||||
{
|
{
|
||||||
@@ -26,7 +32,12 @@ namespace Torch.Server.ViewModels
|
|||||||
set { _currentEntity = value; OnPropertyChanged(); }
|
set { _currentEntity = value; OnPropertyChanged(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntityTreeViewModel()
|
public EntityTreeViewModel(UserControl control)
|
||||||
|
{
|
||||||
|
_control = control;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Init()
|
||||||
{
|
{
|
||||||
MyEntities.OnEntityAdd += MyEntities_OnEntityAdd;
|
MyEntities.OnEntityAdd += MyEntities_OnEntityAdd;
|
||||||
MyEntities.OnEntityRemove += MyEntities_OnEntityRemove;
|
MyEntities.OnEntityRemove += MyEntities_OnEntityRemove;
|
||||||
@@ -56,20 +67,16 @@ namespace Torch.Server.ViewModels
|
|||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
case MyCubeGrid grid:
|
case MyCubeGrid grid:
|
||||||
if (Grids.All(g => g.Entity.EntityId != obj.EntityId))
|
Grids.Insert(new GridViewModel(grid, this), g => g.Name);
|
||||||
Grids.Add(new GridViewModel(grid, this));
|
|
||||||
break;
|
break;
|
||||||
case MyCharacter character:
|
case MyCharacter character:
|
||||||
if (Characters.All(g => g.Entity.EntityId != obj.EntityId))
|
Characters.Insert(new CharacterViewModel(character, this), c => c.Name);
|
||||||
Characters.Add(new CharacterViewModel(character, this));
|
|
||||||
break;
|
break;
|
||||||
case MyFloatingObject floating:
|
case MyFloatingObject floating:
|
||||||
if (FloatingObjects.All(g => g.Entity.EntityId != obj.EntityId))
|
FloatingObjects.Insert(new FloatingObjectViewModel(floating, this), f => f.Name);
|
||||||
FloatingObjects.Add(new FloatingObjectViewModel(floating, this));
|
|
||||||
break;
|
break;
|
||||||
case MyVoxelBase voxel:
|
case MyVoxelBase voxel:
|
||||||
if (VoxelMaps.All(g => g.Entity.EntityId != obj.EntityId))
|
VoxelMaps.Insert(new VoxelMapViewModel(voxel, this), v => v.Name);
|
||||||
VoxelMaps.Add(new VoxelMapViewModel(voxel, this));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ namespace Torch.Server.ViewModels
|
|||||||
{
|
{
|
||||||
public class PluginManagerViewModel : ViewModel
|
public class PluginManagerViewModel : ViewModel
|
||||||
{
|
{
|
||||||
public MTObservableCollection<PluginViewModel> Plugins { get; } = new MTObservableCollection<PluginViewModel>();
|
public ObservableList<PluginViewModel> Plugins { get; } = new ObservableList<PluginViewModel>();
|
||||||
|
|
||||||
private PluginViewModel _selectedPlugin;
|
private PluginViewModel _selectedPlugin;
|
||||||
public PluginViewModel SelectedPlugin
|
public PluginViewModel SelectedPlugin
|
||||||
@@ -24,10 +24,12 @@ namespace Torch.Server.ViewModels
|
|||||||
|
|
||||||
public PluginManagerViewModel(IPluginManager pluginManager)
|
public PluginManagerViewModel(IPluginManager pluginManager)
|
||||||
{
|
{
|
||||||
|
foreach (var plugin in pluginManager)
|
||||||
|
Plugins.Add(new PluginViewModel(plugin));
|
||||||
pluginManager.PluginsLoaded += PluginManager_PluginsLoaded;
|
pluginManager.PluginsLoaded += PluginManager_PluginsLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PluginManager_PluginsLoaded(List<ITorchPlugin> obj)
|
private void PluginManager_PluginsLoaded(IList<ITorchPlugin> obj)
|
||||||
{
|
{
|
||||||
Plugins.Clear();
|
Plugins.Clear();
|
||||||
foreach (var plugin in obj)
|
foreach (var plugin in obj)
|
||||||
|
@@ -35,7 +35,7 @@ namespace Torch.Server.ViewModels
|
|||||||
BlockLimits.Add(new BlockLimitViewModel(this, limit.Key, limit.Value));
|
BlockLimits.Add(new BlockLimitViewModel(this, limit.Key, limit.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MTObservableCollection<BlockLimitViewModel> BlockLimits { get; } = new MTObservableCollection<BlockLimitViewModel>();
|
public ObservableList<BlockLimitViewModel> BlockLimits { get; } = new ObservableList<BlockLimitViewModel>();
|
||||||
|
|
||||||
#region Multipliers
|
#region Multipliers
|
||||||
|
|
||||||
|
@@ -5,12 +5,13 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:Torch.Server"
|
xmlns:local="clr-namespace:Torch.Server"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<DockPanel>
|
<Grid>
|
||||||
<DockPanel DockPanel.Dock="Bottom">
|
<Grid.RowDefinitions>
|
||||||
<Button x:Name="Send" Content="Send" DockPanel.Dock="Right" Width="50" Margin="5,5,5,5" Click="SendButton_Click"></Button>
|
<RowDefinition/>
|
||||||
<TextBox x:Name="Message" DockPanel.Dock="Left" Margin="5,5,5,5" KeyDown="Message_OnKeyDown"></TextBox>
|
<RowDefinition Height="Auto"/>
|
||||||
</DockPanel>
|
</Grid.RowDefinitions>
|
||||||
<ListView x:Name="ChatItems" ItemsSource="{Binding ChatHistory}" Margin="5,5,5,5">
|
<ListView Grid.Row="0" x:Name="ChatItems" ItemsSource="{Binding ChatHistory}" Margin="5,5,5,5">
|
||||||
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled"/>
|
||||||
<ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<WrapPanel>
|
<WrapPanel>
|
||||||
@@ -23,5 +24,13 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView.ItemTemplate>
|
||||||
</ListView>
|
</ListView>
|
||||||
</DockPanel>
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Button Grid.Column="1" x:Name="Send" Content="Send" DockPanel.Dock="Right" Width="50" Margin="5,5,5,5" Click="SendButton_Click"></Button>
|
||||||
|
<TextBox Grid.Column="0" x:Name="Message" DockPanel.Dock="Left" Margin="5,5,5,5" KeyDown="Message_OnKeyDown"></TextBox>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -40,7 +41,20 @@ namespace Torch.Server
|
|||||||
{
|
{
|
||||||
_server = (TorchBase)server;
|
_server = (TorchBase)server;
|
||||||
_multiplayer = (MultiplayerManager)server.Multiplayer;
|
_multiplayer = (MultiplayerManager)server.Multiplayer;
|
||||||
|
ChatItems.Items.Clear();
|
||||||
DataContext = _multiplayer;
|
DataContext = _multiplayer;
|
||||||
|
if (_multiplayer.ChatHistory is INotifyCollectionChanged ncc)
|
||||||
|
ncc.CollectionChanged += ChatHistory_CollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (VisualTreeHelper.GetChildrenCount(ChatItems) > 0)
|
||||||
|
{
|
||||||
|
Border border = (Border)VisualTreeHelper.GetChild(ChatItems, 0);
|
||||||
|
ScrollViewer scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
|
||||||
|
scrollViewer.ScrollToBottom();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendButton_Click(object sender, RoutedEventArgs e)
|
private void SendButton_Click(object sender, RoutedEventArgs e)
|
||||||
@@ -62,22 +76,26 @@ namespace Torch.Server
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var commands = _server.Commands;
|
var commands = _server.Commands;
|
||||||
string response = null;
|
|
||||||
if (commands.IsCommand(text))
|
if (commands.IsCommand(text))
|
||||||
{
|
{
|
||||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text));
|
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text));
|
||||||
_server.InvokeBlocking(() =>
|
_server.Invoke(() =>
|
||||||
{
|
{
|
||||||
response = commands.HandleCommandFromServer(text);
|
var response = commands.HandleCommandFromServer(text);
|
||||||
|
Dispatcher.BeginInvoke(() => OnMessageEntered_Callback(response));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_server.Multiplayer.SendMessage(text);
|
_server.Multiplayer.SendMessage(text);
|
||||||
}
|
}
|
||||||
|
Message.Text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMessageEntered_Callback(string response)
|
||||||
|
{
|
||||||
if (!string.IsNullOrEmpty(response))
|
if (!string.IsNullOrEmpty(response))
|
||||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
|
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
|
||||||
Message.Text = "";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,15 +10,27 @@
|
|||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<viewModels:ConfigDedicatedViewModel />
|
<viewModels:ConfigDedicatedViewModel />
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
<DockPanel>
|
<Grid>
|
||||||
<DockPanel DockPanel.Dock="Top">
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<DockPanel Grid.Row="0">
|
||||||
<Label Content="World:" DockPanel.Dock="Left" />
|
<Label Content="World:" DockPanel.Dock="Left" />
|
||||||
<Button Content="New World" Margin="3" DockPanel.Dock="Right" Click="NewWorld_OnClick"/>
|
<Button Content="New World" Margin="3" DockPanel.Dock="Right" Click="NewWorld_OnClick"/>
|
||||||
<ComboBox Text="{Binding LoadWorld}" ItemsSource="{Binding WorldPaths}" IsEditable="True" Margin="3" SelectionChanged="Selector_OnSelectionChanged"/>
|
<ComboBox Text="{Binding LoadWorld}" ItemsSource="{Binding WorldPaths}" IsEditable="True" Margin="3" SelectionChanged="Selector_OnSelectionChanged"/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<DockPanel DockPanel.Dock="Bottom">
|
<Grid Grid.Row="1">
|
||||||
<StackPanel DockPanel.Dock="Left">
|
<Grid.ColumnDefinitions>
|
||||||
<ScrollViewer IsEnabled="True">
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid Grid.Column="0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<ScrollViewer Grid.Row="0" Margin="3">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<StackPanel Margin="3" DockPanel.Dock="Left">
|
<StackPanel Margin="3" DockPanel.Dock="Left">
|
||||||
<Label Content="Server Name" />
|
<Label Content="Server Name" />
|
||||||
@@ -37,24 +49,6 @@
|
|||||||
<CheckBox IsChecked="{Binding PauseGameWhenEmpty}" Content="Pause When Empty" Margin="3" />
|
<CheckBox IsChecked="{Binding PauseGameWhenEmpty}" Content="Pause When Empty" Margin="3" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Margin="3">
|
<StackPanel Margin="3">
|
||||||
<!--
|
|
||||||
<ListBox ItemsSource="{Binding Banned}" Width="130" Margin="3,0,3,3" Height="100"
|
|
||||||
MouseDoubleClick="Banned_OnMouseDoubleClick" /> -->
|
|
||||||
<!--
|
|
||||||
<ListBox ItemsSource="{Binding Administrators}" Width="130" Margin="3,0,3,3" Height="100"
|
|
||||||
MouseDoubleClick="Administrators_OnMouseDoubleClick">
|
|
||||||
<ListBox.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBox Text="{Binding SteamId}" Width="100"/>
|
|
||||||
<Label Content="{Binding Name}"/>
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
</ListBox> -->
|
|
||||||
<!--
|
|
||||||
<ListBox ItemsSource="{Binding Mods}" Width="130" Margin="3,0,3,3" Height="100"
|
|
||||||
MouseDoubleClick="Mods_OnMouseDoubleClick" /> -->
|
|
||||||
<Label Content="Mods" />
|
<Label Content="Mods" />
|
||||||
<TextBox Text="{Binding Mods}" Margin="3" Height="100" AcceptsReturn="true" VerticalScrollBarVisibility="Auto"/>
|
<TextBox Text="{Binding Mods}" Margin="3" Height="100" AcceptsReturn="true" VerticalScrollBarVisibility="Auto"/>
|
||||||
<Label Content="Administrators" />
|
<Label Content="Administrators" />
|
||||||
@@ -64,23 +58,23 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
<Button Content="Save Config" Margin="3" Click="Save_OnClick" />
|
<Button Grid.Row="1" Content="Save Config" Margin="3" Click="Save_OnClick" />
|
||||||
</StackPanel>
|
</Grid>
|
||||||
<ScrollViewer Margin="3" DockPanel.Dock="Right" IsEnabled="True">
|
<ScrollViewer Grid.Column="1" Margin="3">
|
||||||
<StackPanel DataContext="{Binding SessionSettings}">
|
<StackPanel DataContext="{Binding SessionSettings}">
|
||||||
<Expander Header="Block Limits">
|
<Expander Header="Block Limits">
|
||||||
<StackPanel Margin="10,0,0,0">
|
<StackPanel Margin="10,0,0,0">
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding MaxBlocksPerPlayer}" Margin="3" Width="70" />
|
<TextBox Text="{Binding MaxBlocksPerPlayer}" Margin="3" Width="70" />
|
||||||
<Label Content="Max Blocks Per Player" />
|
<Label Content="Max Blocks Per Player" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding MaxGridSize}" Margin="3" Width="70" />
|
<TextBox Text="{Binding MaxGridSize}" Margin="3" Width="70" />
|
||||||
<Label Content="Max Grid Size" />
|
<Label Content="Max Grid Size" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<Button Content="Add" Margin="3" Click="AddLimit_OnClick" />
|
<Button Content="Add" Margin="3" Click="AddLimit_OnClick" />
|
||||||
<ListView ItemsSource="{Binding BlockLimits}" Margin="3">
|
<ListView ItemsSource="{Binding BlockLimits}" Margin="3">
|
||||||
<ListBox.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding BlockType}" Width="150" Margin="3" />
|
<TextBox Text="{Binding BlockType}" Width="150" Margin="3" />
|
||||||
@@ -88,36 +82,36 @@
|
|||||||
<Button Content=" X " Margin="3" Click="RemoveLimit_OnClick" />
|
<Button Content=" X " Margin="3" Click="RemoveLimit_OnClick" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListView.ItemTemplate>
|
||||||
</ListView>
|
</ListView>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Expander>
|
</Expander>
|
||||||
<Expander Header="Multipliers">
|
<Expander Header="Multipliers">
|
||||||
<StackPanel Margin="10,0,0,0">
|
<StackPanel Margin="10,0,0,0">
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding InventorySizeMultiplier}" Margin="3" Width="70" />
|
<TextBox Text="{Binding InventorySizeMultiplier}" Margin="3" Width="70" />
|
||||||
<Label Content="Inventory Size" />
|
<Label Content="Inventory Size" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding RefinerySpeedMultiplier}" Margin="3" Width="70" />
|
<TextBox Text="{Binding RefinerySpeedMultiplier}" Margin="3" Width="70" />
|
||||||
<Label Content="Refinery Speed" />
|
<Label Content="Refinery Speed" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding AssemblerEfficiencyMultiplier}" Margin="3" Width="70" />
|
<TextBox Text="{Binding AssemblerEfficiencyMultiplier}" Margin="3" Width="70" />
|
||||||
<Label Content="Assembler Efficiency" />
|
<Label Content="Assembler Efficiency" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding AssemblerSpeedMultiplier}" Margin="3" Width="70" />
|
<TextBox Text="{Binding AssemblerSpeedMultiplier}" Margin="3" Width="70" />
|
||||||
<Label Content="Assembler Speed" />
|
<Label Content="Assembler Speed" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding GrinderSpeedMultiplier}" Margin="3" Width="70" />
|
<TextBox Text="{Binding GrinderSpeedMultiplier}" Margin="3" Width="70" />
|
||||||
<Label Content="Grinder Speed" />
|
<Label Content="Grinder Speed" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding HackSpeedMultiplier}" Margin="3" Width="70" />
|
<TextBox Text="{Binding HackSpeedMultiplier}" Margin="3" Width="70" />
|
||||||
<Label Content="Hacking Speed" />
|
<Label Content="Hacking Speed" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Expander>
|
</Expander>
|
||||||
<Expander Header="NPCs">
|
<Expander Header="NPCs">
|
||||||
@@ -131,14 +125,14 @@
|
|||||||
</Expander>
|
</Expander>
|
||||||
<Expander Header="Environment">
|
<Expander Header="Environment">
|
||||||
<StackPanel Margin="10,0,0,0">
|
<StackPanel Margin="10,0,0,0">
|
||||||
<DockPanel ToolTip="Increases physics precision at the cost of performance.">
|
<StackPanel Orientation="Horizontal" ToolTip="Increases physics precision at the cost of performance.">
|
||||||
<TextBox Text="{Binding PhysicsIterations}" Margin="3" Width="70" />
|
<TextBox Text="{Binding PhysicsIterations}" Margin="3" Width="70" />
|
||||||
<Label Content="Physics Iterations" />
|
<Label Content="Physics Iterations" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding MaxFloatingObjects}" Margin="3" Width="70" />
|
<TextBox Text="{Binding MaxFloatingObjects}" Margin="3" Width="70" />
|
||||||
<Label Content="Max Floating Objects" />
|
<Label Content="Max Floating Objects" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<CheckBox IsChecked="{Binding EnableRealisticSound}" Content="Enable Realistic Sound"
|
<CheckBox IsChecked="{Binding EnableRealisticSound}" Content="Enable Realistic Sound"
|
||||||
Margin="3" />
|
Margin="3" />
|
||||||
<CheckBox IsChecked="{Binding EnableAirtightness}" Content="Enable Airtightness" Margin="3" />
|
<CheckBox IsChecked="{Binding EnableAirtightness}" Content="Enable Airtightness" Margin="3" />
|
||||||
@@ -149,41 +143,41 @@
|
|||||||
<CheckBox IsChecked="{Binding EnableVoxelDestruction}" Content="Enable Voxel Destruction"
|
<CheckBox IsChecked="{Binding EnableVoxelDestruction}" Content="Enable Voxel Destruction"
|
||||||
Margin="3" />
|
Margin="3" />
|
||||||
<CheckBox IsChecked="{Binding EnableSunRotation}" Content="Enable Sun Rotation" Margin="3" />
|
<CheckBox IsChecked="{Binding EnableSunRotation}" Content="Enable Sun Rotation" Margin="3" />
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding SunRotationInterval}" Margin="3" Width="70" />
|
<TextBox Text="{Binding SunRotationInterval}" Margin="3" Width="70" />
|
||||||
<Label Content="Sun Rotation Interval (mins)" />
|
<Label Content="Sun Rotation Interval (mins)" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<CheckBox IsChecked="{Binding EnableFlora}" Content="Enable Flora" Margin="3" />
|
<CheckBox IsChecked="{Binding EnableFlora}" Content="Enable Flora" Margin="3" />
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding FloraDensity}" Margin="3" Width="70" />
|
<TextBox Text="{Binding FloraDensity}" Margin="3" Width="70" />
|
||||||
<Label Content="Flora Density" />
|
<Label Content="Flora Density" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding FloraDensityMultiplier}" Margin="3" Width="70" />
|
<TextBox Text="{Binding FloraDensityMultiplier}" Margin="3" Width="70" />
|
||||||
<Label Content="Flora Density Multiplier" />
|
<Label Content="Flora Density Multiplier" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding ViewDistance}" Margin="3" Width="70" />
|
<TextBox Text="{Binding ViewDistance}" Margin="3" Width="70" />
|
||||||
<Label Content="View Distance (meters)" />
|
<Label Content="View Distance (meters)" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding WorldSize}" Margin="3" Width="70" />
|
<TextBox Text="{Binding WorldSize}" Margin="3" Width="70" />
|
||||||
<Label Content="World Size (km)" />
|
<Label Content="World Size (km)" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<ComboBox SelectedItem="{Binding EnvironmentHostility}"
|
<ComboBox SelectedItem="{Binding EnvironmentHostility}"
|
||||||
ItemsSource="{Binding EnvironmentHostilityValues}" Margin="3" Width="100"
|
ItemsSource="{Binding EnvironmentHostilityValues}" Margin="3" Width="100"
|
||||||
DockPanel.Dock="Left" />
|
DockPanel.Dock="Left" />
|
||||||
<Label Content="Environment Hostility" />
|
<Label Content="Environment Hostility" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Expander>
|
</Expander>
|
||||||
<Expander Header="Players">
|
<Expander Header="Players">
|
||||||
<StackPanel Margin="10,0,0,0">
|
<StackPanel Margin="10,0,0,0">
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding MaxPlayers}" Margin="3" Width="70" />
|
<TextBox Text="{Binding MaxPlayers}" Margin="3" Width="70" />
|
||||||
<Label Content="Max Players" />
|
<Label Content="Max Players" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<CheckBox IsChecked="{Binding EnableThirdPerson}" Content="Enable 3rd Person Camera"
|
<CheckBox IsChecked="{Binding EnableThirdPerson}" Content="Enable 3rd Person Camera"
|
||||||
Margin="3" />
|
Margin="3" />
|
||||||
<CheckBox IsChecked="{Binding EnableJetpack}" Content="Enable Jetpack" Margin="3" />
|
<CheckBox IsChecked="{Binding EnableJetpack}" Content="Enable Jetpack" Margin="3" />
|
||||||
@@ -191,20 +185,20 @@
|
|||||||
<CheckBox IsChecked="{Binding EnableCopyPaste}" Content="Enable Copy/Paste" Margin="3" />
|
<CheckBox IsChecked="{Binding EnableCopyPaste}" Content="Enable Copy/Paste" Margin="3" />
|
||||||
<CheckBox IsChecked="{Binding ShowPlayerNamesOnHud}" Content="Show Player Names on HUD"
|
<CheckBox IsChecked="{Binding ShowPlayerNamesOnHud}" Content="Show Player Names on HUD"
|
||||||
Margin="3" />
|
Margin="3" />
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding SpawnTimeMultiplier}" Margin="3" Width="70" />
|
<TextBox Text="{Binding SpawnTimeMultiplier}" Margin="3" Width="70" />
|
||||||
<Label Content="Respawn Time Multiplier" />
|
<Label Content="Respawn Time Multiplier" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<CheckBox IsChecked="{Binding ResetOwnership}" Content="Reset Ownership" Margin="3" />
|
<CheckBox IsChecked="{Binding ResetOwnership}" Content="Reset Ownership" Margin="3" />
|
||||||
<CheckBox IsChecked="{Binding SpawnWithTools}" Content="Spawn With Tools" Margin="3" />
|
<CheckBox IsChecked="{Binding SpawnWithTools}" Content="Spawn With Tools" Margin="3" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Expander>
|
</Expander>
|
||||||
<Expander Header="Miscellaneous">
|
<Expander Header="Miscellaneous">
|
||||||
<StackPanel Margin="10,0,0,0">
|
<StackPanel Margin="10,0,0,0">
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding AutosaveInterval}" Margin="3" Width="70" />
|
<TextBox Text="{Binding AutosaveInterval}" Margin="3" Width="70" />
|
||||||
<Label Content="Autosave Interval (minutes)" />
|
<Label Content="Autosave Interval (minutes)" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<CheckBox IsChecked="{Binding EnableConvertToStation}" Content="Enable Convert to Station"
|
<CheckBox IsChecked="{Binding EnableConvertToStation}" Content="Enable Convert to Station"
|
||||||
Margin="3" />
|
Margin="3" />
|
||||||
|
|
||||||
@@ -223,19 +217,19 @@
|
|||||||
<CheckBox IsChecked="{Binding EnableWeapons}" Content="Enable Weapons" Margin="3" />
|
<CheckBox IsChecked="{Binding EnableWeapons}" Content="Enable Weapons" Margin="3" />
|
||||||
<CheckBox IsChecked="{Binding EnableIngameScripts}" Content="Enable Ingame Scripts"
|
<CheckBox IsChecked="{Binding EnableIngameScripts}" Content="Enable Ingame Scripts"
|
||||||
Margin="3" />
|
Margin="3" />
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<ComboBox SelectedItem="{Binding GameMode}" ItemsSource="{Binding GameModeValues}"
|
<ComboBox SelectedItem="{Binding GameMode}" ItemsSource="{Binding GameModeValues}"
|
||||||
Margin="3" Width="100" DockPanel.Dock="Left" />
|
Margin="3" Width="100" DockPanel.Dock="Left" />
|
||||||
<Label Content="Game Mode" />
|
<Label Content="Game Mode" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding MaxBackupSaves}" Margin="3" Width="70" />
|
<TextBox Text="{Binding MaxBackupSaves}" Margin="3" Width="70" />
|
||||||
<Label Content="Max Backup Saves" />
|
<Label Content="Max Backup Saves" />
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Expander>
|
</Expander>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</DockPanel>
|
</Grid>
|
||||||
</DockPanel>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
@@ -21,6 +21,7 @@ using NLog;
|
|||||||
using Sandbox;
|
using Sandbox;
|
||||||
using Sandbox.Engine.Networking;
|
using Sandbox.Engine.Networking;
|
||||||
using Sandbox.Engine.Utils;
|
using Sandbox.Engine.Utils;
|
||||||
|
using Torch.Server.Managers;
|
||||||
using Torch.Server.ViewModels;
|
using Torch.Server.ViewModels;
|
||||||
using Torch.Views;
|
using Torch.Views;
|
||||||
using VRage;
|
using VRage;
|
||||||
@@ -36,110 +37,29 @@ namespace Torch.Server.Views
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class ConfigControl : UserControl
|
public partial class ConfigControl : UserControl
|
||||||
{
|
{
|
||||||
private readonly Logger Log = LogManager.GetLogger("Config");
|
private InstanceManager _instanceManager;
|
||||||
public MyConfigDedicated<MyObjectBuilder_SessionSettings> Config { get; set; }
|
|
||||||
private ConfigDedicatedViewModel _viewModel;
|
|
||||||
private string _configPath;
|
|
||||||
private TorchConfig _torchConfig;
|
|
||||||
|
|
||||||
public ConfigControl()
|
public ConfigControl()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
_instanceManager = TorchBase.Instance.GetManager<InstanceManager>();
|
||||||
|
DataContext = _instanceManager.DedicatedConfig;
|
||||||
public void SaveConfig()
|
|
||||||
{
|
|
||||||
_viewModel.Save(_configPath);
|
|
||||||
Log.Info("Saved DS config.");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//var checkpoint = MyLocalCache.LoadCheckpoint(Config.LoadWorld, out _);
|
|
||||||
MyObjectBuilderSerializer.DeserializeXML(Path.Combine(Config.LoadWorld, "Sandbox.sbc"), out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
|
||||||
if (checkpoint == null)
|
|
||||||
{
|
|
||||||
Log.Error($"Failed to load {Config.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
checkpoint.Settings = Config.SessionSettings;
|
|
||||||
checkpoint.Mods.Clear();
|
|
||||||
foreach (var modId in Config.Mods)
|
|
||||||
checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId));
|
|
||||||
|
|
||||||
MyLocalCache.SaveCheckpoint(checkpoint, Config.LoadWorld);
|
|
||||||
Log.Info("Saved world config.");
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error("Failed to write sandbox config, changes will not appear on server");
|
|
||||||
Log.Error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadDedicatedConfig(TorchConfig torchConfig)
|
|
||||||
{
|
|
||||||
_torchConfig = torchConfig;
|
|
||||||
DataContext = null;
|
|
||||||
MySandboxGame.Config = new MyConfig(MyPerServerSettings.GameNameSafe + ".cfg");
|
|
||||||
var path = Path.Combine(torchConfig.InstancePath, "SpaceEngineers-Dedicated.cfg");
|
|
||||||
|
|
||||||
if (!File.Exists(path))
|
|
||||||
{
|
|
||||||
Log.Error($"Failed to load dedicated config at {path}");
|
|
||||||
DataContext = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(path);
|
|
||||||
Config.Load(path);
|
|
||||||
_configPath = path;
|
|
||||||
|
|
||||||
_viewModel = new ConfigDedicatedViewModel(Config);
|
|
||||||
var worldFolders = Directory.EnumerateDirectories(Path.Combine(torchConfig.InstancePath, "Saves"));
|
|
||||||
|
|
||||||
foreach (var f in worldFolders)
|
|
||||||
_viewModel.WorldPaths.Add(f);
|
|
||||||
|
|
||||||
LoadWorldMods();
|
|
||||||
DataContext = _viewModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadWorldMods()
|
|
||||||
{
|
|
||||||
var sandboxPath = Path.Combine(Config.LoadWorld, "Sandbox.sbc");
|
|
||||||
|
|
||||||
if (!File.Exists(sandboxPath))
|
|
||||||
return;
|
|
||||||
|
|
||||||
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
|
||||||
if (checkpoint == null)
|
|
||||||
{
|
|
||||||
Log.Error($"Failed to load {Config.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
foreach (var mod in checkpoint.Mods)
|
|
||||||
sb.AppendLine(mod.PublishedFileId.ToString());
|
|
||||||
|
|
||||||
_viewModel.Mods = sb.ToString();
|
|
||||||
|
|
||||||
Log.Info("Loaded mod list from world");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Save_OnClick(object sender, RoutedEventArgs e)
|
private void Save_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
SaveConfig();
|
_instanceManager.SaveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveLimit_OnClick(object sender, RoutedEventArgs e)
|
private void RemoveLimit_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var vm = (BlockLimitViewModel)((Button)sender).DataContext;
|
var vm = (BlockLimitViewModel)((Button)sender).DataContext;
|
||||||
_viewModel.SessionSettings.BlockLimits.Remove(vm);
|
_instanceManager.DedicatedConfig.SessionSettings.BlockLimits.Remove(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddLimit_OnClick(object sender, RoutedEventArgs e)
|
private void AddLimit_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_viewModel.SessionSettings.BlockLimits.Add(new BlockLimitViewModel(_viewModel.SessionSettings, "", 0));
|
_instanceManager.DedicatedConfig.SessionSettings.BlockLimits.Add(new BlockLimitViewModel(_instanceManager.DedicatedConfig.SessionSettings, "", 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NewWorld_OnClick(object sender, RoutedEventArgs e)
|
private void NewWorld_OnClick(object sender, RoutedEventArgs e)
|
||||||
@@ -152,8 +72,7 @@ namespace Torch.Server.Views
|
|||||||
//The control doesn't update the binding before firing the event.
|
//The control doesn't update the binding before firing the event.
|
||||||
if (e.AddedItems.Count > 0)
|
if (e.AddedItems.Count > 0)
|
||||||
{
|
{
|
||||||
Config.LoadWorld = (string)e.AddedItems[0];
|
_instanceManager.SelectWorld((string)e.AddedItems[0]);
|
||||||
LoadWorldMods();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,8 +9,12 @@
|
|||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<blocks:BlockViewModel />
|
<blocks:BlockViewModel />
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
<DockPanel x:Name="Stack" Margin="3">
|
<Grid Margin="3">
|
||||||
<StackPanel DockPanel.Dock="Top">
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Grid.Row="0">
|
||||||
<Label Content="{Binding FullName}" FontSize="16" />
|
<Label Content="{Binding FullName}" FontSize="16" />
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Label Content="Built By: "/>
|
<Label Content="Built By: "/>
|
||||||
@@ -18,7 +22,7 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Label Content="Properties"/>
|
<Label Content="Properties"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ListView ItemsSource="{Binding Properties}" Margin="3" IsEnabled="True" DockPanel.Dock="Bottom">
|
<ListView Grid.Row="1" ItemsSource="{Binding Properties}" Margin="3" IsEnabled="True">
|
||||||
<ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<local:PropertyView />
|
<local:PropertyView />
|
||||||
@@ -35,5 +39,5 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</ListView.ItemContainerStyle>
|
</ListView.ItemContainerStyle>
|
||||||
</ListView>
|
</ListView>
|
||||||
</DockPanel>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
@@ -10,13 +10,17 @@
|
|||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:StringIdConverter x:Key="StringIdConverter"/>
|
<converters:StringIdConverter x:Key="StringIdConverter"/>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<DockPanel x:Name="Dock">
|
<Grid>
|
||||||
<Label x:Name="Label" Width="150" VerticalAlignment="Center" DockPanel.Dock="Left">
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Label Grid.Column="0" x:Name="Label" Width="150" VerticalAlignment="Center">
|
||||||
<Label.Content>
|
<Label.Content>
|
||||||
<TextBlock Text="{Binding Name, StringFormat={}{0}: }" />
|
<TextBlock Text="{Binding Name, StringFormat={}{0}: }" />
|
||||||
</Label.Content>
|
</Label.Content>
|
||||||
</Label>
|
</Label>
|
||||||
<Frame x:Name="Frame" DockPanel.Dock="Right" NavigationUIVisibility="Hidden"/>
|
<Frame Grid.Column="1" x:Name="Frame" NavigationUIVisibility="Hidden"/>
|
||||||
</DockPanel>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
||||||
|
@@ -32,10 +32,10 @@ namespace Torch.Server.Views.Blocks
|
|||||||
{
|
{
|
||||||
switch (args.NewValue)
|
switch (args.NewValue)
|
||||||
{
|
{
|
||||||
case PropertyViewModel<bool> vmBool:
|
case PropertyViewModel<bool> _:
|
||||||
InitBool();
|
InitBool();
|
||||||
break;
|
break;
|
||||||
case PropertyViewModel<StringBuilder> vmSb:
|
case PropertyViewModel<StringBuilder> _:
|
||||||
InitStringBuilder();
|
InitStringBuilder();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@@ -8,17 +8,17 @@
|
|||||||
xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities"
|
xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities"
|
||||||
xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks"
|
xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<UserControl.DataContext>
|
<Grid>
|
||||||
<viewModels:EntityTreeViewModel />
|
<Grid.ColumnDefinitions>
|
||||||
</UserControl.DataContext>
|
<ColumnDefinition/>
|
||||||
<DockPanel>
|
<ColumnDefinition MinWidth="300" Width="Auto"/>
|
||||||
<DockPanel DockPanel.Dock="Left">
|
</Grid.ColumnDefinitions>
|
||||||
<StackPanel DockPanel.Dock="Bottom">
|
<Grid Grid.Column="0">
|
||||||
<Button Content="Delete" Click="Delete_OnClick" IsEnabled="{Binding CurrentEntity.CanDelete}"
|
<Grid.RowDefinitions>
|
||||||
Margin="3" />
|
<RowDefinition/>
|
||||||
<Button Content="Stop" Click="Stop_OnClick" IsEnabled="{Binding CurrentEntity.CanStop}" Margin="3" />
|
<RowDefinition Height="Auto"/>
|
||||||
</StackPanel>
|
</Grid.RowDefinitions>
|
||||||
<TreeView Width="300" Margin="3" DockPanel.Dock="Top" SelectedItemChanged="TreeView_OnSelectedItemChanged" TreeViewItem.Expanded="TreeViewItem_OnExpanded">
|
<TreeView Grid.Row="0" Margin="3" DockPanel.Dock="Top" SelectedItemChanged="TreeView_OnSelectedItemChanged" TreeViewItem.Expanded="TreeViewItem_OnExpanded">
|
||||||
<TreeView.Resources>
|
<TreeView.Resources>
|
||||||
<HierarchicalDataTemplate DataType="{x:Type entities:GridViewModel}" ItemsSource="{Binding Blocks}">
|
<HierarchicalDataTemplate DataType="{x:Type entities:GridViewModel}" ItemsSource="{Binding Blocks}">
|
||||||
<TextBlock Text="{Binding DescriptiveName}" />
|
<TextBlock Text="{Binding DescriptiveName}" />
|
||||||
@@ -66,7 +66,12 @@
|
|||||||
</TreeViewItem.ItemTemplate>
|
</TreeViewItem.ItemTemplate>
|
||||||
</TreeViewItem>
|
</TreeViewItem>
|
||||||
</TreeView>
|
</TreeView>
|
||||||
</DockPanel>
|
<StackPanel Grid.Row="1" DockPanel.Dock="Bottom">
|
||||||
<Frame x:Name="EditorFrame" Margin="3" NavigationUIVisibility="Hidden" />
|
<Button Content="Delete" Click="Delete_OnClick" IsEnabled="{Binding CurrentEntity.CanDelete}"
|
||||||
</DockPanel>
|
Margin="3" />
|
||||||
|
<Button Content="Stop" Click="Stop_OnClick" IsEnabled="{Binding CurrentEntity.CanStop}" Margin="3" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<Frame Grid.Column="1" x:Name="EditorFrame" Margin="3" NavigationUIVisibility="Hidden" />
|
||||||
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
@@ -27,12 +27,14 @@ namespace Torch.Server.Views
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class EntitiesControl : UserControl
|
public partial class EntitiesControl : UserControl
|
||||||
{
|
{
|
||||||
public EntityTreeViewModel Entities { get; set; } = new EntityTreeViewModel();
|
public EntityTreeViewModel Entities { get; set; }
|
||||||
|
|
||||||
public EntitiesControl()
|
public EntitiesControl()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
Entities = new EntityTreeViewModel(this);
|
||||||
DataContext = Entities;
|
DataContext = Entities;
|
||||||
|
Entities.Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||||
@@ -41,7 +43,7 @@ namespace Torch.Server.Views
|
|||||||
{
|
{
|
||||||
Entities.CurrentEntity = vm;
|
Entities.CurrentEntity = vm;
|
||||||
if (e.NewValue is GridViewModel gvm)
|
if (e.NewValue is GridViewModel gvm)
|
||||||
EditorFrame.Content = new Entities.GridView { DataContext = gvm};
|
EditorFrame.Content = new Entities.GridView {DataContext = gvm};
|
||||||
if (e.NewValue is BlockViewModel bvm)
|
if (e.NewValue is BlockViewModel bvm)
|
||||||
EditorFrame.Content = new BlockView {DataContext = bvm};
|
EditorFrame.Content = new BlockView {DataContext = bvm};
|
||||||
if (e.NewValue is VoxelMapViewModel vvm)
|
if (e.NewValue is VoxelMapViewModel vvm)
|
||||||
@@ -58,7 +60,7 @@ namespace Torch.Server.Views
|
|||||||
{
|
{
|
||||||
if (Entities.CurrentEntity?.Entity is IMyCharacter)
|
if (Entities.CurrentEntity?.Entity is IMyCharacter)
|
||||||
return;
|
return;
|
||||||
TorchBase.Instance.Invoke(() => Entities.CurrentEntity?.Entity.Close());
|
TorchBase.Instance.Invoke(() => Entities.CurrentEntity?.Delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Stop_OnClick(object sender, RoutedEventArgs e)
|
private void Stop_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
@@ -14,9 +14,9 @@
|
|||||||
<ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<WrapPanel>
|
<WrapPanel>
|
||||||
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
|
<TextBlock Text="{Binding Value.Name}" FontWeight="Bold"/>
|
||||||
<TextBlock Text=" ("/>
|
<TextBlock Text=" ("/>
|
||||||
<TextBlock Text="{Binding State}"/>
|
<TextBlock Text="{Binding Value.State}"/>
|
||||||
<TextBlock Text=")"/>
|
<TextBlock Text=")"/>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
@@ -21,6 +21,7 @@ using Sandbox.ModAPI;
|
|||||||
using SteamSDK;
|
using SteamSDK;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
|
using Torch.ViewModels;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
namespace Torch.Server
|
namespace Torch.Server
|
||||||
@@ -45,20 +46,14 @@ namespace Torch.Server
|
|||||||
|
|
||||||
private void KickButton_Click(object sender, RoutedEventArgs e)
|
private void KickButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var player = PlayerList.SelectedItem as IMyPlayer;
|
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
||||||
if (player != null)
|
_server.Multiplayer.KickPlayer(player.Key);
|
||||||
{
|
|
||||||
_server.Multiplayer.KickPlayer(player.SteamUserId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BanButton_Click(object sender, RoutedEventArgs e)
|
private void BanButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var player = PlayerList.SelectedItem as IMyPlayer;
|
var player = (KeyValuePair<ulong, PlayerViewModel>) PlayerList.SelectedItem;
|
||||||
if (player != null)
|
_server.Multiplayer.BanPlayer(player.Key);
|
||||||
{
|
|
||||||
_server.Multiplayer.BanPlayer(player.SteamUserId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,21 +10,26 @@
|
|||||||
<UserControl.DataContext>
|
<UserControl.DataContext>
|
||||||
<viewModels:PluginManagerViewModel/>
|
<viewModels:PluginManagerViewModel/>
|
||||||
</UserControl.DataContext>
|
</UserControl.DataContext>
|
||||||
<DockPanel>
|
<Grid>
|
||||||
<DockPanel>
|
<Grid.ColumnDefinitions>
|
||||||
<Button Content="Open Folder" Margin="3" DockPanel.Dock="Bottom" IsEnabled="false"></Button>
|
<ColumnDefinition Width="150"/>
|
||||||
<ListView Width="150" ItemsSource="{Binding Plugins}" SelectedItem="{Binding SelectedPlugin}" Margin="3">
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid Grid.Column="0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<ListView Grid.Row="0" ItemsSource="{Binding Plugins}" SelectedItem="{Binding SelectedPlugin}" Margin="3">
|
||||||
<ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding Name}"/>
|
<TextBlock Text="{Binding Name}"/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView.ItemTemplate>
|
||||||
</ListView>
|
</ListView>
|
||||||
</DockPanel>
|
<Button Grid.Row="1" Content="Open Folder" Margin="3" DockPanel.Dock="Bottom" IsEnabled="false"/>
|
||||||
<StackPanel Margin="3">
|
</Grid>
|
||||||
<Label Content="{Binding SelectedPlugin.Name}" FontSize="16"/>
|
<Frame Grid.Column="1" NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/>
|
||||||
<Frame NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/>
|
</Grid>
|
||||||
</StackPanel>
|
|
||||||
</DockPanel>
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
||||||
|
@@ -5,14 +5,22 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:local="clr-namespace:Torch.Server"
|
xmlns:local="clr-namespace:Torch.Server"
|
||||||
xmlns:views="clr-namespace:Torch.Server.Views"
|
xmlns:views="clr-namespace:Torch.Server.Views"
|
||||||
|
xmlns:converters="clr-namespace:Torch.Server.Views.Converters"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="Torch">
|
Title="Torch">
|
||||||
<DockPanel>
|
<Window.Resources>
|
||||||
<StackPanel DockPanel.Dock="Top" Margin="5,5,5,5" Orientation="Horizontal">
|
<converters:InverseBooleanConverter x:Key="InverseBool"/>
|
||||||
|
</Window.Resources>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Grid.Row="0" Margin="5,5,5,5" Orientation="Horizontal">
|
||||||
<Button x:Name="BtnStart" Content="Start" Height="24" Width="75" Margin="5,0,5,0"
|
<Button x:Name="BtnStart" Content="Start" Height="24" Width="75" Margin="5,0,5,0"
|
||||||
HorizontalAlignment="Left" Click="BtnStart_Click" IsDefault="True" />
|
HorizontalAlignment="Left" Click="BtnStart_Click" IsEnabled="{Binding IsRunning, Converter={StaticResource InverseBool}}"/>
|
||||||
<Button x:Name="BtnStop" Content="Stop" Height="24" Width="75" Margin="5,0,5,0" HorizontalAlignment="Left"
|
<Button x:Name="BtnStop" Content="Stop" Height="24" Width="75" Margin="5,0,5,0" HorizontalAlignment="Left"
|
||||||
Click="BtnStop_Click" IsEnabled="False" />
|
Click="BtnStop_Click" IsEnabled="{Binding IsRunning}" />
|
||||||
<Label>
|
<Label>
|
||||||
<Label.Content>
|
<Label.Content>
|
||||||
<TextBlock Text="{Binding State, StringFormat=Status: {0}}"></TextBlock>
|
<TextBlock Text="{Binding State, StringFormat=Status: {0}}"></TextBlock>
|
||||||
@@ -29,22 +37,34 @@
|
|||||||
</Label.Content>
|
</Label.Content>
|
||||||
</Label>
|
</Label>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<TabControl x:Name="TabControl" DockPanel.Dock="Bottom" Margin="5,0,5,5">
|
<TabControl Grid.Row="1" Height="Auto" x:Name="TabControl" Margin="5,0,5,5">
|
||||||
<TabItem Header="Configuration">
|
<TabItem Header="Configuration">
|
||||||
<DockPanel>
|
<Grid>
|
||||||
<DockPanel DockPanel.Dock="Top">
|
<Grid.RowDefinitions>
|
||||||
<Label Content="Instance Path: " Margin="3" />
|
<RowDefinition Height="Auto"/>
|
||||||
<TextBox x:Name="InstancePathBox" Margin="3" Height="20"
|
<RowDefinition/>
|
||||||
TextChanged="InstancePathBox_OnTextChanged" IsEnabled="False" />
|
</Grid.RowDefinitions>
|
||||||
</DockPanel>
|
<Grid Grid.Row="0">
|
||||||
<views:ConfigControl x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" />
|
<Grid.ColumnDefinitions>
|
||||||
</DockPanel>
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Label Grid.Column="0" Content="Instance Path: " Margin="3" />
|
||||||
|
<TextBox Grid.Column="1" x:Name="InstancePathBox" Margin="3" Height="20"
|
||||||
|
LostKeyboardFocus="InstancePathBox_OnLostKeyboardFocus" />
|
||||||
|
</Grid>
|
||||||
|
<views:ConfigControl Grid.Row="1" x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" IsEnabled="{Binding IsRunning, Converter={StaticResource InverseBool}}"/>
|
||||||
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Chat/Players">
|
<TabItem Header="Chat/Players">
|
||||||
<DockPanel>
|
<Grid>
|
||||||
<local:PlayerListControl x:Name="PlayerList" DockPanel.Dock="Right" Width="250" IsEnabled="False" />
|
<Grid.ColumnDefinitions>
|
||||||
<local:ChatControl x:Name="Chat" IsEnabled="False" />
|
<ColumnDefinition/>
|
||||||
</DockPanel>
|
<ColumnDefinition Width="250"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<local:ChatControl Grid.Column="0" x:Name="Chat" IsEnabled="{Binding IsRunning}"/>
|
||||||
|
<local:PlayerListControl Grid.Column="1" x:Name="PlayerList" DockPanel.Dock="Right"/>
|
||||||
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Entity Manager">
|
<TabItem Header="Entity Manager">
|
||||||
<views:EntitiesControl />
|
<views:EntitiesControl />
|
||||||
@@ -53,5 +73,5 @@
|
|||||||
<views:PluginsControl x:Name="Plugins" />
|
<views:PluginsControl x:Name="Plugins" />
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
</DockPanel>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
@@ -19,6 +19,7 @@ using System.Windows.Navigation;
|
|||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Sandbox;
|
using Sandbox;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using Torch.Server.Managers;
|
||||||
using Timer = System.Timers.Timer;
|
using Timer = System.Timers.Timer;
|
||||||
|
|
||||||
namespace Torch.Server
|
namespace Torch.Server
|
||||||
@@ -47,6 +48,7 @@ namespace Torch.Server
|
|||||||
Chat.BindServer(server);
|
Chat.BindServer(server);
|
||||||
PlayerList.BindServer(server);
|
PlayerList.BindServer(server);
|
||||||
Plugins.BindServer(server);
|
Plugins.BindServer(server);
|
||||||
|
LoadConfig((TorchConfig)server.Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadConfig(TorchConfig config)
|
public void LoadConfig(TorchConfig config)
|
||||||
@@ -57,7 +59,6 @@ namespace Torch.Server
|
|||||||
_config = config;
|
_config = config;
|
||||||
Dispatcher.Invoke(() =>
|
Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
ConfigControl.LoadDedicatedConfig(config);
|
|
||||||
InstancePathBox.Text = config.InstancePath;
|
InstancePathBox.Text = config.InstancePath;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -65,22 +66,11 @@ namespace Torch.Server
|
|||||||
private void BtnStart_Click(object sender, RoutedEventArgs e)
|
private void BtnStart_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_config.Save();
|
_config.Save();
|
||||||
Chat.IsEnabled = true;
|
|
||||||
PlayerList.IsEnabled = true;
|
|
||||||
((Button) sender).IsEnabled = false;
|
|
||||||
BtnStop.IsEnabled = true;
|
|
||||||
ConfigControl.SaveConfig();
|
|
||||||
new Thread(_server.Start).Start();
|
new Thread(_server.Start).Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BtnStop_Click(object sender, RoutedEventArgs e)
|
private void BtnStop_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_config.Save();
|
|
||||||
Chat.IsEnabled = false;
|
|
||||||
PlayerList.IsEnabled = false;
|
|
||||||
((Button) sender).IsEnabled = false;
|
|
||||||
//HACK: Uncomment when restarting is possible.
|
|
||||||
//BtnStart.IsEnabled = true;
|
|
||||||
_server.Stop();
|
_server.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,13 +91,15 @@ namespace Torch.Server
|
|||||||
//MySandboxGame.Static.Invoke(MySandboxGame.ReloadDedicatedServerSession); use i
|
//MySandboxGame.Static.Invoke(MySandboxGame.ReloadDedicatedServerSession); use i
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InstancePathBox_OnTextChanged(object sender, TextChangedEventArgs e)
|
private void InstancePathBox_OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||||
{
|
{
|
||||||
var name = ((TextBox)sender).Text;
|
var name = ((TextBox)sender).Text;
|
||||||
|
|
||||||
_config.InstancePath = name;
|
if (!Directory.Exists(name))
|
||||||
|
return;
|
||||||
|
|
||||||
LoadConfig(_config);
|
_config.InstancePath = name;
|
||||||
|
_server.GetManager<InstanceManager>().LoadInstance(_config.InstancePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="Extended.Wpf.Toolkit" version="2.9" targetFramework="net461" />
|
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
||||||
<package id="NLog" version="4.4.1" targetFramework="net461" />
|
<package id="NLog" version="4.4.11" targetFramework="net461" />
|
||||||
</packages>
|
</packages>
|
@@ -10,7 +10,7 @@ using VRage.Network;
|
|||||||
|
|
||||||
namespace Torch
|
namespace Torch
|
||||||
{
|
{
|
||||||
public struct ChatMessage : IChatMessage
|
public class ChatMessage : IChatMessage
|
||||||
{
|
{
|
||||||
public DateTime Timestamp { get; }
|
public DateTime Timestamp { get; }
|
||||||
public ulong SteamId { get; }
|
public ulong SteamId { get; }
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
@@ -10,15 +9,18 @@ using System.Windows.Threading;
|
|||||||
|
|
||||||
namespace Torch
|
namespace Torch
|
||||||
{
|
{
|
||||||
|
[Obsolete("Use ObservableList<T>.")]
|
||||||
public class MTObservableCollection<T> : ObservableCollection<T>
|
public class MTObservableCollection<T> : ObservableCollection<T>
|
||||||
{
|
{
|
||||||
public override event NotifyCollectionChangedEventHandler CollectionChanged;
|
public override event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||||
|
|
||||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
|
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
|
||||||
if (collectionChanged != null)
|
if (collectionChanged != null)
|
||||||
foreach (NotifyCollectionChangedEventHandler nh in collectionChanged.GetInvocationList())
|
foreach (var del in collectionChanged.GetInvocationList())
|
||||||
{
|
{
|
||||||
|
var nh = (NotifyCollectionChangedEventHandler)del;
|
||||||
var dispObj = nh.Target as DispatcherObject;
|
var dispObj = nh.Target as DispatcherObject;
|
||||||
|
|
||||||
var dispatcher = dispObj?.Dispatcher;
|
var dispatcher = dispObj?.Dispatcher;
|
||||||
|
63
Torch/Collections/ObservableDictionary.cs
Normal file
63
Torch/Collections/ObservableDictionary.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace Torch.Collections
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class ObservableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public new void Add(TKey key, TValue value)
|
||||||
|
{
|
||||||
|
base.Add(key, value);
|
||||||
|
var kv = new KeyValuePair<TKey, TValue>(key, value);
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, kv));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public new bool Remove(TKey key)
|
||||||
|
{
|
||||||
|
if (!ContainsKey(key))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var kv = new KeyValuePair<TKey, TValue>(key, this[key]);
|
||||||
|
base.Remove(key);
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, kv));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
|
||||||
|
if (collectionChanged != null)
|
||||||
|
foreach (NotifyCollectionChangedEventHandler nh in collectionChanged.GetInvocationList())
|
||||||
|
{
|
||||||
|
var dispObj = nh.Target as DispatcherObject;
|
||||||
|
|
||||||
|
var dispatcher = dispObj?.Dispatcher;
|
||||||
|
if (dispatcher != null && !dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
dispatcher.BeginInvoke(
|
||||||
|
(Action)(() => nh.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
|
||||||
|
DispatcherPriority.DataBind);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nh.Invoke(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
}
|
||||||
|
}
|
186
Torch/Collections/ObservableList.cs
Normal file
186
Torch/Collections/ObservableList.cs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace Torch
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An observable version of <see cref="List{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class ObservableList<T> : IList<T>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private List<T> _internalList = new List<T>();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_internalList.Clear();
|
||||||
|
OnPropertyChanged(nameof(Count));
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Contains(T item)
|
||||||
|
{
|
||||||
|
return _internalList.Contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void CopyTo(T[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
_internalList.CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Remove(T item)
|
||||||
|
{
|
||||||
|
var oldIndex = _internalList.IndexOf(item);
|
||||||
|
if (!_internalList.Remove(item))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(Count));
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, oldIndex));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int Count => _internalList.Count;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
_internalList.Add(item);
|
||||||
|
OnPropertyChanged(nameof(Count));
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, Count - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int IndexOf(T item) => _internalList.IndexOf(item);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Insert(int index, T item)
|
||||||
|
{
|
||||||
|
_internalList.Insert(index, item);
|
||||||
|
OnPropertyChanged(nameof(Count));
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts an item in order based on the provided selector and comparer. This will only work properly on a pre-sorted list.
|
||||||
|
/// </summary>
|
||||||
|
public void Insert<TKey>(T item, Func<T, TKey> selector, IComparer<TKey> comparer = null)
|
||||||
|
{
|
||||||
|
comparer = comparer ?? Comparer<TKey>.Default;
|
||||||
|
var key1 = selector(item);
|
||||||
|
for (var i = 0; i < _internalList.Count; i++)
|
||||||
|
{
|
||||||
|
var key2 = selector(_internalList[i]);
|
||||||
|
if (comparer.Compare(key1, key2) < 1)
|
||||||
|
{
|
||||||
|
Insert(i, item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
var old = this[index];
|
||||||
|
_internalList.RemoveAt(index);
|
||||||
|
OnPropertyChanged(nameof(Count));
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get => _internalList[index];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var old = _internalList[index];
|
||||||
|
if (old.Equals(value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_internalList[index] = value;
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, old, index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sorts the list using the given selector and comparer./>
|
||||||
|
/// </summary>
|
||||||
|
public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer = null)
|
||||||
|
{
|
||||||
|
comparer = comparer ?? Comparer<TKey>.Default;
|
||||||
|
var sortedItems = _internalList.OrderBy(selector, comparer).ToList();
|
||||||
|
|
||||||
|
_internalList = sortedItems;
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all items that satisfy the given condition.
|
||||||
|
/// </summary>
|
||||||
|
public void RemoveWhere(Func<T, bool> condition)
|
||||||
|
{
|
||||||
|
for (var i = Count - 1; i > 0; i--)
|
||||||
|
{
|
||||||
|
if (condition?.Invoke(this[i]) ?? false)
|
||||||
|
RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var collectionChanged = CollectionChanged;
|
||||||
|
if (collectionChanged != null)
|
||||||
|
foreach (var del in collectionChanged.GetInvocationList())
|
||||||
|
{
|
||||||
|
var nh = (NotifyCollectionChangedEventHandler)del;
|
||||||
|
var dispObj = nh.Target as DispatcherObject;
|
||||||
|
|
||||||
|
var dispatcher = dispObj?.Dispatcher;
|
||||||
|
if (dispatcher != null && !dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
dispatcher.BeginInvoke(() => nh.Invoke(this, e), DispatcherPriority.DataBind);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nh.Invoke(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _internalList.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return ((IEnumerable)_internalList).GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,17 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Torch
|
namespace Torch
|
||||||
{
|
{
|
||||||
public class CommandLine
|
/// <summary>
|
||||||
|
/// Base class that adds tools for setting type properties through the command line.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class CommandLine
|
||||||
{
|
{
|
||||||
private readonly string _argPrefix;
|
private readonly string _argPrefix;
|
||||||
private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>();
|
private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>();
|
||||||
|
|
||||||
public CommandLine(string argPrefix = "-")
|
protected CommandLine(string argPrefix = "-")
|
||||||
{
|
{
|
||||||
_argPrefix = argPrefix;
|
_argPrefix = argPrefix;
|
||||||
foreach (var prop in GetType().GetProperties())
|
foreach (var prop in GetType().GetProperties())
|
||||||
|
@@ -8,6 +8,7 @@ using NLog;
|
|||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Plugins;
|
using Torch.API.Plugins;
|
||||||
using Torch.Commands.Permissions;
|
using Torch.Commands.Permissions;
|
||||||
|
using VRage.Game;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
namespace Torch.Commands
|
namespace Torch.Commands
|
||||||
@@ -26,6 +27,7 @@ namespace Torch.Commands
|
|||||||
private readonly MethodInfo _method;
|
private readonly MethodInfo _method;
|
||||||
private ParameterInfo[] _parameters;
|
private ParameterInfo[] _parameters;
|
||||||
private int? _requiredParamCount;
|
private int? _requiredParamCount;
|
||||||
|
private static readonly Logger Log = LogManager.GetLogger(nameof(Command));
|
||||||
|
|
||||||
public Command(ITorchPlugin plugin, MethodInfo commandMethod)
|
public Command(ITorchPlugin plugin, MethodInfo commandMethod)
|
||||||
{
|
{
|
||||||
@@ -83,6 +85,8 @@ namespace Torch.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool TryInvoke(CommandContext context)
|
public bool TryInvoke(CommandContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var parameters = new object[_parameters.Length];
|
var parameters = new object[_parameters.Length];
|
||||||
|
|
||||||
@@ -110,6 +114,14 @@ namespace Torch.Commands
|
|||||||
_method.Invoke(moduleInstance, parameters);
|
_method.Invoke(moduleInstance, parameters);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
context.Respond(e.Message, "Error", MyFontEnum.Red);
|
||||||
|
Log.Error($"Command '{SyntaxHelp}' from '{Plugin.Name ?? "Torch"}' threw an exception. Args: {string.Join(", ", context.Args)}");
|
||||||
|
Log.Error(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
|
@@ -21,7 +21,7 @@ namespace Torch.Commands
|
|||||||
public CommandTree Commands { get; set; } = new CommandTree();
|
public CommandTree Commands { get; set; } = new CommandTree();
|
||||||
private Logger _log = LogManager.GetLogger(nameof(CommandManager));
|
private Logger _log = LogManager.GetLogger(nameof(CommandManager));
|
||||||
|
|
||||||
public CommandManager(ITorchBase torch, char prefix = '/') : base(torch)
|
public CommandManager(ITorchBase torch, char prefix = '!') : base(torch)
|
||||||
{
|
{
|
||||||
Prefix = prefix;
|
Prefix = prefix;
|
||||||
}
|
}
|
||||||
@@ -76,6 +76,8 @@ namespace Torch.Commands
|
|||||||
{
|
{
|
||||||
var cmdText = new string(message.Skip(1).ToArray());
|
var cmdText = new string(message.Skip(1).ToArray());
|
||||||
var command = Commands.GetCommand(cmdText, out string argText);
|
var command = Commands.GetCommand(cmdText, out string argText);
|
||||||
|
if (command == null)
|
||||||
|
return null;
|
||||||
var cmdPath = string.Join(".", command.Path);
|
var cmdPath = string.Join(".", command.Path);
|
||||||
|
|
||||||
var splitArgs = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();
|
var splitArgs = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();
|
||||||
|
@@ -14,6 +14,7 @@ namespace Torch.Commands
|
|||||||
public class TorchCommands : CommandModule
|
public class TorchCommands : CommandModule
|
||||||
{
|
{
|
||||||
[Command("help", "Displays help for a command")]
|
[Command("help", "Displays help for a command")]
|
||||||
|
[Permission(MyPromoteLevel.None)]
|
||||||
public void Help()
|
public void Help()
|
||||||
{
|
{
|
||||||
var commandManager = ((TorchBase)Context.Torch).Commands;
|
var commandManager = ((TorchBase)Context.Torch).Commands;
|
||||||
@@ -39,12 +40,48 @@ namespace Torch.Commands
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var topNodeNames = commandManager.Commands.Root.Select(x => x.Key);
|
Context.Respond($"Use the {commandManager.Prefix}longhelp command and check your Comms menu for a full list of commands.");
|
||||||
Context.Respond($"Top level commands: {string.Join(", ", topNodeNames)}");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")]
|
||||||
|
public void LongHelp()
|
||||||
|
{
|
||||||
|
var commandManager = Context.Torch.GetManager<CommandManager>();
|
||||||
|
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($"Syntax: {command.SyntaxHelp}");
|
||||||
|
sb.Append(command.HelpText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Subcommands.Count() != 0)
|
||||||
|
sb.Append($"\nSubcommands: {string.Join(", ", children)}");
|
||||||
|
|
||||||
|
Context.Respond(sb.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder("Available commands:\n");
|
||||||
|
foreach (var command in commandManager.Commands.WalkTree())
|
||||||
|
{
|
||||||
|
if (command.IsCommand)
|
||||||
|
sb.AppendLine($"{command.Command.SyntaxHelp}\n {command.Command.HelpText}");
|
||||||
|
}
|
||||||
|
Context.Respond(sb.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("ver", "Shows the running Torch version.")]
|
[Command("ver", "Shows the running Torch version.")]
|
||||||
|
[Permission(MyPromoteLevel.None)]
|
||||||
public void Version()
|
public void Version()
|
||||||
{
|
{
|
||||||
var ver = Context.Torch.TorchVersion;
|
var ver = Context.Torch.TorchVersion;
|
||||||
@@ -52,6 +89,7 @@ namespace Torch.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Command("plugins", "Lists the currently loaded plugins.")]
|
[Command("plugins", "Lists the currently loaded plugins.")]
|
||||||
|
[Permission(MyPromoteLevel.None)]
|
||||||
public void Plugins()
|
public void Plugins()
|
||||||
{
|
{
|
||||||
var plugins = Context.Torch.Plugins.Select(p => p.Name);
|
var plugins = Context.Torch.Plugins.Select(p => p.Name);
|
||||||
@@ -59,11 +97,32 @@ namespace Torch.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Command("stop", "Stops the server.")]
|
[Command("stop", "Stops the server.")]
|
||||||
[Permission(MyPromoteLevel.Admin)]
|
public void Stop(bool save = true)
|
||||||
public void Stop()
|
|
||||||
{
|
{
|
||||||
Context.Respond("Stopping server.");
|
Context.Respond("Stopping server.");
|
||||||
|
if (save)
|
||||||
|
Context.Torch.Save(Context.Player?.IdentityId ?? 0).Wait();
|
||||||
Context.Torch.Stop();
|
Context.Torch.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command("restart", "Restarts the server.")]
|
||||||
|
public void Restart(bool save = true)
|
||||||
|
{
|
||||||
|
Context.Respond("Restarting server.");
|
||||||
|
if (save)
|
||||||
|
Context.Torch.Save(Context.Player?.IdentityId ?? 0).Wait();
|
||||||
|
Context.Torch.Restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a save of the game.
|
||||||
|
/// Caller id defaults to 0 in the case of triggering the chat command from server.
|
||||||
|
/// </summary>
|
||||||
|
[Command("save", "Saves the game.")]
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
Context.Respond("Saving game.");
|
||||||
|
Context.Torch.Save(Context.Player?.IdentityId ?? 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
17
Torch/DispatcherExtensions.cs
Normal file
17
Torch/DispatcherExtensions.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace Torch
|
||||||
|
{
|
||||||
|
public static class DispatcherExtensions
|
||||||
|
{
|
||||||
|
public static DispatcherOperation BeginInvoke(this Dispatcher dispatcher, Action action, DispatcherPriority priority = DispatcherPriority.Normal)
|
||||||
|
{
|
||||||
|
return dispatcher.BeginInvoke(priority, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
Torch/Managers/FilesystemManager.cs
Normal file
51
Torch/Managers/FilesystemManager.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Torch.API;
|
||||||
|
|
||||||
|
namespace Torch.Managers
|
||||||
|
{
|
||||||
|
public class FilesystemManager : Manager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Temporary directory for Torch that is cleared every time the program is started.
|
||||||
|
/// </summary>
|
||||||
|
public string TempDirectory { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Directory that contains the current Torch assemblies.
|
||||||
|
/// </summary>
|
||||||
|
public string TorchDirectory { get; }
|
||||||
|
|
||||||
|
public FilesystemManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
var temp = Path.Combine(Path.GetTempPath(), "Torch");
|
||||||
|
TempDirectory = Directory.CreateDirectory(temp).FullName;
|
||||||
|
var torch = new FileInfo(typeof(FilesystemManager).Assembly.Location).Directory.FullName;
|
||||||
|
TorchDirectory = torch;
|
||||||
|
|
||||||
|
ClearTemp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearTemp()
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.GetFiles(TempDirectory, "*", SearchOption.AllDirectories))
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Move the given file (if it exists) to a temporary directory that will be cleared the next time the application starts.
|
||||||
|
/// </summary>
|
||||||
|
public void SoftDelete(string file)
|
||||||
|
{
|
||||||
|
if (!File.Exists(file))
|
||||||
|
return;
|
||||||
|
var rand = Path.GetRandomFileName();
|
||||||
|
var dest = Path.Combine(TempDirectory, rand);
|
||||||
|
File.Move(file, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -16,13 +16,14 @@ using NLog;
|
|||||||
using Torch;
|
using Torch;
|
||||||
using Sandbox;
|
using Sandbox;
|
||||||
using Sandbox.Engine.Multiplayer;
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Game.Entities.Character;
|
||||||
using Sandbox.Game.Multiplayer;
|
using Sandbox.Game.Multiplayer;
|
||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
using Sandbox.ModAPI;
|
using Sandbox.ModAPI;
|
||||||
using SharpDX.Toolkit.Collections;
|
|
||||||
using SteamSDK;
|
using SteamSDK;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
|
using Torch.Collections;
|
||||||
using Torch.Commands;
|
using Torch.Commands;
|
||||||
using Torch.ViewModels;
|
using Torch.ViewModels;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
@@ -33,20 +34,21 @@ using VRage.Utils;
|
|||||||
|
|
||||||
namespace Torch.Managers
|
namespace Torch.Managers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Provides a proxy to the game's multiplayer-related functions.
|
|
||||||
/// </summary>
|
|
||||||
public class MultiplayerManager : Manager, IMultiplayerManager
|
public class MultiplayerManager : Manager, IMultiplayerManager
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public event Action<IPlayer> PlayerJoined;
|
public event Action<IPlayer> PlayerJoined;
|
||||||
|
/// <inheritdoc />
|
||||||
public event Action<IPlayer> PlayerLeft;
|
public event Action<IPlayer> PlayerLeft;
|
||||||
|
/// <inheritdoc />
|
||||||
public event MessageReceivedDel MessageReceived;
|
public event MessageReceivedDel MessageReceived;
|
||||||
|
|
||||||
public MTObservableCollection<IChatMessage> ChatHistory { get; } = new MTObservableCollection<IChatMessage>();
|
public IList<IChatMessage> ChatHistory { get; } = new ObservableList<IChatMessage>();
|
||||||
public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>();
|
public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>();
|
||||||
public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer;
|
public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer;
|
||||||
private static readonly Logger _log = LogManager.GetLogger(nameof(MultiplayerManager));
|
private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager));
|
||||||
private static readonly Logger _chatLog = LogManager.GetLogger("Chat");
|
private static readonly Logger ChatLog = LogManager.GetLogger("Chat");
|
||||||
private Dictionary<MyPlayer.PlayerId, MyPlayer> _onlinePlayers;
|
private Dictionary<MyPlayer.PlayerId, MyPlayer> _onlinePlayers;
|
||||||
|
|
||||||
internal MultiplayerManager(ITorchBase torch) : base(torch)
|
internal MultiplayerManager(ITorchBase torch) : base(torch)
|
||||||
@@ -65,12 +67,14 @@ namespace Torch.Managers
|
|||||||
{
|
{
|
||||||
var message = ChatMessage.FromChatMsg(msg);
|
var message = ChatMessage.FromChatMsg(msg);
|
||||||
ChatHistory.Add(message);
|
ChatHistory.Add(message);
|
||||||
_chatLog.Info($"{message.Name}: {message.Message}");
|
ChatLog.Info($"{message.Name}: {message.Message}");
|
||||||
MessageReceived?.Invoke(message, ref sendToOthers);
|
MessageReceived?.Invoke(message, ref sendToOthers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void BanPlayer(ulong steamId, bool banned = true)
|
public void BanPlayer(ulong steamId, bool banned = true)
|
||||||
{
|
{
|
||||||
Torch.Invoke(() =>
|
Torch.Invoke(() =>
|
||||||
@@ -81,12 +85,14 @@ namespace Torch.Managers
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IMyPlayer GetPlayerByName(string name)
|
public IMyPlayer GetPlayerByName(string name)
|
||||||
{
|
{
|
||||||
ValidateOnlinePlayersList();
|
ValidateOnlinePlayersList();
|
||||||
return _onlinePlayers.FirstOrDefault(x => x.Value.DisplayName == name).Value;
|
return _onlinePlayers.FirstOrDefault(x => x.Value.DisplayName == name).Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IMyPlayer GetPlayerBySteamId(ulong steamId)
|
public IMyPlayer GetPlayerBySteamId(ulong steamId)
|
||||||
{
|
{
|
||||||
ValidateOnlinePlayersList();
|
ValidateOnlinePlayersList();
|
||||||
@@ -94,27 +100,44 @@ namespace Torch.Managers
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ulong GetSteamId(long identityId)
|
||||||
|
{
|
||||||
|
foreach (var kv in _onlinePlayers)
|
||||||
|
{
|
||||||
|
if (kv.Value.Identity.IdentityId == identityId)
|
||||||
|
return kv.Key.SteamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string GetSteamUsername(ulong steamId)
|
public string GetSteamUsername(ulong steamId)
|
||||||
{
|
{
|
||||||
return MyMultiplayer.Static.GetMemberName(steamId);
|
return MyMultiplayer.Static.GetMemberName(steamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Send a message in chat.
|
|
||||||
/// </summary>
|
|
||||||
public void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Red)
|
public void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Red)
|
||||||
{
|
{
|
||||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", message));
|
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, message));
|
||||||
var commands = Torch.GetManager<CommandManager>();
|
var commands = Torch.GetManager<CommandManager>();
|
||||||
if (commands.IsCommand(message))
|
if (commands.IsCommand(message))
|
||||||
{
|
{
|
||||||
var response = commands.HandleCommandFromServer(message);
|
var response = commands.HandleCommandFromServer(message);
|
||||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
|
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, response));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var msg = new ScriptedChatMsg { Author = author, Font = font, Target = playerId, Text = message };
|
var msg = new ScriptedChatMsg { Author = author, Font = font, Target = playerId, Text = message };
|
||||||
MyMultiplayerBase.SendScriptedChatMessage(ref msg);
|
MyMultiplayerBase.SendScriptedChatMessage(ref msg);
|
||||||
|
var character = MySession.Static.Players.TryGetIdentity(playerId)?.Character;
|
||||||
|
var steamId = GetSteamId(playerId);
|
||||||
|
if (character == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var addToGlobalHistoryMethod = typeof(MyCharacter).GetMethod("OnGlobalMessageSuccess", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
Torch.GetManager<NetworkManager>().RaiseEvent(addToGlobalHistoryMethod, character, steamId, steamId, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +149,7 @@ namespace Torch.Managers
|
|||||||
|
|
||||||
private void OnSessionLoaded()
|
private void OnSessionLoaded()
|
||||||
{
|
{
|
||||||
|
Log.Info("Initializing Steam auth");
|
||||||
MyMultiplayer.Static.ClientKicked += OnClientKicked;
|
MyMultiplayer.Static.ClientKicked += OnClientKicked;
|
||||||
MyMultiplayer.Static.ClientLeft += OnClientLeft;
|
MyMultiplayer.Static.ClientLeft += OnClientLeft;
|
||||||
|
|
||||||
@@ -137,6 +161,7 @@ namespace Torch.Managers
|
|||||||
SteamServerAPI.Instance.GameServer.UserGroupStatus += UserGroupStatus;
|
SteamServerAPI.Instance.GameServer.UserGroupStatus += UserGroupStatus;
|
||||||
_members = (List<ulong>)typeof(MyDedicatedServerBase).GetField("m_members", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
_members = (List<ulong>)typeof(MyDedicatedServerBase).GetField("m_members", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
||||||
_waitingForGroup = (HashSet<ulong>)typeof(MyDedicatedServerBase).GetField("m_waitingForGroup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
_waitingForGroup = (HashSet<ulong>)typeof(MyDedicatedServerBase).GetField("m_waitingForGroup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
||||||
|
Log.Info("Steam auth initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClientKicked(ulong steamId)
|
private void OnClientKicked(ulong steamId)
|
||||||
@@ -146,9 +171,11 @@ namespace Torch.Managers
|
|||||||
|
|
||||||
private void OnClientLeft(ulong steamId, ChatMemberStateChangeEnum stateChange)
|
private void OnClientLeft(ulong steamId, ChatMemberStateChangeEnum stateChange)
|
||||||
{
|
{
|
||||||
_log.Info($"{GetSteamUsername(steamId)} disconnected ({(ConnectionState)stateChange}).");
|
|
||||||
Players.TryGetValue(steamId, out PlayerViewModel vm);
|
Players.TryGetValue(steamId, out PlayerViewModel vm);
|
||||||
PlayerLeft?.Invoke(vm ?? new PlayerViewModel(steamId));
|
if (vm == null)
|
||||||
|
vm = new PlayerViewModel(steamId);
|
||||||
|
Log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}.");
|
||||||
|
PlayerLeft?.Invoke(vm);
|
||||||
Players.Remove(steamId);
|
Players.Remove(steamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +201,7 @@ namespace Torch.Managers
|
|||||||
if (handle.Method.Name == "GameServer_ValidateAuthTicketResponse")
|
if (handle.Method.Name == "GameServer_ValidateAuthTicketResponse")
|
||||||
{
|
{
|
||||||
SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse -= handle as ValidateAuthTicketResponse;
|
SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse -= handle as ValidateAuthTicketResponse;
|
||||||
|
Log.Debug("Removed GameServer_ValidateAuthTicketResponse");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,6 +214,7 @@ namespace Torch.Managers
|
|||||||
if (handle.Method.Name == "GameServer_UserGroupStatus")
|
if (handle.Method.Name == "GameServer_UserGroupStatus")
|
||||||
{
|
{
|
||||||
SteamServerAPI.Instance.GameServer.UserGroupStatus -= handle as UserGroupStatus;
|
SteamServerAPI.Instance.GameServer.UserGroupStatus -= handle as UserGroupStatus;
|
||||||
|
Log.Debug("Removed GameServer_UserGroupStatus");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,16 +223,16 @@ namespace Torch.Managers
|
|||||||
//Largely copied from SE
|
//Largely copied from SE
|
||||||
private void ValidateAuthTicketResponse(ulong steamID, AuthSessionResponseEnum response, ulong ownerSteamID)
|
private void ValidateAuthTicketResponse(ulong steamID, AuthSessionResponseEnum response, ulong ownerSteamID)
|
||||||
{
|
{
|
||||||
_log.Info($"Server ValidateAuthTicketResponse ({response}), owner: {ownerSteamID}");
|
Log.Info($"Server ValidateAuthTicketResponse ({response}), owner: {ownerSteamID}");
|
||||||
|
|
||||||
if (steamID != ownerSteamID)
|
if (steamID != ownerSteamID)
|
||||||
{
|
{
|
||||||
_log.Info($"User {steamID} is using a game owned by {ownerSteamID}. Tracking...");
|
Log.Info($"User {steamID} is using a game owned by {ownerSteamID}. Tracking...");
|
||||||
_gameOwnerIds[steamID] = ownerSteamID;
|
_gameOwnerIds[steamID] = ownerSteamID;
|
||||||
|
|
||||||
if (MySandboxGame.ConfigDedicated.Banned.Contains(ownerSteamID))
|
if (MySandboxGame.ConfigDedicated.Banned.Contains(ownerSteamID))
|
||||||
{
|
{
|
||||||
_log.Info($"Game owner {ownerSteamID} is banned. Banning and rejecting client {steamID}...");
|
Log.Info($"Game owner {ownerSteamID} is banned. Banning and rejecting client {steamID}...");
|
||||||
UserRejected(steamID, JoinResult.BannedByAdmins);
|
UserRejected(steamID, JoinResult.BannedByAdmins);
|
||||||
BanPlayer(steamID);
|
BanPlayer(steamID);
|
||||||
}
|
}
|
||||||
@@ -299,7 +328,8 @@ namespace Torch.Managers
|
|||||||
private void UserAccepted(ulong steamId)
|
private void UserAccepted(ulong steamId)
|
||||||
{
|
{
|
||||||
typeof(MyDedicatedServerBase).GetMethod("UserAccepted", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId});
|
typeof(MyDedicatedServerBase).GetMethod("UserAccepted", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId});
|
||||||
var vm = new PlayerViewModel(steamId);
|
var vm = new PlayerViewModel(steamId) {State = ConnectionState.Connected};
|
||||||
|
Log.Info($"Player {vm.Name} joined ({vm.SteamId})");
|
||||||
Players.Add(steamId, vm);
|
Players.Add(steamId, vm);
|
||||||
PlayerJoined?.Invoke(vm);
|
PlayerJoined?.Invoke(vm);
|
||||||
}
|
}
|
||||||
|
@@ -64,6 +64,11 @@ namespace Torch.Managers
|
|||||||
/// Loads the network intercept system
|
/// Loads the network intercept system
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override void Init()
|
public override void Init()
|
||||||
|
{
|
||||||
|
Torch.SessionLoaded += OnSessionLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSessionLoaded()
|
||||||
{
|
{
|
||||||
if (_init)
|
if (_init)
|
||||||
return;
|
return;
|
||||||
@@ -306,7 +311,7 @@ namespace Torch.Managers
|
|||||||
var parameters = method.GetParameters();
|
var parameters = method.GetParameters();
|
||||||
for (var i = 0; i < parameters.Length; i++)
|
for (var i = 0; i < parameters.Length; i++)
|
||||||
{
|
{
|
||||||
if (argTypes[i] != parameters[i].ParameterType)
|
if (argTypes[i + 1] != parameters[i].ParameterType)
|
||||||
throw new TypeLoadException($"Type mismatch on method parameters. Expected {string.Join(", ", parameters.Select(p => p.ParameterType.ToString()))} got {string.Join(", ", argTypes.Select(t => t.ToString()))}");
|
throw new TypeLoadException($"Type mismatch on method parameters. Expected {string.Join(", ", parameters.Select(p => p.ParameterType.ToString()))} got {string.Join(", ", argTypes.Select(t => t.ToString()))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,42 +5,30 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Sandbox;
|
|
||||||
using Sandbox.ModAPI;
|
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
using Torch.API.Plugins;
|
using Torch.API.Plugins;
|
||||||
using Torch.Commands;
|
using Torch.Commands;
|
||||||
using Torch.Managers;
|
|
||||||
using Torch.Updater;
|
|
||||||
using VRage.Plugins;
|
|
||||||
using VRage.Collections;
|
using VRage.Collections;
|
||||||
using VRage.Library.Collections;
|
|
||||||
|
|
||||||
namespace Torch.Managers
|
namespace Torch.Managers
|
||||||
{
|
{
|
||||||
public class PluginManager : IPluginManager
|
/// <inheritdoc />
|
||||||
|
public class PluginManager : Manager, IPluginManager
|
||||||
{
|
{
|
||||||
private readonly ITorchBase _torch;
|
|
||||||
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
|
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
|
||||||
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
||||||
|
private UpdateManager _updateManager;
|
||||||
|
|
||||||
public List<ITorchPlugin> Plugins { get; } = new List<ITorchPlugin>();
|
/// <inheritdoc />
|
||||||
|
public IList<ITorchPlugin> Plugins { get; } = new ObservableList<ITorchPlugin>();
|
||||||
|
|
||||||
public float LastUpdateMs => _lastUpdateMs;
|
public event Action<IList<ITorchPlugin>> PluginsLoaded;
|
||||||
private volatile float _lastUpdateMs;
|
|
||||||
|
|
||||||
public event Action<List<ITorchPlugin>> PluginsLoaded;
|
public PluginManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
|
||||||
public PluginManager(ITorchBase torch)
|
|
||||||
{
|
{
|
||||||
_torch = torch;
|
|
||||||
|
|
||||||
if (!Directory.Exists(PluginDir))
|
if (!Directory.Exists(PluginDir))
|
||||||
Directory.CreateDirectory(PluginDir);
|
Directory.CreateDirectory(PluginDir);
|
||||||
}
|
}
|
||||||
@@ -50,11 +38,8 @@ namespace Torch.Managers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdatePlugins()
|
public void UpdatePlugins()
|
||||||
{
|
{
|
||||||
var s = Stopwatch.StartNew();
|
|
||||||
foreach (var plugin in Plugins)
|
foreach (var plugin in Plugins)
|
||||||
plugin.Update();
|
plugin.Update();
|
||||||
s.Stop();
|
|
||||||
_lastUpdateMs = (float)s.Elapsed.TotalMilliseconds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -70,40 +55,42 @@ namespace Torch.Managers
|
|||||||
|
|
||||||
private void DownloadPlugins()
|
private void DownloadPlugins()
|
||||||
{
|
{
|
||||||
_log.Info("Downloading plugins");
|
|
||||||
var updater = new PluginUpdater(this);
|
|
||||||
|
|
||||||
var folders = Directory.GetDirectories(PluginDir);
|
var folders = Directory.GetDirectories(PluginDir);
|
||||||
var taskList = new List<Task>();
|
var taskList = new List<Task>();
|
||||||
if (_torch.Config.RedownloadPlugins)
|
|
||||||
_log.Warn("Force downloading all plugins because the RedownloadPlugins flag is set in the config");
|
//Copy list because we don't want to modify the config.
|
||||||
|
var toDownload = Torch.Config.Plugins.ToList();
|
||||||
|
|
||||||
foreach (var folder in folders)
|
foreach (var folder in folders)
|
||||||
{
|
{
|
||||||
var manifestPath = Path.Combine(folder, "manifest.xml");
|
var manifestPath = Path.Combine(folder, "manifest.xml");
|
||||||
if (!File.Exists(manifestPath))
|
if (!File.Exists(manifestPath))
|
||||||
{
|
{
|
||||||
_log.Info($"No manifest in {folder}, skipping");
|
_log.Debug($"No manifest in {folder}, skipping");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_log.Info($"Checking for updates for {folder}");
|
|
||||||
var manifest = PluginManifest.Load(manifestPath);
|
var manifest = PluginManifest.Load(manifestPath);
|
||||||
taskList.Add(updater.CheckAndUpdate(manifest, _torch.Config.RedownloadPlugins));
|
toDownload.RemoveAll(x => string.Compare(manifest.Repository, x, StringComparison.InvariantCultureIgnoreCase) == 0);
|
||||||
|
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var repository in toDownload)
|
||||||
|
{
|
||||||
|
var manifest = new PluginManifest {Repository = repository, Version = "0.0"};
|
||||||
|
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.WaitAll(taskList.ToArray());
|
Task.WaitAll(taskList.ToArray());
|
||||||
_torch.Config.RedownloadPlugins = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Loads and creates instances of all plugins in the <see cref="PluginDir"/> folder.
|
public void LoadPlugins()
|
||||||
/// </summary>
|
|
||||||
public void Init()
|
|
||||||
{
|
{
|
||||||
var commands = ((TorchBase)_torch).Commands;
|
_updateManager = Torch.GetManager<UpdateManager>();
|
||||||
|
var commands = Torch.GetManager<CommandManager>();
|
||||||
|
|
||||||
if (_torch.Config.AutomaticUpdates)
|
if (Torch.Config.ShouldUpdatePlugins)
|
||||||
DownloadPlugins();
|
DownloadPlugins();
|
||||||
else
|
else
|
||||||
_log.Warn("Automatic plugin updates are disabled.");
|
_log.Warn("Automatic plugin updates are disabled.");
|
||||||
@@ -129,7 +116,7 @@ namespace Torch.Managers
|
|||||||
throw new TypeLoadException($"Plugin '{type.FullName}' is missing a {nameof(PluginAttribute)}");
|
throw new TypeLoadException($"Plugin '{type.FullName}' is missing a {nameof(PluginAttribute)}");
|
||||||
|
|
||||||
_log.Info($"Loading plugin {plugin.Name} ({plugin.Version})");
|
_log.Info($"Loading plugin {plugin.Name} ({plugin.Version})");
|
||||||
plugin.StoragePath = new FileInfo(asm.Location).Directory.FullName;
|
plugin.StoragePath = Torch.Config.InstancePath;
|
||||||
Plugins.Add(plugin);
|
Plugins.Add(plugin);
|
||||||
|
|
||||||
commands.RegisterPluginCommands(plugin);
|
commands.RegisterPluginCommands(plugin);
|
||||||
@@ -143,8 +130,8 @@ namespace Torch.Managers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Plugins.ForEach(p => p.Init(_torch));
|
Plugins.ForEach(p => p.Init(Torch));
|
||||||
PluginsLoaded?.Invoke(Plugins);
|
PluginsLoaded?.Invoke(Plugins.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<ITorchPlugin> GetEnumerator()
|
public IEnumerator<ITorchPlugin> GetEnumerator()
|
||||||
|
@@ -1,28 +1,129 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.IO.Packaging;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using Octokit;
|
||||||
using SteamSDK;
|
using SteamSDK;
|
||||||
|
using Torch.API;
|
||||||
|
|
||||||
namespace Torch.Managers
|
namespace Torch.Managers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles updating of the DS and Torch plugins.
|
/// Handles updating of the DS and Torch plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UpdateManager : IDisposable
|
public class UpdateManager : Manager, IDisposable
|
||||||
{
|
{
|
||||||
private Timer _updatePollTimer;
|
private Timer _updatePollTimer;
|
||||||
|
private GitHubClient _gitClient = new GitHubClient(new ProductHeaderValue("Torch"));
|
||||||
|
private string _torchDir = new FileInfo(typeof(UpdateManager).Assembly.Location).DirectoryName;
|
||||||
|
private Logger _log = LogManager.GetLogger(nameof(UpdateManager));
|
||||||
|
private FilesystemManager _fsManager;
|
||||||
|
|
||||||
public UpdateManager()
|
public UpdateManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
{
|
{
|
||||||
_updatePollTimer = new Timer(CheckForUpdates, this, TimeSpan.Zero, TimeSpan.FromMinutes(5));
|
//_updatePollTimer = new Timer(TimerElapsed, this, TimeSpan.Zero, TimeSpan.FromMinutes(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckForUpdates(object state)
|
/// <inheritdoc />
|
||||||
|
public override void Init()
|
||||||
{
|
{
|
||||||
|
_fsManager = Torch.GetManager<FilesystemManager>();
|
||||||
|
CheckAndUpdateTorch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TimerElapsed(object state)
|
||||||
|
{
|
||||||
|
CheckAndUpdateTorch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Tuple<Version, string>> GetLatestRelease(string owner, string name)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var latest = await _gitClient.Repository.Release.GetLatest(owner, name).ConfigureAwait(false);
|
||||||
|
if (latest == null)
|
||||||
|
return new Tuple<Version, string>(new Version(), null);
|
||||||
|
|
||||||
|
var zip = latest.Assets.FirstOrDefault(x => x.Name.Contains(".zip"));
|
||||||
|
return new Tuple<Version, string>(new Version(latest.TagName ?? "0"), zip?.BrowserDownloadUrl);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Error($"An error occurred getting release information for '{owner}/{name}'");
|
||||||
|
_log.Error(e);
|
||||||
|
return new Tuple<Version, string>(new Version(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CheckAndUpdatePlugin(PluginManifest manifest)
|
||||||
|
{
|
||||||
|
if (!Torch.Config.GetPluginUpdates)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var name = manifest.Repository.Split('/');
|
||||||
|
if (name.Length != 2)
|
||||||
|
{
|
||||||
|
_log.Error($"'{manifest.Repository}' is not a valid GitHub repository.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentVersion = new Version(manifest.Version);
|
||||||
|
var releaseInfo = await GetLatestRelease(name[0], name[1]).ConfigureAwait(false);
|
||||||
|
if (releaseInfo.Item1 > currentVersion)
|
||||||
|
{
|
||||||
|
_log.Warn($"Updating {manifest.Repository} from version {currentVersion} to version {releaseInfo.Item1}");
|
||||||
|
var updateName = Path.Combine(_fsManager.TempDirectory, $"{name[0]}_{name[1]}.zip");
|
||||||
|
var updatePath = Path.Combine(_torchDir, "Plugins");
|
||||||
|
await new WebClient().DownloadFileTaskAsync(new Uri(releaseInfo.Item2), updateName).ConfigureAwait(false);
|
||||||
|
UpdateFromZip(updateName, updatePath);
|
||||||
|
File.Delete(updateName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_log.Info($"{manifest.Repository} is up to date. ({currentVersion})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void CheckAndUpdateTorch()
|
||||||
|
{
|
||||||
|
if (!Torch.Config.GetTorchUpdates)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var releaseInfo = await GetLatestRelease("TorchAPI", "Torch").ConfigureAwait(false);
|
||||||
|
if (releaseInfo.Item1 > Torch.TorchVersion)
|
||||||
|
{
|
||||||
|
_log.Warn($"Updating Torch from version {Torch.TorchVersion} to version {releaseInfo.Item1}");
|
||||||
|
var updateName = Path.Combine(_fsManager.TempDirectory, "torchupdate.zip");
|
||||||
|
new WebClient().DownloadFile(new Uri(releaseInfo.Item2), updateName);
|
||||||
|
UpdateFromZip(updateName, _torchDir);
|
||||||
|
File.Delete(updateName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_log.Info("Torch is up to date.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFromZip(string zipFile, string extractPath)
|
||||||
|
{
|
||||||
|
using (var zip = ZipFile.OpenRead(zipFile))
|
||||||
|
{
|
||||||
|
foreach (var file in zip.Entries)
|
||||||
|
{
|
||||||
|
_log.Debug($"Unzipping {file.FullName}");
|
||||||
|
var targetFile = Path.Combine(extractPath, file.FullName);
|
||||||
|
_fsManager.SoftDelete(targetFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
zip.ExtractToDirectory(extractPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@@ -1,15 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
namespace Torch
|
namespace Torch
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Simple class that manages saving <see cref="Persistent{T}.Data"/> to disk using JSON serialization.
|
/// Simple class that manages saving <see cref="Persistent{T}.Data"/> to disk using XML serialization.
|
||||||
|
/// Can automatically save on changes by implementing <see cref="INotifyPropertyChanged"/> in the data class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Data class type</typeparam>
|
/// <typeparam name="T">Data class type</typeparam>
|
||||||
public sealed class Persistent<T> : IDisposable where T : new()
|
public sealed class Persistent<T> : IDisposable where T : new()
|
||||||
@@ -26,6 +29,13 @@ namespace Torch
|
|||||||
{
|
{
|
||||||
Path = path;
|
Path = path;
|
||||||
Data = data;
|
Data = data;
|
||||||
|
if (Data is INotifyPropertyChanged npc)
|
||||||
|
npc.PropertyChanged += OnPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save(string path = null)
|
public void Save(string path = null)
|
||||||
@@ -33,11 +43,10 @@ namespace Torch
|
|||||||
if (path == null)
|
if (path == null)
|
||||||
path = Path;
|
path = Path;
|
||||||
|
|
||||||
|
var ser = new XmlSerializer(typeof(T));
|
||||||
using (var f = File.Create(path))
|
using (var f = File.Create(path))
|
||||||
{
|
{
|
||||||
var writer = new StreamWriter(f);
|
ser.Serialize(f, Data);
|
||||||
writer.Write(JsonConvert.SerializeObject(Data, Formatting.Indented));
|
|
||||||
writer.Flush();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,10 +56,10 @@ namespace Torch
|
|||||||
|
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
|
var ser = new XmlSerializer(typeof(T));
|
||||||
using (var f = File.OpenRead(path))
|
using (var f = File.OpenRead(path))
|
||||||
{
|
{
|
||||||
var reader = new StreamReader(f);
|
config.Data = (T)ser.Deserialize(f);
|
||||||
config.Data = JsonConvert.DeserializeObject<T>(reader.ReadToEnd());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (saveIfNew)
|
else if (saveIfNew)
|
||||||
@@ -65,6 +74,8 @@ namespace Torch
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (Data is INotifyPropertyChanged npc)
|
||||||
|
npc.PropertyChanged -= OnPropertyChanged;
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
28
Torch/SaveGameStatus.cs
Normal file
28
Torch/SaveGameStatus.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Torch
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Describes the possible outcomes when attempting to save the game progress.
|
||||||
|
/// </summary>
|
||||||
|
public enum SaveGameStatus : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The game was saved.
|
||||||
|
/// </summary>
|
||||||
|
Success = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A save operation is already in progress.
|
||||||
|
/// </summary>
|
||||||
|
SaveInProgress = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The game is not in a save-able state.
|
||||||
|
/// </summary>
|
||||||
|
GameNotReady = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The save operation timed out.
|
||||||
|
/// </summary>
|
||||||
|
TimedOut = 3
|
||||||
|
};
|
||||||
|
}
|
@@ -81,6 +81,8 @@
|
|||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.IO.Compression" />
|
||||||
|
<Reference Include="System.IO.Compression.FileSystem" />
|
||||||
<Reference Include="System.Xaml" />
|
<Reference Include="System.Xaml" />
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
@@ -144,7 +146,11 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="ChatMessage.cs" />
|
<Compile Include="ChatMessage.cs" />
|
||||||
|
<Compile Include="Collections\ObservableList.cs" />
|
||||||
|
<Compile Include="DispatcherExtensions.cs" />
|
||||||
|
<Compile Include="SaveGameStatus.cs" />
|
||||||
<Compile Include="Collections\KeyTree.cs" />
|
<Compile Include="Collections\KeyTree.cs" />
|
||||||
|
<Compile Include="Collections\ObservableDictionary.cs" />
|
||||||
<Compile Include="Collections\RollingAverage.cs" />
|
<Compile Include="Collections\RollingAverage.cs" />
|
||||||
<Compile Include="CommandLine.cs" />
|
<Compile Include="CommandLine.cs" />
|
||||||
<Compile Include="Commands\CategoryAttribute.cs" />
|
<Compile Include="Commands\CategoryAttribute.cs" />
|
||||||
@@ -159,19 +165,19 @@
|
|||||||
<Compile Include="Commands\TorchCommands.cs" />
|
<Compile Include="Commands\TorchCommands.cs" />
|
||||||
<Compile Include="Managers\ChatManager.cs" />
|
<Compile Include="Managers\ChatManager.cs" />
|
||||||
<Compile Include="Managers\EntityManager.cs" />
|
<Compile Include="Managers\EntityManager.cs" />
|
||||||
|
<Compile Include="Managers\FilesystemManager.cs" />
|
||||||
<Compile Include="Managers\Manager.cs" />
|
<Compile Include="Managers\Manager.cs" />
|
||||||
<Compile Include="Managers\NetworkManager\NetworkHandlerBase.cs" />
|
<Compile Include="Managers\NetworkManager\NetworkHandlerBase.cs" />
|
||||||
<Compile Include="Managers\NetworkManager\NetworkManager.cs" />
|
<Compile Include="Managers\NetworkManager\NetworkManager.cs" />
|
||||||
<Compile Include="Managers\MultiplayerManager.cs" />
|
<Compile Include="Managers\MultiplayerManager.cs" />
|
||||||
<Compile Include="Managers\UpdateManager.cs" />
|
<Compile Include="Managers\UpdateManager.cs" />
|
||||||
<Compile Include="Persistent.cs" />
|
<Compile Include="Persistent.cs" />
|
||||||
<Compile Include="Updater\PluginManifest.cs" />
|
<Compile Include="PluginManifest.cs" />
|
||||||
<Compile Include="Reflection.cs" />
|
<Compile Include="Reflection.cs" />
|
||||||
<Compile Include="Managers\ScriptingManager.cs" />
|
<Compile Include="Managers\ScriptingManager.cs" />
|
||||||
<Compile Include="TorchBase.cs" />
|
<Compile Include="TorchBase.cs" />
|
||||||
<Compile Include="SteamService.cs" />
|
<Compile Include="SteamService.cs" />
|
||||||
<Compile Include="TorchPluginBase.cs" />
|
<Compile Include="TorchPluginBase.cs" />
|
||||||
<Compile Include="Updater\PluginUpdater.cs" />
|
|
||||||
<Compile Include="ViewModels\ModViewModel.cs" />
|
<Compile Include="ViewModels\ModViewModel.cs" />
|
||||||
<Compile Include="Collections\MTObservableCollection.cs" />
|
<Compile Include="Collections\MTObservableCollection.cs" />
|
||||||
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -41,22 +41,38 @@ namespace Torch
|
|||||||
/// Use only if necessary, prefer dependency injection.
|
/// Use only if necessary, prefer dependency injection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ITorchBase Instance { get; private set; }
|
public static ITorchBase Instance { get; private set; }
|
||||||
|
/// <inheritdoc />
|
||||||
public ITorchConfig Config { get; protected set; }
|
public ITorchConfig Config { get; protected set; }
|
||||||
protected static Logger Log { get; } = LogManager.GetLogger("Torch");
|
/// <inheritdoc />
|
||||||
public Version TorchVersion { get; protected set; }
|
public Version TorchVersion { get; protected set; }
|
||||||
|
/// <inheritdoc />
|
||||||
public Version GameVersion { get; private set; }
|
public Version GameVersion { get; private set; }
|
||||||
|
/// <inheritdoc />
|
||||||
public string[] RunArgs { get; set; }
|
public string[] RunArgs { get; set; }
|
||||||
|
/// <inheritdoc />
|
||||||
public IPluginManager Plugins { get; protected set; }
|
public IPluginManager Plugins { get; protected set; }
|
||||||
|
/// <inheritdoc />
|
||||||
public IMultiplayerManager Multiplayer { get; protected set; }
|
public IMultiplayerManager Multiplayer { get; protected set; }
|
||||||
|
/// <inheritdoc />
|
||||||
public EntityManager Entities { get; protected set; }
|
public EntityManager Entities { get; protected set; }
|
||||||
|
/// <inheritdoc />
|
||||||
public INetworkManager Network { get; protected set; }
|
public INetworkManager Network { get; protected set; }
|
||||||
|
/// <inheritdoc />
|
||||||
public CommandManager Commands { get; protected set; }
|
public CommandManager Commands { get; protected set; }
|
||||||
|
/// <inheritdoc />
|
||||||
public event Action SessionLoading;
|
public event Action SessionLoading;
|
||||||
|
/// <inheritdoc />
|
||||||
public event Action SessionLoaded;
|
public event Action SessionLoaded;
|
||||||
|
/// <inheritdoc />
|
||||||
public event Action SessionUnloading;
|
public event Action SessionUnloading;
|
||||||
|
/// <inheritdoc />
|
||||||
public event Action SessionUnloaded;
|
public event Action SessionUnloaded;
|
||||||
private readonly List<IManager> _managers;
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Common log for the Torch instance.
|
||||||
|
/// </summary>
|
||||||
|
protected static Logger Log { get; } = LogManager.GetLogger("Torch");
|
||||||
|
private readonly List<IManager> _managers;
|
||||||
private bool _init;
|
private bool _init;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -79,22 +95,25 @@ namespace Torch
|
|||||||
Network = new NetworkManager(this);
|
Network = new NetworkManager(this);
|
||||||
Commands = new CommandManager(this);
|
Commands = new CommandManager(this);
|
||||||
|
|
||||||
_managers = new List<IManager> {Network, Commands, Plugins, Multiplayer, Entities, new ChatManager(this)};
|
_managers = new List<IManager> { new FilesystemManager(this), new UpdateManager(this), Network, Commands, Plugins, Multiplayer, Entities, new ChatManager(this), };
|
||||||
|
|
||||||
|
|
||||||
TorchAPI.Instance = this;
|
TorchAPI.Instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public ListReader<IManager> GetManagers()
|
public ListReader<IManager> GetManagers()
|
||||||
{
|
{
|
||||||
return new ListReader<IManager>(_managers);
|
return new ListReader<IManager>(_managers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public T GetManager<T>() where T : class, IManager
|
public T GetManager<T>() where T : class, IManager
|
||||||
{
|
{
|
||||||
return _managers.FirstOrDefault(m => m is T) as T;
|
return _managers.FirstOrDefault(m => m is T) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool AddManager<T>(T manager) where T : class, IManager
|
public bool AddManager<T>(T manager) where T : class, IManager
|
||||||
{
|
{
|
||||||
if (_managers.Any(x => x is T))
|
if (_managers.Any(x => x is T))
|
||||||
@@ -109,33 +128,31 @@ namespace Torch
|
|||||||
return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId;
|
return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveGameAsync()
|
public Task SaveGameAsync(Action<SaveGameStatus> callback)
|
||||||
{
|
{
|
||||||
Log.Info("Saving game");
|
Log.Info("Saving game");
|
||||||
if (MySandboxGame.IsGameReady && !MyAsyncSaving.InProgress && Sync.IsServer && !(MySession.Static.LocalCharacter?.IsDead ?? true))
|
|
||||||
{
|
|
||||||
using (var e = new AutoResetEvent(false))
|
|
||||||
{
|
|
||||||
MyAsyncSaving.Start(() =>
|
|
||||||
{
|
|
||||||
MySector.ResetEyeAdaptation = true;
|
|
||||||
e.Set();
|
|
||||||
});
|
|
||||||
|
|
||||||
await Task.Run(() =>
|
if (!MySandboxGame.IsGameReady)
|
||||||
{
|
{
|
||||||
if (e.WaitOne(60000))
|
callback?.Invoke(SaveGameStatus.GameNotReady);
|
||||||
return;
|
|
||||||
|
|
||||||
Log.Error("Save failed!");
|
|
||||||
Multiplayer.SendMessage("Save timed out!", "Error");
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
else if(MyAsyncSaving.InProgress)
|
||||||
|
{
|
||||||
|
callback?.Invoke(SaveGameStatus.SaveInProgress);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Error("Cannot save");
|
var e = new AutoResetEvent(false);
|
||||||
|
MyAsyncSaving.Start(() => e.Set());
|
||||||
|
|
||||||
|
return Task.Run(() =>
|
||||||
|
{
|
||||||
|
callback?.Invoke(e.WaitOne(5000) ? SaveGameStatus.Success : SaveGameStatus.TimedOut);
|
||||||
|
e.Dispose();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Game Actions
|
#region Game Actions
|
||||||
@@ -201,6 +218,7 @@ namespace Torch
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public virtual void Init()
|
public virtual void Init()
|
||||||
{
|
{
|
||||||
Debug.Assert(!_init, "Torch instance is already initialized.");
|
Debug.Assert(!_init, "Torch instance is already initialized.");
|
||||||
@@ -208,19 +226,9 @@ namespace Torch
|
|||||||
SpaceEngineersGame.SetupBasicGameInfo();
|
SpaceEngineersGame.SetupBasicGameInfo();
|
||||||
SpaceEngineersGame.SetupPerGameSettings();
|
SpaceEngineersGame.SetupPerGameSettings();
|
||||||
|
|
||||||
/*
|
|
||||||
if (Directory.Exists("DedicatedServer64"))
|
|
||||||
{
|
|
||||||
Log.Debug("Inserting DedicatedServer64 before MyPerGameSettings assembly paths");
|
|
||||||
MyPerGameSettings.GameModAssembly = $"DedicatedServer64\\{MyPerGameSettings.GameModAssembly}";
|
|
||||||
MyPerGameSettings.GameModObjBuildersAssembly = $"DedicatedServer64\\{MyPerGameSettings.GameModObjBuildersAssembly}";
|
|
||||||
MyPerGameSettings.SandboxAssembly = $"DedicatedServer64\\{MyPerGameSettings.SandboxAssembly}";
|
|
||||||
MyPerGameSettings.SandboxGameAssembly = $"DedicatedServer64\\{MyPerGameSettings.SandboxGameAssembly}";
|
|
||||||
}*/
|
|
||||||
|
|
||||||
TorchVersion = Assembly.GetEntryAssembly().GetName().Version;
|
TorchVersion = Assembly.GetEntryAssembly().GetName().Version;
|
||||||
GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));
|
GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));
|
||||||
var verInfo = $"Torch {TorchVersion}, SE {GameVersion}";
|
var verInfo = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}";
|
||||||
Console.Title = verInfo;
|
Console.Title = verInfo;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Log.Info("DEBUG");
|
Log.Info("DEBUG");
|
||||||
@@ -231,15 +239,40 @@ namespace Torch
|
|||||||
Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}");
|
Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}");
|
||||||
Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}");
|
Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}");
|
||||||
|
|
||||||
MySession.OnLoading += () => SessionLoading?.Invoke();
|
MySession.OnLoading += OnSessionLoading;
|
||||||
MySession.AfterLoading += () => SessionLoaded?.Invoke();
|
MySession.AfterLoading += OnSessionLoaded;
|
||||||
MySession.OnUnloading += () => SessionUnloading?.Invoke();
|
MySession.OnUnloading += OnSessionUnloading;
|
||||||
MySession.OnUnloaded += () => SessionUnloaded?.Invoke();
|
MySession.OnUnloaded += OnSessionUnloaded;
|
||||||
RegisterVRagePlugin();
|
RegisterVRagePlugin();
|
||||||
|
foreach (var manager in _managers)
|
||||||
|
manager.Init();
|
||||||
_init = true;
|
_init = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnSessionLoading()
|
||||||
|
{
|
||||||
|
Log.Debug("Session loading");
|
||||||
|
SessionLoading?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSessionLoaded()
|
||||||
|
{
|
||||||
|
Log.Debug("Session loaded");
|
||||||
|
SessionLoaded?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSessionUnloading()
|
||||||
|
{
|
||||||
|
Log.Debug("Session unloading");
|
||||||
|
SessionUnloading?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSessionUnloaded()
|
||||||
|
{
|
||||||
|
Log.Debug("Session unloaded");
|
||||||
|
SessionUnloaded?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hook into the VRage plugin system for updates.
|
/// Hook into the VRage plugin system for updates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -253,12 +286,29 @@ namespace Torch
|
|||||||
pluginList.Add(this);
|
pluginList.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual Task Save(long callerId)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public virtual void Start()
|
public virtual void Start()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Stop() { }
|
/// <inheritdoc />
|
||||||
|
public virtual void Stop()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual void Restart()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual void Dispose()
|
public virtual void Dispose()
|
||||||
@@ -269,8 +319,7 @@ namespace Torch
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual void Init(object gameInstance)
|
public virtual void Init(object gameInstance)
|
||||||
{
|
{
|
||||||
foreach (var manager in _managers)
|
|
||||||
manager.Init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@@ -1,93 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NLog;
|
|
||||||
using Octokit;
|
|
||||||
using Torch.API;
|
|
||||||
using Torch.Managers;
|
|
||||||
using VRage.Compression;
|
|
||||||
|
|
||||||
namespace Torch.Updater
|
|
||||||
{
|
|
||||||
public class PluginUpdater
|
|
||||||
{
|
|
||||||
private readonly PluginManager _pluginManager;
|
|
||||||
private static readonly Logger Log = LogManager.GetLogger("PluginUpdater");
|
|
||||||
|
|
||||||
public PluginUpdater(PluginManager pm)
|
|
||||||
{
|
|
||||||
_pluginManager = pm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CheckAndUpdate(PluginManifest manifest, bool force = false)
|
|
||||||
{
|
|
||||||
Log.Info($"Checking for update at {manifest.Repository}");
|
|
||||||
var split = manifest.Repository.Split('/');
|
|
||||||
|
|
||||||
if (split.Length != 2)
|
|
||||||
{
|
|
||||||
Log.Warn($"Manifest has an invalid repository name: {manifest.Repository}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var gitClient = new GitHubClient(new ProductHeaderValue("Torch"));
|
|
||||||
var releases = await gitClient.Repository.Release.GetAll(split[0], split[1]).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (releases.Count == 0)
|
|
||||||
{
|
|
||||||
Log.Debug("No releases in repo");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Version currentVersion;
|
|
||||||
Version latestVersion;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
currentVersion = new Version(manifest.Version);
|
|
||||||
latestVersion = new Version(releases[0].TagName);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Log.Warn("Invalid version number on manifest or GitHub release");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (force || latestVersion > currentVersion)
|
|
||||||
{
|
|
||||||
var webClient = new WebClient();
|
|
||||||
var assets = await gitClient.Repository.Release.GetAllAssets(split[0], split[1], releases[0].Id).ConfigureAwait(false);
|
|
||||||
foreach (var asset in assets)
|
|
||||||
{
|
|
||||||
if (asset.Name.EndsWith(".zip"))
|
|
||||||
{
|
|
||||||
Log.Debug(asset.BrowserDownloadUrl);
|
|
||||||
var localPath = Path.Combine(Path.GetTempPath(), asset.Name);
|
|
||||||
await webClient.DownloadFileTaskAsync(new Uri(asset.BrowserDownloadUrl), localPath).ConfigureAwait(false);
|
|
||||||
UnzipPlugin(localPath);
|
|
||||||
Log.Info($"Downloaded update for {manifest.Repository}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.Info($"{manifest.Repository} is up to date.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnzipPlugin(string zipName)
|
|
||||||
{
|
|
||||||
if (!File.Exists(zipName))
|
|
||||||
return;
|
|
||||||
|
|
||||||
MyZipArchive.ExtractToDirectory(zipName, _pluginManager.PluginDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,8 +3,10 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
using SteamSDK;
|
using SteamSDK;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using VRage.Replication;
|
||||||
|
|
||||||
namespace Torch.ViewModels
|
namespace Torch.ViewModels
|
||||||
{
|
{
|
||||||
@@ -18,7 +20,7 @@ namespace Torch.ViewModels
|
|||||||
public PlayerViewModel(ulong steamId, string name = null)
|
public PlayerViewModel(ulong steamId, string name = null)
|
||||||
{
|
{
|
||||||
SteamId = steamId;
|
SteamId = steamId;
|
||||||
Name = name ?? SteamAPI.Instance?.Friends?.GetPersonaName(steamId) ?? "???";
|
Name = name ?? ((MyDedicatedServerBase)MyMultiplayerMinimalBase.Instance).GetMemberName(steamId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ namespace Torch
|
|||||||
{
|
{
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
protected void OnPropertyChanged([CallerMemberName] string propName = "")
|
protected virtual void OnPropertyChanged([CallerMemberName] string propName = "")
|
||||||
{
|
{
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
|
||||||
}
|
}
|
||||||
|
1
docs/_config.yml
Normal file
1
docs/_config.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-modernist
|
1
docs/index.md
Normal file
1
docs/index.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Torch
|
Reference in New Issue
Block a user