Merge branch 'master' of git://github.com/TorchAPI/Torch
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
pushd
|
pushd
|
||||||
|
|
||||||
$steamData = "C:/Steam/Data/"
|
$steamData = "C:/Steam/Data-playtest/"
|
||||||
$steamCMDPath = "C:/Steam/steamcmd/"
|
$steamCMDPath = "C:/Steam/steamcmd/"
|
||||||
$steamCMDZip = "C:/Steam/steamcmd.zip"
|
$steamCMDZip = "C:/Steam/steamcmd.zip"
|
||||||
|
|
||||||
@@ -17,6 +17,6 @@ if (!(Test-Path $steamCMDPath)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cd "$steamData"
|
cd "$steamData"
|
||||||
& "$steamCMDPath/steamcmd.exe" "+login anonymous" "+force_install_dir $steamData" "+app_update 298740" "+quit"
|
& "$steamCMDPath/steamcmd.exe" "+login anonymous" "+force_install_dir $steamData" "+app_update 298740 validate" "+quit"
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
4
Jenkinsfile
vendored
4
Jenkinsfile
vendored
@@ -22,7 +22,7 @@ node {
|
|||||||
stage('Acquire SE') {
|
stage('Acquire SE') {
|
||||||
bat 'powershell -File Jenkins/jenkins-grab-se.ps1'
|
bat 'powershell -File Jenkins/jenkins-grab-se.ps1'
|
||||||
bat 'IF EXIST GameBinaries RMDIR GameBinaries'
|
bat 'IF EXIST GameBinaries RMDIR GameBinaries'
|
||||||
bat 'mklink /J GameBinaries "C:/Steam/Data/DedicatedServer64/"'
|
bat 'mklink /J GameBinaries "C:/Steam/Data-playtest/DedicatedServer64/"'
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Acquire NuGet Packages') {
|
stage('Acquire NuGet Packages') {
|
||||||
@@ -31,7 +31,7 @@ node {
|
|||||||
|
|
||||||
stage('Build') {
|
stage('Build') {
|
||||||
currentBuild.description = bat(returnStdout: true, script: '@powershell -File Versioning/version.ps1').trim()
|
currentBuild.description = bat(returnStdout: true, script: '@powershell -File Versioning/version.ps1').trim()
|
||||||
if (env.BRANCH_NAME == "master") {
|
if (env.BRANCH_NAME == "master" || env.BRANCH_NAME == "Patron" || env.BRANCH_NAME == "publictest") {
|
||||||
buildMode = "Release"
|
buildMode = "Release"
|
||||||
} else {
|
} else {
|
||||||
buildMode = "Debug"
|
buildMode = "Debug"
|
||||||
|
21
NLog-user.config
Normal file
21
NLog-user.config
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<variable name="logStamp" value="${time} ${pad:padding=-8:inner=[${level:uppercase=true}]}" />
|
||||||
|
<variable name="logContent" value="${message:withException=true}"/>
|
||||||
|
|
||||||
|
<targets async="true">
|
||||||
|
<target xsi:type="Null" name="null" formatMessage="false" />
|
||||||
|
<target xsi:type="File" name="keen" layout="${var:logStamp} ${logger}: ${var:logContent}" fileName="Logs\Keen-${shortdate}.log" />
|
||||||
|
<target xsi:type="File" name="main" layout="${var:logStamp} ${logger}: ${var:logContent}" fileName="Logs\Torch-${shortdate}.log" />
|
||||||
|
<target xsi:type="File" name="chat" layout="${longdate} ${message}" fileName="Logs\Chat.log" />
|
||||||
|
<target xsi:type="ColoredConsole" name="console" layout="${var:logStamp} ${logger:shortName=true}: ${var:logContent}" />
|
||||||
|
<target xsi:type="File" name="patch" layout="${var:logContent}" fileName="Logs\patch.log"/>
|
||||||
|
<target xsi:type="FlowDocument" name="wpf" layout="${var:logStamp} ${logger:shortName=true}: ${var:logContent}" />
|
||||||
|
</targets>
|
||||||
|
|
||||||
|
<rules>
|
||||||
|
<!-- Define custom rules below. The example line will pipe all debug output to log file, in-UI console, and independent console. -->
|
||||||
|
<!--<logger name="*" minlevel="Debug" writeTo="main, console, wpf" />-->
|
||||||
|
</rules>
|
||||||
|
</nlog>
|
@@ -4,6 +4,8 @@
|
|||||||
<variable name="logStamp" value="${time} ${pad:padding=-8:inner=[${level:uppercase=true}]}" />
|
<variable name="logStamp" value="${time} ${pad:padding=-8:inner=[${level:uppercase=true}]}" />
|
||||||
<variable name="logContent" value="${message:withException=true}"/>
|
<variable name="logContent" value="${message:withException=true}"/>
|
||||||
|
|
||||||
|
<include file="NLog-user.config"/>
|
||||||
|
|
||||||
<targets async="true">
|
<targets async="true">
|
||||||
<target xsi:type="Null" name="null" formatMessage="false" />
|
<target xsi:type="Null" name="null" formatMessage="false" />
|
||||||
<target xsi:type="File" name="keen" layout="${var:logStamp} ${logger}: ${var:logContent}" fileName="Logs\Keen-${shortdate}.log" />
|
<target xsi:type="File" name="keen" layout="${var:logStamp} ${logger}: ${var:logContent}" fileName="Logs\Keen-${shortdate}.log" />
|
||||||
@@ -15,6 +17,7 @@
|
|||||||
</targets>
|
</targets>
|
||||||
|
|
||||||
<rules>
|
<rules>
|
||||||
|
<!-- Do not define custom rules here. Use NLog-user.config -->
|
||||||
<logger name="Keen" minlevel="Warn" writeTo="main"/>
|
<logger name="Keen" minlevel="Warn" writeTo="main"/>
|
||||||
<logger name="Keen" minlevel="Info" writeTo="console, wpf"/>
|
<logger name="Keen" minlevel="Info" writeTo="console, wpf"/>
|
||||||
<logger name="Keen" minlevel="Debug" writeTo="keen" final="true" />
|
<logger name="Keen" minlevel="Debug" writeTo="keen" final="true" />
|
||||||
|
12
README.md
12
README.md
@@ -18,18 +18,8 @@ Torch is the successor to SE Server Extender and gives server admins the tools t
|
|||||||
* Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files.
|
* Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files.
|
||||||
- If you already have a DS installed you can unzip the Torch files into the folder that contains the DedicatedServer64 folder.
|
- If you already have a DS installed you can unzip the Torch files into the folder that contains the DedicatedServer64 folder.
|
||||||
|
|
||||||
## Torch.Client
|
|
||||||
* An optional client-side version of Torch. More documentation to come.
|
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
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.
|
||||||
|
|
||||||
In both cases you will need to set the InstancePath in TorchConfig.xml to an existing dedicated server instance as Torch can't fully generate it on its own yet.
|
|
||||||
|
|
||||||
# Official Plugins
|
|
||||||
Install plugins by unzipping them into the 'Plugins' folder which should be in the same location as the Torch files. If it doesn't exist you can simply create it.
|
|
||||||
* [Essentials](https://github.com/TorchAPI/Essentials): Adds a slew of chat commands and other tools to help manage your server.
|
|
||||||
* [Concealment](https://github.com/TorchAPI/Concealment): Adds game logic and physics optimizations that significantly improve sim speed.
|
|
||||||
|
|
||||||
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon.
|
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon.
|
||||||
[](https://www.patreon.com/bePatron?u=847269)!
|
[](https://www.patreon.com/bePatron?u=847269)
|
||||||
|
@@ -104,7 +104,7 @@ namespace Torch.API
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restart the Torch instance, blocking until the restart has been performed.
|
/// Restart the Torch instance, blocking until the restart has been performed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Restart();
|
void Restart(bool save = true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a save of the game.
|
/// Initializes a save of the game.
|
||||||
@@ -154,6 +154,8 @@ namespace Torch.API
|
|||||||
/// Raised when the server's Init() method has completed.
|
/// Raised when the server's Init() method has completed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action<ITorchServer> Initialized;
|
event Action<ITorchServer> Initialized;
|
||||||
|
|
||||||
|
TimeSpan ElapsedPlayTime { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Torch
|
namespace Torch
|
||||||
{
|
{
|
||||||
@@ -12,12 +13,17 @@ namespace Torch
|
|||||||
string InstancePath { get; set; }
|
string InstancePath { get; set; }
|
||||||
bool NoGui { get; set; }
|
bool NoGui { get; set; }
|
||||||
bool NoUpdate { get; set; }
|
bool NoUpdate { get; set; }
|
||||||
List<string> Plugins { get; set; }
|
List<Guid> Plugins { get; set; }
|
||||||
|
bool LocalPlugins { get; set; }
|
||||||
bool RestartOnCrash { get; set; }
|
bool RestartOnCrash { get; set; }
|
||||||
bool ShouldUpdatePlugins { get; }
|
bool ShouldUpdatePlugins { get; }
|
||||||
bool ShouldUpdateTorch { get; }
|
bool ShouldUpdateTorch { get; }
|
||||||
int TickTimeout { get; set; }
|
int TickTimeout { get; set; }
|
||||||
string WaitForPID { get; set; }
|
string WaitForPID { get; set; }
|
||||||
|
string ChatName { get; set; }
|
||||||
|
string ChatColor { get; set; }
|
||||||
|
string TestPlugin { get; set; }
|
||||||
|
bool DisconnectOnRestart { get; set; }
|
||||||
|
|
||||||
bool Save(string path = null);
|
bool Save(string path = null);
|
||||||
}
|
}
|
||||||
|
@@ -7,36 +7,36 @@ using System.Threading.Tasks;
|
|||||||
namespace Torch.API
|
namespace Torch.API
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version in the form v#.#.#.#-info
|
/// Version in the form v#.#.#.#-branch
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class InformationalVersion
|
public class InformationalVersion
|
||||||
{
|
{
|
||||||
public Version Version { get; set; }
|
public Version Version { get; set; }
|
||||||
public string[] Information { get; set; }
|
public string Branch { get; set; }
|
||||||
|
|
||||||
public static bool TryParse(string input, out InformationalVersion version)
|
public static bool TryParse(string input, out InformationalVersion version)
|
||||||
{
|
{
|
||||||
version = default(InformationalVersion);
|
version = default(InformationalVersion);
|
||||||
var trim = input.TrimStart('v');
|
var trim = input.TrimStart('v');
|
||||||
var info = trim.Split('-');
|
var info = trim.Split(new[]{'-'}, 2);
|
||||||
if (!Version.TryParse(info[0], out Version result))
|
if (!Version.TryParse(info[0], out Version result))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
version = new InformationalVersion { Version = result };
|
version = new InformationalVersion { Version = result };
|
||||||
|
|
||||||
if (info.Length > 1)
|
if (info.Length > 1)
|
||||||
version.Information = info.Skip(1).ToArray();
|
version.Branch = info[1];
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (Information == null || Information.Length == 0)
|
if (Branch == null)
|
||||||
return $"v{Version}";
|
return $"v{Version}";
|
||||||
|
|
||||||
return $"v{Version}-{string.Join("-", Information)}";
|
return $"v{Version}-{string.Join("-", Branch)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static explicit operator InformationalVersion(Version v)
|
public static explicit operator InformationalVersion(Version v)
|
||||||
@@ -48,5 +48,15 @@ namespace Torch.API
|
|||||||
{
|
{
|
||||||
return v.Version;
|
return v.Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool operator >(InformationalVersion lhs, InformationalVersion rhs)
|
||||||
|
{
|
||||||
|
return lhs.Version > rhs.Version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator <(InformationalVersion lhs, InformationalVersion rhs)
|
||||||
|
{
|
||||||
|
return lhs.Version < rhs.Version;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Sandbox.Engine.Multiplayer;
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Game.Gui;
|
||||||
using Sandbox.Game.Multiplayer;
|
using Sandbox.Game.Multiplayer;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
using VRage.Network;
|
using VRage.Network;
|
||||||
@@ -28,6 +29,8 @@ namespace Torch.API.Managers
|
|||||||
AuthorSteamId = null;
|
AuthorSteamId = null;
|
||||||
Author = author;
|
Author = author;
|
||||||
Message = message;
|
Message = message;
|
||||||
|
Channel = ChatChannel.Global;
|
||||||
|
Target = 0;
|
||||||
Font = font;
|
Font = font;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,12 +41,14 @@ namespace Torch.API.Managers
|
|||||||
/// <param name="authorSteamId">Author's steam ID</param>
|
/// <param name="authorSteamId">Author's steam ID</param>
|
||||||
/// <param name="message">Message</param>
|
/// <param name="message">Message</param>
|
||||||
/// <param name="font">Font</param>
|
/// <param name="font">Font</param>
|
||||||
public TorchChatMessage(string author, ulong authorSteamId, string message, string font = MyFontEnum.Blue)
|
public TorchChatMessage(string author, ulong authorSteamId, string message, ChatChannel channel, long target, string font = MyFontEnum.Blue)
|
||||||
{
|
{
|
||||||
Timestamp = DateTime.Now;
|
Timestamp = DateTime.Now;
|
||||||
AuthorSteamId = authorSteamId;
|
AuthorSteamId = authorSteamId;
|
||||||
Author = author;
|
Author = author;
|
||||||
Message = message;
|
Message = message;
|
||||||
|
Channel = channel;
|
||||||
|
Target = target;
|
||||||
Font = font;
|
Font = font;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,12 +58,14 @@ namespace Torch.API.Managers
|
|||||||
/// <param name="authorSteamId">Author's steam ID</param>
|
/// <param name="authorSteamId">Author's steam ID</param>
|
||||||
/// <param name="message">Message</param>
|
/// <param name="message">Message</param>
|
||||||
/// <param name="font">Font</param>
|
/// <param name="font">Font</param>
|
||||||
public TorchChatMessage(ulong authorSteamId, string message, string font = MyFontEnum.Blue)
|
public TorchChatMessage(ulong authorSteamId, string message, ChatChannel channel, long target, string font = MyFontEnum.Blue)
|
||||||
{
|
{
|
||||||
Timestamp = DateTime.Now;
|
Timestamp = DateTime.Now;
|
||||||
AuthorSteamId = authorSteamId;
|
AuthorSteamId = authorSteamId;
|
||||||
Author = MyMultiplayer.Static?.GetMemberName(authorSteamId) ?? "Player";
|
Author = MyMultiplayer.Static?.GetMemberName(authorSteamId) ?? "Player";
|
||||||
Message = message;
|
Message = message;
|
||||||
|
Channel = channel;
|
||||||
|
Target = target;
|
||||||
Font = font;
|
Font = font;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +86,14 @@ namespace Torch.API.Managers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly string Message;
|
public readonly string Message;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// The chat channel the message is part of.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ChatChannel Channel;
|
||||||
|
/// <summary>
|
||||||
|
/// The intended recipient of the message.
|
||||||
|
/// </summary>
|
||||||
|
public readonly long Target;
|
||||||
|
/// <summary>
|
||||||
/// The font, or null if default.
|
/// The font, or null if default.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly string Font;
|
public readonly string Font;
|
||||||
|
@@ -3,6 +3,7 @@ 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 VRage.Collections;
|
||||||
using VRage.Network;
|
using VRage.Network;
|
||||||
|
|
||||||
namespace Torch.API.Managers
|
namespace Torch.API.Managers
|
||||||
@@ -41,5 +42,24 @@ namespace Torch.API.Managers
|
|||||||
/// <param name="font">Font to use</param>
|
/// <param name="font">Font to use</param>
|
||||||
/// <param name="targetSteamId">Player to send the message to, or everyone by default</param>
|
/// <param name="targetSteamId">Player to send the message to, or everyone by default</param>
|
||||||
void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0);
|
void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mute user from global chat.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool MuteUser(ulong steamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unmute user from global chat.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool UnmuteUser(ulong steamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Users which are not allowed to chat.
|
||||||
|
/// </summary>
|
||||||
|
HashSetReader<ulong> MutedUsers { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,5 +34,22 @@ namespace Torch.API.Plugins
|
|||||||
/// This is called on the game thread after each tick.
|
/// This is called on the game thread after each tick.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Update();
|
void Update();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin's enabled state. Mainly for UI niceness
|
||||||
|
/// </summary>
|
||||||
|
PluginState State { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PluginState
|
||||||
|
{
|
||||||
|
NotInitialized,
|
||||||
|
DisabledError,
|
||||||
|
DisabledUser,
|
||||||
|
UpdateRequired,
|
||||||
|
UninstallRequested,
|
||||||
|
NotInstalled,
|
||||||
|
MissingDependency,
|
||||||
|
Enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
|
using VRage.Game;
|
||||||
|
|
||||||
namespace Torch.API.Session
|
namespace Torch.API.Session
|
||||||
{
|
{
|
||||||
@@ -47,5 +49,29 @@ namespace Torch.API.Session
|
|||||||
/// <returns>true if removed, false if not present</returns>
|
/// <returns>true if removed, false if not present</returns>
|
||||||
/// <exception cref="ArgumentNullException">If the factory is null</exception>
|
/// <exception cref="ArgumentNullException">If the factory is null</exception>
|
||||||
bool RemoveFactory(SessionManagerFactoryDel factory);
|
bool RemoveFactory(SessionManagerFactoryDel factory);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a mod to be injected into client's world download.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool AddOverrideMod(ulong modId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a mod from the injected mod list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool RemoveOverrideMod(ulong modId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List over mods that will be injected into client world downloads.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<MyObjectBuilder_Checkpoint.ModItem> OverrideMods { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised when injected mod list changes.
|
||||||
|
/// </summary>
|
||||||
|
event Action<CollectionChangeEventArgs> OverrideModsChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,7 @@
|
|||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.API.xml</DocumentationFile>
|
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.API.xml</DocumentationFile>
|
||||||
|
<NoWarn>1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
|
||||||
@@ -38,6 +39,10 @@
|
|||||||
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath>
|
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
|
||||||
|
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</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.12\lib\net45\NLog.dll</HintPath>
|
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
@@ -188,6 +193,8 @@
|
|||||||
<Compile Include="Session\ITorchSessionManager.cs" />
|
<Compile Include="Session\ITorchSessionManager.cs" />
|
||||||
<Compile Include="Session\TorchSessionState.cs" />
|
<Compile Include="Session\TorchSessionState.cs" />
|
||||||
<Compile Include="TorchGameState.cs" />
|
<Compile Include="TorchGameState.cs" />
|
||||||
|
<Compile Include="WebAPI\JenkinsQuery.cs" />
|
||||||
|
<Compile Include="WebAPI\PluginQuery.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
130
Torch.API/WebAPI/JenkinsQuery.cs
Normal file
130
Torch.API/WebAPI/JenkinsQuery.cs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
|
namespace Torch.API.WebAPI
|
||||||
|
{
|
||||||
|
public class JenkinsQuery
|
||||||
|
{
|
||||||
|
private const string BRANCH_QUERY = "https://build.torchapi.net/job/Torch/job/Torch/job/{0}/" + API_PATH;
|
||||||
|
private const string ARTIFACT_PATH = "artifact/bin/torch-server.zip";
|
||||||
|
private const string API_PATH = "api/json";
|
||||||
|
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
private static JenkinsQuery _instance;
|
||||||
|
public static JenkinsQuery Instance => _instance ?? (_instance = new JenkinsQuery());
|
||||||
|
private HttpClient _client;
|
||||||
|
|
||||||
|
private JenkinsQuery()
|
||||||
|
{
|
||||||
|
_client = new HttpClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Job> GetLatestVersion(string branch)
|
||||||
|
{
|
||||||
|
var h = await _client.GetAsync(string.Format(BRANCH_QUERY, branch));
|
||||||
|
if (!h.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Log.Error($"Branch query failed with code {h.StatusCode}");
|
||||||
|
if(h.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
Log.Error("This likely means you're trying to update a branch that is not public on Jenkins. Sorry :(");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string r = await h.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
BranchResponse response;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response = JsonConvert.DeserializeObject<BranchResponse>(r);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to deserialize branch response!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
h = await _client.GetAsync($"{response.LastStableBuild.URL}{API_PATH}");
|
||||||
|
if (!h.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Log.Error($"Job query failed with code {h.StatusCode}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = await h.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
Job job;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
job = JsonConvert.DeserializeObject<Job>(r);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to deserialize job response!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DownloadRelease(Job job, string path)
|
||||||
|
{
|
||||||
|
var h = await _client.GetAsync(job.URL + ARTIFACT_PATH);
|
||||||
|
if (!h.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Log.Error($"Job download failed with code {h.StatusCode}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var s = await h.Content.ReadAsStreamAsync();
|
||||||
|
using (var fs = new FileStream(path, FileMode.Create))
|
||||||
|
{
|
||||||
|
await s.CopyToAsync(fs);
|
||||||
|
await fs.FlushAsync();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BranchResponse
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public string URL;
|
||||||
|
public Build LastBuild;
|
||||||
|
public Build LastStableBuild;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Build
|
||||||
|
{
|
||||||
|
public int Number;
|
||||||
|
public string URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Job
|
||||||
|
{
|
||||||
|
public int Number;
|
||||||
|
public bool Building;
|
||||||
|
public string Description;
|
||||||
|
public string Result;
|
||||||
|
public string URL;
|
||||||
|
private InformationalVersion _version;
|
||||||
|
|
||||||
|
public InformationalVersion Version
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_version == null)
|
||||||
|
InformationalVersion.TryParse(Description, out _version);
|
||||||
|
|
||||||
|
return _version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
168
Torch.API/WebAPI/PluginQuery.cs
Normal file
168
Torch.API/WebAPI/PluginQuery.cs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
|
namespace Torch.API.WebAPI
|
||||||
|
{
|
||||||
|
public class PluginQuery
|
||||||
|
{
|
||||||
|
private const string ALL_QUERY = "https://torchapi.net/api/plugins";
|
||||||
|
private const string PLUGIN_QUERY = "https://torchapi.net/api/plugins/{0}";
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
private static PluginQuery _instance;
|
||||||
|
public static PluginQuery Instance => _instance ?? (_instance = new PluginQuery());
|
||||||
|
|
||||||
|
private PluginQuery()
|
||||||
|
{
|
||||||
|
_client = new HttpClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PluginResponse> QueryAll()
|
||||||
|
{
|
||||||
|
var h = await _client.GetAsync(ALL_QUERY);
|
||||||
|
if (!h.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Log.Error($"Plugin query returned response {h.StatusCode}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = await h.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
PluginResponse response;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response = JsonConvert.DeserializeObject<PluginResponse>(r);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to deserialize plugin query response!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PluginFullItem> QueryOne(Guid guid)
|
||||||
|
{
|
||||||
|
return await QueryOne(guid.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PluginFullItem> QueryOne(string guid)
|
||||||
|
{
|
||||||
|
|
||||||
|
var h = await _client.GetAsync(string.Format(PLUGIN_QUERY, guid));
|
||||||
|
if (!h.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Log.Error($"Plugin query returned response {h.StatusCode}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = await h.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
PluginFullItem response;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response = JsonConvert.DeserializeObject<PluginFullItem>(r);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to deserialize plugin query response!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DownloadPlugin(Guid guid, string path = null)
|
||||||
|
{
|
||||||
|
return await DownloadPlugin(guid.ToString(), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DownloadPlugin(string guid, string path = null)
|
||||||
|
{
|
||||||
|
var item = await QueryOne(guid);
|
||||||
|
return await DownloadPlugin(item, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DownloadPlugin(PluginFullItem item, string path = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
path = path ?? $"Plugins\\{item.Name}.zip";
|
||||||
|
string relpath = Path.GetDirectoryName(path);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(relpath);
|
||||||
|
|
||||||
|
var h = await _client.GetAsync(string.Format(PLUGIN_QUERY, item.ID));
|
||||||
|
string res = await h.Content.ReadAsStringAsync();
|
||||||
|
var response = JsonConvert.DeserializeObject<PluginFullItem>(res);
|
||||||
|
if (response.Versions.Length == 0)
|
||||||
|
{
|
||||||
|
Log.Error($"Selected plugin {item.Name} does not have any versions to download!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var version = response.Versions.FirstOrDefault(v => v.Version == response.LatestVersion);
|
||||||
|
if (version == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Could not find latest version for selected plugin {item.Name}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var s = await _client.GetStreamAsync(version.URL);
|
||||||
|
|
||||||
|
if(File.Exists(path))
|
||||||
|
File.Delete(path);
|
||||||
|
|
||||||
|
using (var f = File.Create(path))
|
||||||
|
{
|
||||||
|
await s.CopyToAsync(f);
|
||||||
|
await f.FlushAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to download plugin!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PluginResponse
|
||||||
|
{
|
||||||
|
public PluginItem[] Plugins;
|
||||||
|
public int Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PluginItem
|
||||||
|
{
|
||||||
|
public string ID;
|
||||||
|
public string Name;
|
||||||
|
public string Author;
|
||||||
|
public string Description;
|
||||||
|
public string LatestVersion;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PluginFullItem : PluginItem
|
||||||
|
{
|
||||||
|
public VersionItem[] Versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VersionItem
|
||||||
|
{
|
||||||
|
public string Version;
|
||||||
|
public string Note;
|
||||||
|
public bool IsBeta;
|
||||||
|
public string URL;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||||
|
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net461" />
|
||||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||||
</packages>
|
</packages>
|
@@ -86,6 +86,7 @@
|
|||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Include="app.config" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
11
Torch.Client.Tests/app.config
Normal file
11
Torch.Client.Tests/app.config
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="protobuf-net" publicKeyToken="257b51d87d2e4d67" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-2.4.0.0" newVersion="2.4.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
@@ -36,6 +36,7 @@
|
|||||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
<Prefer32Bit>true</Prefer32Bit>
|
<Prefer32Bit>true</Prefer32Bit>
|
||||||
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.Client.xml</DocumentationFile>
|
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.Client.xml</DocumentationFile>
|
||||||
|
<NoWarn>1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ApplicationIcon>torchicon.ico</ApplicationIcon>
|
<ApplicationIcon>torchicon.ico</ApplicationIcon>
|
||||||
@@ -146,6 +147,7 @@
|
|||||||
<Generator>ResXFileCodeGenerator</Generator>
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<None Include="app.config" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
<None Include="Properties\Settings.settings">
|
<None Include="Properties\Settings.settings">
|
||||||
<Generator>SettingsSingleFileGenerator</Generator>
|
<Generator>SettingsSingleFileGenerator</Generator>
|
||||||
|
@@ -23,6 +23,8 @@ namespace Torch.Client
|
|||||||
public bool NoGui { get; set; } = false;
|
public bool NoGui { get; set; } = false;
|
||||||
public bool RestartOnCrash { get; set; } = false;
|
public bool RestartOnCrash { get; set; } = false;
|
||||||
public string WaitForPID { get; set; } = null;
|
public string WaitForPID { get; set; } = null;
|
||||||
|
public string ChatName { get; set; }
|
||||||
|
public string ChatColor { get; set; }
|
||||||
|
|
||||||
public bool Save(string path = null)
|
public bool Save(string path = null)
|
||||||
{
|
{
|
||||||
|
11
Torch.Client/app.config
Normal file
11
Torch.Client/app.config
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="protobuf-net" publicKeyToken="257b51d87d2e4d67" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-2.4.0.0" newVersion="2.4.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
57
Torch.Mod/Messages/JoinServerMessage.cs
Normal file
57
Torch.Mod/Messages/JoinServerMessage.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using ProtoBuf;
|
||||||
|
using Sandbox.ModAPI;
|
||||||
|
|
||||||
|
namespace Torch.Mod.Messages
|
||||||
|
{
|
||||||
|
[ProtoContract]
|
||||||
|
public class JoinServerMessage : MessageBase
|
||||||
|
{
|
||||||
|
[ProtoMember(201)]
|
||||||
|
public int Delay;
|
||||||
|
[ProtoMember(202)]
|
||||||
|
public string Address;
|
||||||
|
|
||||||
|
private JoinServerMessage()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public JoinServerMessage(string address)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JoinServerMessage(string address, int delay)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
Delay = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessClient()
|
||||||
|
{
|
||||||
|
if (TorchModCore.Debug)
|
||||||
|
{
|
||||||
|
MyAPIGateway.Utilities.ShowMessage("Torch", $"Joining server {Address} with delay {Delay}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Delay <= 0)
|
||||||
|
{
|
||||||
|
MyAPIGateway.Multiplayer.JoinServer(Address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MyAPIGateway.Parallel.StartBackground(() =>
|
||||||
|
{
|
||||||
|
MyAPIGateway.Parallel.Sleep(Delay);
|
||||||
|
MyAPIGateway.Multiplayer.JoinServer(Address);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessServer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -11,6 +11,7 @@ namespace Torch.Mod.Messages
|
|||||||
[ProtoInclude(1, typeof(DialogMessage))]
|
[ProtoInclude(1, typeof(DialogMessage))]
|
||||||
[ProtoInclude(2, typeof(NotificationMessage))]
|
[ProtoInclude(2, typeof(NotificationMessage))]
|
||||||
[ProtoInclude(3, typeof(VoxelResetMessage))]
|
[ProtoInclude(3, typeof(VoxelResetMessage))]
|
||||||
|
[ProtoInclude(4, typeof(JoinServerMessage))]
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
[ProtoContract]
|
[ProtoContract]
|
||||||
@@ -28,7 +29,7 @@ namespace Torch.Mod.Messages
|
|||||||
internal ulong[] Ignore;
|
internal ulong[] Ignore;
|
||||||
internal byte[] CompressedData;
|
internal byte[] CompressedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MessageTarget
|
public enum MessageTarget
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -10,6 +10,7 @@ using Torch.Mod.Messages;
|
|||||||
using VRage;
|
using VRage;
|
||||||
using VRage.Collections;
|
using VRage.Collections;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
|
using VRage.Network;
|
||||||
using VRage.Utils;
|
using VRage.Utils;
|
||||||
using Task = ParallelTasks.Task;
|
using Task = ParallelTasks.Task;
|
||||||
|
|
||||||
@@ -50,6 +51,10 @@ namespace Torch.Mod
|
|||||||
{
|
{
|
||||||
var m = _messagePool.Get();
|
var m = _messagePool.Get();
|
||||||
m.CompressedData = bytes;
|
m.CompressedData = bytes;
|
||||||
|
#if TORCH
|
||||||
|
m.SenderId = MyEventContext.Current.Sender.Value;
|
||||||
|
#endif
|
||||||
|
|
||||||
_processing.Add(m);
|
_processing.Add(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,10 +64,19 @@ namespace Torch.Mod
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var m = _processing.Take();
|
MessageBase m;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m = _processing.Take();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
MyLog.Default.WriteLineAndConsole($"Processing message: {m.GetType().Name}");
|
MyLog.Default.WriteLineAndConsole($"Processing message: {m.GetType().Name}");
|
||||||
|
|
||||||
if (m is IncomingMessage)
|
if (m is IncomingMessage) //process incoming messages
|
||||||
{
|
{
|
||||||
MessageBase i;
|
MessageBase i;
|
||||||
try
|
try
|
||||||
@@ -78,50 +92,55 @@ namespace Torch.Mod
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TorchModCore.Debug)
|
||||||
|
MyAPIGateway.Utilities.ShowMessage("Torch", $"Received message of type {i.GetType().Name}");
|
||||||
|
|
||||||
if (MyAPIGateway.Multiplayer.IsServer)
|
if (MyAPIGateway.Multiplayer.IsServer)
|
||||||
i.ProcessServer();
|
i.ProcessServer();
|
||||||
else
|
else
|
||||||
i.ProcessClient();
|
i.ProcessClient();
|
||||||
}
|
}
|
||||||
else
|
else //process outgoing messages
|
||||||
{
|
{
|
||||||
|
if (TorchModCore.Debug)
|
||||||
|
MyAPIGateway.Utilities.ShowMessage("Torch", $"Sending message of type {m.GetType().Name}");
|
||||||
|
|
||||||
var b = MyAPIGateway.Utilities.SerializeToBinary(m);
|
var b = MyAPIGateway.Utilities.SerializeToBinary(m);
|
||||||
m.CompressedData = MyCompression.Compress(b);
|
m.CompressedData = MyCompression.Compress(b);
|
||||||
|
|
||||||
MyAPIGateway.Utilities.InvokeOnGameThread(() =>
|
switch (m.TargetType)
|
||||||
{
|
{
|
||||||
|
case MessageTarget.Single:
|
||||||
|
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, m.Target);
|
||||||
|
break;
|
||||||
|
case MessageTarget.Server:
|
||||||
|
MyAPIGateway.Multiplayer.SendMessageToServer(NET_ID, m.CompressedData);
|
||||||
|
break;
|
||||||
|
case MessageTarget.AllClients:
|
||||||
|
MyAPIGateway.Players.GetPlayers(_playerCache);
|
||||||
|
foreach (var p in _playerCache)
|
||||||
|
{
|
||||||
|
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId)
|
||||||
|
continue;
|
||||||
|
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, p.SteamUserId);
|
||||||
|
}
|
||||||
|
|
||||||
switch (m.TargetType)
|
break;
|
||||||
{
|
case MessageTarget.AllExcept:
|
||||||
case MessageTarget.Single:
|
MyAPIGateway.Players.GetPlayers(_playerCache);
|
||||||
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, m.Target);
|
foreach (var p in _playerCache)
|
||||||
break;
|
{
|
||||||
case MessageTarget.Server:
|
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId || m.Ignore.Contains(p.SteamUserId))
|
||||||
MyAPIGateway.Multiplayer.SendMessageToServer(NET_ID, m.CompressedData);
|
continue;
|
||||||
break;
|
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, p.SteamUserId);
|
||||||
case MessageTarget.AllClients:
|
}
|
||||||
MyAPIGateway.Players.GetPlayers(_playerCache);
|
|
||||||
foreach (var p in _playerCache)
|
break;
|
||||||
{
|
default:
|
||||||
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId)
|
throw new Exception();
|
||||||
continue;
|
}
|
||||||
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, p.SteamUserId);
|
|
||||||
}
|
_playerCache.Clear();
|
||||||
break;
|
|
||||||
case MessageTarget.AllExcept:
|
|
||||||
MyAPIGateway.Players.GetPlayers(_playerCache);
|
|
||||||
foreach (var p in _playerCache)
|
|
||||||
{
|
|
||||||
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId || m.Ignore.Contains(p.SteamUserId))
|
|
||||||
continue;
|
|
||||||
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, p.SteamUserId);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
_playerCache.Clear();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -130,7 +149,7 @@ namespace Torch.Mod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MyLog.Default.WriteLineAndConsole("TORCH MOD: COMMUNICATION THREAD: EXIT SIGNAL RECEIVED!");
|
MyLog.Default.WriteLineAndConsole("TORCH MOD: INFO: Communication thread shut down successfully! THIS IS NOT AN ERROR");
|
||||||
//exit signal received. Clean everything and GTFO
|
//exit signal received. Clean everything and GTFO
|
||||||
_processing?.Dispose();
|
_processing?.Dispose();
|
||||||
_processing = null;
|
_processing = null;
|
||||||
|
@@ -10,6 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Messages\IncomingMessage.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Messages\IncomingMessage.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Messages\JoinServerMessage.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Messages\NotificationMessage.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Messages\NotificationMessage.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Messages\DialogMessage.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Messages\DialogMessage.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Messages\MessageBase.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Messages\MessageBase.cs" />
|
||||||
|
@@ -3,6 +3,7 @@ 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.ModAPI;
|
||||||
using VRage.Game.Components;
|
using VRage.Game.Components;
|
||||||
|
|
||||||
namespace Torch.Mod
|
namespace Torch.Mod
|
||||||
@@ -12,6 +13,7 @@ namespace Torch.Mod
|
|||||||
{
|
{
|
||||||
public const ulong MOD_ID = 1406994352;
|
public const ulong MOD_ID = 1406994352;
|
||||||
private static bool _init;
|
private static bool _init;
|
||||||
|
public static bool Debug;
|
||||||
|
|
||||||
public override void UpdateAfterSimulation()
|
public override void UpdateAfterSimulation()
|
||||||
{
|
{
|
||||||
@@ -20,12 +22,24 @@ namespace Torch.Mod
|
|||||||
|
|
||||||
_init = true;
|
_init = true;
|
||||||
ModCommunication.Register();
|
ModCommunication.Register();
|
||||||
|
MyAPIGateway.Utilities.MessageEntered += Utilities_MessageEntered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Utilities_MessageEntered(string messageText, ref bool sendToOthers)
|
||||||
|
{
|
||||||
|
if (messageText == "@!debug")
|
||||||
|
{
|
||||||
|
Debug = !Debug;
|
||||||
|
MyAPIGateway.Utilities.ShowMessage("Torch", $"Debug: {Debug}");
|
||||||
|
sendToOthers = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UnloadData()
|
protected override void UnloadData()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
MyAPIGateway.Utilities.MessageEntered -= Utilities_MessageEntered;
|
||||||
ModCommunication.Unregister();
|
ModCommunication.Unregister();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
@@ -92,6 +92,7 @@
|
|||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Include="app.config" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
11
Torch.Server.Tests/app.config
Normal file
11
Torch.Server.Tests/app.config
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="protobuf-net" publicKeyToken="257b51d87d2e4d67" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-2.4.0.0" newVersion="2.4.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
@@ -16,7 +16,6 @@ using NLog.Targets;
|
|||||||
using Sandbox.Engine.Utils;
|
using Sandbox.Engine.Utils;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
using VRage.FileSystem;
|
using VRage.FileSystem;
|
||||||
using VRage.Library.Exceptions;
|
|
||||||
|
|
||||||
namespace Torch.Server
|
namespace Torch.Server
|
||||||
{
|
{
|
||||||
@@ -55,6 +54,15 @@ quit";
|
|||||||
AppDomain.CurrentDomain.UnhandledException += HandleException;
|
AppDomain.CurrentDomain.UnhandledException += HandleException;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
//enables logging debug messages when built in debug mode. Amazing.
|
||||||
|
LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Debug, "main");
|
||||||
|
LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Debug, "console");
|
||||||
|
LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Debug, "wpf");
|
||||||
|
LogManager.ReconfigExistingLoggers();
|
||||||
|
Log.Debug("Debug logging enabled.");
|
||||||
|
#endif
|
||||||
|
|
||||||
// This is what happens when Keen is bad and puts extensions into the System namespace.
|
// This is what happens when Keen is bad and puts extensions into the System namespace.
|
||||||
if (!Enumerable.Contains(args, "-noupdate"))
|
if (!Enumerable.Contains(args, "-noupdate"))
|
||||||
RunSteamCmd();
|
RunSteamCmd();
|
||||||
@@ -62,10 +70,30 @@ quit";
|
|||||||
var basePath = new FileInfo(typeof(Program).Assembly.Location).Directory.ToString();
|
var basePath = new FileInfo(typeof(Program).Assembly.Location).Directory.ToString();
|
||||||
var apiSource = Path.Combine(basePath, "DedicatedServer64", "steam_api64.dll");
|
var apiSource = Path.Combine(basePath, "DedicatedServer64", "steam_api64.dll");
|
||||||
var apiTarget = Path.Combine(basePath, "steam_api64.dll");
|
var apiTarget = Path.Combine(basePath, "steam_api64.dll");
|
||||||
|
|
||||||
if (!File.Exists(apiTarget))
|
if (!File.Exists(apiTarget))
|
||||||
|
{
|
||||||
File.Copy(apiSource, apiTarget);
|
File.Copy(apiSource, apiTarget);
|
||||||
|
}
|
||||||
|
else if (File.GetLastWriteTime(apiTarget) < File.GetLastWriteTime(apiSource))
|
||||||
|
{
|
||||||
|
File.Delete(apiTarget);
|
||||||
|
File.Copy(apiSource, apiTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
var havokSource = Path.Combine(basePath, "DedicatedServer64", "Havok.dll");
|
||||||
|
var havokTarget = Path.Combine(basePath, "Havok.dll");
|
||||||
|
|
||||||
|
if (!File.Exists(havokTarget))
|
||||||
|
{
|
||||||
|
File.Copy(havokSource, havokTarget);
|
||||||
|
}
|
||||||
|
else if (File.GetLastWriteTime(havokTarget) < File.GetLastWriteTime(havokSource))
|
||||||
|
{
|
||||||
|
File.Delete(havokTarget);
|
||||||
|
File.Copy(havokSource, havokTarget);
|
||||||
|
}
|
||||||
|
|
||||||
_config = InitConfig();
|
_config = InitConfig();
|
||||||
if (!_config.Parse(args))
|
if (!_config.Parse(args))
|
||||||
return false;
|
return false;
|
||||||
@@ -97,28 +125,34 @@ quit";
|
|||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
_server = new TorchServer(_config);
|
_server = new TorchServer(_config);
|
||||||
var init = Task.Run(() => _server.Init()).ContinueWith(x =>
|
|
||||||
{
|
|
||||||
if (!x.IsFaulted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Log.Error("Error initializing server.");
|
if (_config.NoGui)
|
||||||
LogException(x.Exception);
|
|
||||||
});
|
|
||||||
if (!_config.NoGui)
|
|
||||||
{
|
{
|
||||||
if (_config.Autostart)
|
_server.Init();
|
||||||
init.ContinueWith(x => _server.Start());
|
_server.Start();
|
||||||
|
|
||||||
Log.Info("Showing UI");
|
|
||||||
Console.SetOut(TextWriter.Null);
|
|
||||||
NativeMethods.FreeConsole();
|
|
||||||
new TorchUI(_server).ShowDialog();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
init.Wait();
|
#if !DEBUG
|
||||||
_server.Start();
|
if (!_config.IndependentConsole)
|
||||||
|
{
|
||||||
|
Console.SetOut(TextWriter.Null);
|
||||||
|
NativeMethods.FreeConsole();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var gameThread = new Thread(() =>
|
||||||
|
{
|
||||||
|
_server.Init();
|
||||||
|
|
||||||
|
if (_config.Autostart)
|
||||||
|
_server.Start();
|
||||||
|
});
|
||||||
|
|
||||||
|
gameThread.Start();
|
||||||
|
|
||||||
|
var ui = new TorchUI(_server);
|
||||||
|
ui.ShowDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,9 +198,10 @@ quit";
|
|||||||
File.Delete(STEAMCMD_ZIP);
|
File.Delete(STEAMCMD_ZIP);
|
||||||
log.Info("SteamCMD downloaded successfully!");
|
log.Info("SteamCMD downloaded successfully!");
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
log.Error("Failed to download SteamCMD, unable to update the DS.");
|
log.Error("Failed to download SteamCMD, unable to update the DS.");
|
||||||
|
log.Error(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,43 +215,53 @@ quit";
|
|||||||
StandardOutputEncoding = Encoding.ASCII
|
StandardOutputEncoding = Encoding.ASCII
|
||||||
};
|
};
|
||||||
var cmd = Process.Start(steamCmdProc);
|
var cmd = Process.Start(steamCmdProc);
|
||||||
|
|
||||||
// ReSharper disable once PossibleNullReferenceException
|
// ReSharper disable once PossibleNullReferenceException
|
||||||
while (!cmd.HasExited)
|
while (!cmd.HasExited)
|
||||||
{
|
{
|
||||||
log.Info(cmd.StandardOutput.ReadLine());
|
log.Info(cmd.StandardOutput.ReadToEnd());
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogException(Exception ex)
|
private void LogException(Exception ex)
|
||||||
{
|
{
|
||||||
|
if (ex is AggregateException ag)
|
||||||
|
{
|
||||||
|
foreach (var e in ag.InnerExceptions)
|
||||||
|
LogException(e);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Fatal(ex);
|
||||||
|
|
||||||
|
if (ex is ReflectionTypeLoadException extl)
|
||||||
|
{
|
||||||
|
foreach (var exl in extl.LoaderExceptions)
|
||||||
|
LogException(exl);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ex.InnerException != null)
|
if (ex.InnerException != null)
|
||||||
{
|
{
|
||||||
LogException(ex.InnerException);
|
LogException(ex.InnerException);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Fatal(ex);
|
|
||||||
|
|
||||||
if (ex is ReflectionTypeLoadException exti)
|
|
||||||
foreach (Exception exl in exti.LoaderExceptions)
|
|
||||||
LogException(exl);
|
|
||||||
|
|
||||||
if (ex is AggregateException ag)
|
|
||||||
foreach (Exception e in ag.InnerExceptions)
|
|
||||||
LogException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleException(object sender, UnhandledExceptionEventArgs e)
|
private void HandleException(object sender, UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
|
_server.FatalException = true;
|
||||||
var ex = (Exception)e.ExceptionObject;
|
var ex = (Exception)e.ExceptionObject;
|
||||||
LogException(ex);
|
LogException(ex);
|
||||||
if (MyFakes.ENABLE_MINIDUMP_SENDING)
|
if (MyFakes.ENABLE_MINIDUMP_SENDING)
|
||||||
{
|
{
|
||||||
string path = Path.Combine(MyFileSystem.UserDataPath, "Minidump.dmp");
|
string path = Path.Combine(MyFileSystem.UserDataPath, "Minidump.dmp");
|
||||||
Log.Info($"Generating minidump at {path}");
|
Log.Info($"Generating minidump at {path}");
|
||||||
MyMiniDump.Options options = MyMiniDump.Options.WithProcessThreadData | MyMiniDump.Options.WithThreadInfo;
|
Log.Error("Keen broke the minidump, sorry.");
|
||||||
MyMiniDump.Write(path, options, MyMiniDump.ExceptionInfo.Present);
|
//MyMiniDump.Options options = MyMiniDump.Options.WithProcessThreadData | MyMiniDump.Options.WithThreadInfo;
|
||||||
|
//MyMiniDump.Write(path, options, MyMiniDump.ExceptionInfo.Present);
|
||||||
}
|
}
|
||||||
LogManager.Flush();
|
LogManager.Flush();
|
||||||
if (_config.RestartOnCrash)
|
if (_config.RestartOnCrash)
|
||||||
@@ -235,4 +280,4 @@ quit";
|
|||||||
Process.GetCurrentProcess().Kill();
|
Process.GetCurrentProcess().Kill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -14,6 +15,7 @@ using Sandbox.Game;
|
|||||||
using Sandbox.Game.Gui;
|
using Sandbox.Game.Gui;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
|
using Torch.Collections;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
using Torch.Mod;
|
using Torch.Mod;
|
||||||
using Torch.Server.ViewModels;
|
using Torch.Server.ViewModels;
|
||||||
@@ -69,8 +71,16 @@ namespace Torch.Server.Managers
|
|||||||
|
|
||||||
foreach (var f in worldFolders)
|
foreach (var f in worldFolders)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(f) && File.Exists(Path.Combine(f, "Sandbox.sbc")))
|
try
|
||||||
DedicatedConfig.Worlds.Add(new WorldViewModel(f));
|
{
|
||||||
|
if (!string.IsNullOrEmpty(f) && File.Exists(Path.Combine(f, "Sandbox.sbc")))
|
||||||
|
DedicatedConfig.Worlds.Add(new WorldViewModel(f));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to load world at path: " + f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DedicatedConfig.Worlds.Count == 0)
|
if (DedicatedConfig.Worlds.Count == 0)
|
||||||
@@ -87,14 +97,32 @@ namespace Torch.Server.Managers
|
|||||||
public void SelectWorld(string worldPath, bool modsOnly = true)
|
public void SelectWorld(string worldPath, bool modsOnly = true)
|
||||||
{
|
{
|
||||||
DedicatedConfig.LoadWorld = worldPath;
|
DedicatedConfig.LoadWorld = worldPath;
|
||||||
DedicatedConfig.SelectedWorld = DedicatedConfig.Worlds.FirstOrDefault(x => x.WorldPath == worldPath);
|
|
||||||
|
var worldInfo = DedicatedConfig.Worlds.FirstOrDefault(x => x.WorldPath == worldPath);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (worldInfo?.Checkpoint == null)
|
||||||
|
{
|
||||||
|
worldInfo = new WorldViewModel(worldPath);
|
||||||
|
DedicatedConfig.Worlds.Add(worldInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to load world at path: " + worldPath);
|
||||||
|
DedicatedConfig.LoadWorld = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DedicatedConfig.SelectedWorld = worldInfo;
|
||||||
if (DedicatedConfig.SelectedWorld?.Checkpoint != null)
|
if (DedicatedConfig.SelectedWorld?.Checkpoint != null)
|
||||||
{
|
{
|
||||||
DedicatedConfig.Mods.Clear();
|
DedicatedConfig.Mods.Clear();
|
||||||
//remove the Torch mod to avoid running multiple copies of it
|
//remove the Torch mod to avoid running multiple copies of it
|
||||||
DedicatedConfig.SelectedWorld.Checkpoint.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
DedicatedConfig.SelectedWorld.WorldConfiguration.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
||||||
foreach (var m in DedicatedConfig.SelectedWorld.Checkpoint.Mods)
|
foreach (var m in DedicatedConfig.SelectedWorld.WorldConfiguration.Mods)
|
||||||
DedicatedConfig.Mods.Add(m.PublishedFileId);
|
DedicatedConfig.Mods.Add(new ModItemInfo(m));
|
||||||
|
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,9 +134,10 @@ namespace Torch.Server.Managers
|
|||||||
{
|
{
|
||||||
DedicatedConfig.Mods.Clear();
|
DedicatedConfig.Mods.Clear();
|
||||||
//remove the Torch mod to avoid running multiple copies of it
|
//remove the Torch mod to avoid running multiple copies of it
|
||||||
DedicatedConfig.SelectedWorld.Checkpoint.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
DedicatedConfig.SelectedWorld.WorldConfiguration.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
||||||
foreach (var m in DedicatedConfig.SelectedWorld.Checkpoint.Mods)
|
foreach (var m in DedicatedConfig.SelectedWorld.WorldConfiguration.Mods)
|
||||||
DedicatedConfig.Mods.Add(m.PublishedFileId);
|
DedicatedConfig.Mods.Add(new ModItemInfo(m));
|
||||||
|
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,17 +148,16 @@ namespace Torch.Server.Managers
|
|||||||
|
|
||||||
private void ImportWorldConfig(WorldViewModel world, bool modsOnly = true)
|
private void ImportWorldConfig(WorldViewModel world, bool modsOnly = true)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var mods = new MtObservableList<ModItemInfo>();
|
||||||
foreach (var mod in world.Checkpoint.Mods)
|
foreach (var mod in world.WorldConfiguration.Mods)
|
||||||
sb.AppendLine(mod.PublishedFileId.ToString());
|
mods.Add(new ModItemInfo(mod));
|
||||||
|
DedicatedConfig.Mods = mods;
|
||||||
DedicatedConfig.Mods = world.Checkpoint.Mods.Select(x => x.PublishedFileId).ToList();
|
|
||||||
|
|
||||||
|
|
||||||
Log.Debug("Loaded mod list from world");
|
Log.Debug("Loaded mod list from world");
|
||||||
|
|
||||||
if (!modsOnly)
|
if (!modsOnly)
|
||||||
DedicatedConfig.SessionSettings = world.Checkpoint.Settings;
|
DedicatedConfig.SessionSettings = world.WorldConfiguration.Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ImportWorldConfig(bool modsOnly = true)
|
private void ImportWorldConfig(bool modsOnly = true)
|
||||||
@@ -151,7 +179,10 @@ namespace Torch.Server.Managers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DedicatedConfig.Mods = checkpoint.Mods.Select(x => x.PublishedFileId).ToList();
|
var mods = new MtObservableList<ModItemInfo>();
|
||||||
|
foreach (var mod in checkpoint.Mods)
|
||||||
|
mods.Add(new ModItemInfo(mod));
|
||||||
|
DedicatedConfig.Mods = mods;
|
||||||
|
|
||||||
Log.Debug("Loaded mod list from world");
|
Log.Debug("Loaded mod list from world");
|
||||||
|
|
||||||
@@ -167,29 +198,33 @@ namespace Torch.Server.Managers
|
|||||||
|
|
||||||
public void SaveConfig()
|
public void SaveConfig()
|
||||||
{
|
{
|
||||||
|
if (((TorchServer)Torch).HasRun)
|
||||||
|
{
|
||||||
|
Log.Warn("Checkpoint cache is stale, not saving dedicated config.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DedicatedConfig.Save(Path.Combine(Torch.Config.InstancePath, CONFIG_NAME));
|
DedicatedConfig.Save(Path.Combine(Torch.Config.InstancePath, CONFIG_NAME));
|
||||||
Log.Info("Saved dedicated config.");
|
Log.Info("Saved dedicated config.");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var sandboxPath = Path.Combine(DedicatedConfig.LoadWorld, "Sandbox.sbc");
|
var world = DedicatedConfig.Worlds.FirstOrDefault(x => x.WorldPath == DedicatedConfig.LoadWorld) ?? new WorldViewModel(DedicatedConfig.LoadWorld);
|
||||||
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
|
||||||
if (checkpoint == null)
|
world.Checkpoint.SessionName = DedicatedConfig.WorldName;
|
||||||
|
world.WorldConfiguration.Settings = DedicatedConfig.SessionSettings;
|
||||||
|
world.WorldConfiguration.Mods.Clear();
|
||||||
|
|
||||||
|
foreach (var mod in DedicatedConfig.Mods)
|
||||||
{
|
{
|
||||||
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {Torch.Config.InstancePath})");
|
var savedMod = new MyObjectBuilder_Checkpoint.ModItem(mod.Name, mod.PublishedFileId, mod.FriendlyName);
|
||||||
return;
|
savedMod.IsDependency = mod.IsDependency;
|
||||||
|
world.WorldConfiguration.Mods.Add(savedMod);
|
||||||
}
|
}
|
||||||
|
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||||
|
|
||||||
checkpoint.SessionName = DedicatedConfig.WorldName;
|
world.SaveSandbox();
|
||||||
checkpoint.Settings = DedicatedConfig.SessionSettings;
|
|
||||||
checkpoint.Mods.Clear();
|
|
||||||
|
|
||||||
foreach (var modId in DedicatedConfig.Mods)
|
|
||||||
checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId));
|
|
||||||
|
|
||||||
MyObjectBuilderSerializer.SerializeXML(sandboxPath, false, checkpoint);
|
|
||||||
|
|
||||||
//MyLocalCache.SaveCheckpoint(checkpoint, DedicatedConfig.LoadWorld);
|
|
||||||
Log.Info("Saved world config.");
|
Log.Info("Saved world config.");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -223,35 +258,63 @@ namespace Torch.Server.Managers
|
|||||||
public string WorldPath { get; }
|
public string WorldPath { get; }
|
||||||
public long WorldSizeKB { get; }
|
public long WorldSizeKB { get; }
|
||||||
private string _checkpointPath;
|
private string _checkpointPath;
|
||||||
|
private string _worldConfigPath;
|
||||||
public CheckpointViewModel Checkpoint { get; private set; }
|
public CheckpointViewModel Checkpoint { get; private set; }
|
||||||
|
|
||||||
|
public WorldConfigurationViewModel WorldConfiguration { get; private set; }
|
||||||
|
|
||||||
public WorldViewModel(string worldPath)
|
public WorldViewModel(string worldPath)
|
||||||
{
|
{
|
||||||
WorldPath = worldPath;
|
try
|
||||||
WorldSizeKB = new DirectoryInfo(worldPath).GetFiles().Sum(x => x.Length) / 1024;
|
{
|
||||||
_checkpointPath = Path.Combine(WorldPath, "Sandbox.sbc");
|
WorldPath = worldPath;
|
||||||
FolderName = Path.GetFileName(worldPath);
|
WorldSizeKB = new DirectoryInfo(worldPath).GetFiles().Sum(x => x.Length) / 1024;
|
||||||
BeginLoadCheckpoint();
|
_checkpointPath = Path.Combine(WorldPath, "Sandbox.sbc");
|
||||||
|
_worldConfigPath = Path.Combine(WorldPath, "Sandbox_config.sbc");
|
||||||
|
FolderName = Path.GetFileName(worldPath);
|
||||||
|
LoadSandbox();
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
Log.Error($"World view model failed to load the path: {worldPath} Please ensure this is a valid path.");
|
||||||
|
throw; //rethrow to be handled further up the stack
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveCheckpointAsync()
|
public void SaveSandbox()
|
||||||
{
|
{
|
||||||
await Task.Run(() =>
|
using (var f = File.Open(_checkpointPath, FileMode.Create))
|
||||||
{
|
MyObjectBuilderSerializer.SerializeXML(f, Checkpoint);
|
||||||
using (var f = File.Open(_checkpointPath, FileMode.Create))
|
|
||||||
MyObjectBuilderSerializer.SerializeXML(f, Checkpoint);
|
using (var f = File.Open(_worldConfigPath, FileMode.Create))
|
||||||
});
|
MyObjectBuilderSerializer.SerializeXML(f, WorldConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BeginLoadCheckpoint()
|
private void LoadSandbox()
|
||||||
{
|
{
|
||||||
//Task.Run(() =>
|
MyObjectBuilderSerializer.DeserializeXML(_checkpointPath, out MyObjectBuilder_Checkpoint checkpoint);
|
||||||
|
Checkpoint = new CheckpointViewModel(checkpoint);
|
||||||
|
|
||||||
|
// migrate old saves
|
||||||
|
if (File.Exists(_worldConfigPath))
|
||||||
{
|
{
|
||||||
Log.Info($"Preloading checkpoint {_checkpointPath}");
|
MyObjectBuilderSerializer.DeserializeXML(_worldConfigPath, out MyObjectBuilder_WorldConfiguration worldConfig);
|
||||||
MyObjectBuilderSerializer.DeserializeXML(_checkpointPath, out MyObjectBuilder_Checkpoint checkpoint);
|
WorldConfiguration = new WorldConfigurationViewModel(worldConfig);
|
||||||
Checkpoint = new CheckpointViewModel(checkpoint);
|
}
|
||||||
OnPropertyChanged(nameof(Checkpoint));
|
else
|
||||||
}//);
|
{
|
||||||
|
WorldConfiguration = new WorldConfigurationViewModel(new MyObjectBuilder_WorldConfiguration
|
||||||
|
{
|
||||||
|
Mods = checkpoint.Mods,
|
||||||
|
Settings = checkpoint.Settings
|
||||||
|
});
|
||||||
|
|
||||||
|
checkpoint.Mods = null;
|
||||||
|
checkpoint.Settings = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(Checkpoint));
|
||||||
|
OnPropertyChanged(nameof(WorldConfiguration));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -209,26 +209,21 @@ namespace Torch.Server.Managers
|
|||||||
//Largely copied from SE
|
//Largely copied from SE
|
||||||
private void ValidateAuthTicketResponse(ulong steamId, JoinResult response, ulong steamOwner)
|
private void ValidateAuthTicketResponse(ulong steamId, JoinResult response, ulong steamOwner)
|
||||||
{
|
{
|
||||||
//SteamNetworking.GetP2PSessionState(new CSteamID(steamId), out P2PSessionState_t state);
|
var state = new MyP2PSessionState();
|
||||||
//state.GetRemoteIP();
|
MySteamServiceWrapper.Static.Peer2Peer.GetSessionState(steamId, ref state);
|
||||||
MyP2PSessionState statehack = new MyP2PSessionState();
|
var ip = new IPAddress(BitConverter.GetBytes(state.RemoteIP).Reverse().ToArray());
|
||||||
VRage.Steam.MySteamService.Static.Peer2Peer.GetSessionState(steamId, ref statehack);
|
|
||||||
var ip = new IPAddress(BitConverter.GetBytes(statehack.RemoteIP).Reverse().ToArray());
|
|
||||||
|
|
||||||
Torch.CurrentSession.KeenSession.PromotedUsers.TryGetValue(steamId, out MyPromoteLevel promoteLevel);
|
Torch.CurrentSession.KeenSession.PromotedUsers.TryGetValue(steamId, out MyPromoteLevel promoteLevel);
|
||||||
|
|
||||||
_log.Debug($"ValidateAuthTicketResponse(user={steamId}, response={response}, owner={steamOwner}, permissions={promoteLevel})");
|
_log.Debug($"ValidateAuthTicketResponse(user={steamId}, response={response}, owner={steamOwner}, permissions={promoteLevel})");
|
||||||
|
|
||||||
_log.Info($"Connection attempt by {steamId} from {ip}");
|
_log.Info($"Connection attempt by {steamId} from {ip}");
|
||||||
// TODO implement IP bans
|
|
||||||
var config = (TorchConfig) Torch.Config;
|
if (Players.ContainsKey(steamId))
|
||||||
if (config.EnableWhitelist && !config.Whitelist.Contains(steamId))
|
|
||||||
{
|
{
|
||||||
_log.Warn($"Rejecting user {steamId} because they are not whitelisted in Torch.cfg.");
|
_log.Warn($"Player {steamId} has already joined!");
|
||||||
UserRejected(steamId, JoinResult.NotInGroup);
|
UserRejected(steamId, JoinResult.AlreadyJoined);
|
||||||
}
|
}
|
||||||
else if(config.EnableReservedSlots && config.ReservedPlayers.Contains(steamId))
|
|
||||||
UserAccepted(steamId);
|
|
||||||
else if (Torch.CurrentSession.KeenSession.OnlineMode == MyOnlineModeEnum.OFFLINE &&
|
else if (Torch.CurrentSession.KeenSession.OnlineMode == MyOnlineModeEnum.OFFLINE &&
|
||||||
promoteLevel < MyPromoteLevel.Admin)
|
promoteLevel < MyPromoteLevel.Admin)
|
||||||
{
|
{
|
||||||
@@ -252,40 +247,48 @@ namespace Torch.Server.Managers
|
|||||||
|
|
||||||
private void RunEvent(ValidateAuthTicketEvent info)
|
private void RunEvent(ValidateAuthTicketEvent info)
|
||||||
{
|
{
|
||||||
MultiplayerManagerDedicatedEventShim.RaiseValidateAuthTicket(ref info);
|
JoinResult internalAuth;
|
||||||
|
|
||||||
if (info.FutureVerdict == null)
|
|
||||||
|
if (IsBanned(info.SteamOwner) || IsBanned(info.SteamID))
|
||||||
|
internalAuth = JoinResult.BannedByAdmins;
|
||||||
|
else if (_isClientKicked(MyMultiplayer.Static, info.SteamID) ||
|
||||||
|
_isClientKicked(MyMultiplayer.Static, info.SteamOwner))
|
||||||
|
internalAuth = JoinResult.KickedRecently;
|
||||||
|
else if (info.SteamResponse == JoinResult.OK)
|
||||||
{
|
{
|
||||||
if (IsBanned(info.SteamOwner) || IsBanned(info.SteamID))
|
var config = (TorchConfig) Torch.Config;
|
||||||
CommitVerdict(info.SteamID, JoinResult.BannedByAdmins);
|
if (config.EnableWhitelist && !config.Whitelist.Contains(info.SteamID))
|
||||||
else if (_isClientKicked(MyMultiplayer.Static, info.SteamID) ||
|
|
||||||
_isClientKicked(MyMultiplayer.Static, info.SteamOwner))
|
|
||||||
CommitVerdict(info.SteamID, JoinResult.KickedRecently);
|
|
||||||
else if (info.SteamResponse == JoinResult.OK)
|
|
||||||
{
|
{
|
||||||
//Admins can bypass member limit
|
_log.Warn($"Rejecting user {info.SteamID} because they are not whitelisted in Torch.cfg.");
|
||||||
if (MySandboxGame.ConfigDedicated.Administrators.Contains(info.SteamID.ToString()) ||
|
internalAuth = JoinResult.NotInGroup;
|
||||||
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(info.SteamID)))
|
|
||||||
CommitVerdict(info.SteamID, JoinResult.OK);
|
|
||||||
//Server counts as a client, so subtract 1 from MemberCount
|
|
||||||
else if (MyMultiplayer.Static.MemberLimit > 0 &&
|
|
||||||
MyMultiplayer.Static.MemberCount - 1 >= MyMultiplayer.Static.MemberLimit)
|
|
||||||
CommitVerdict(info.SteamID, JoinResult.ServerFull);
|
|
||||||
else if (MySandboxGame.ConfigDedicated.GroupID == 0uL)
|
|
||||||
CommitVerdict(info.SteamID, JoinResult.OK);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (MySandboxGame.ConfigDedicated.GroupID == info.Group && (info.Member || info.Officer))
|
|
||||||
CommitVerdict(info.SteamID, JoinResult.OK);
|
|
||||||
else
|
|
||||||
CommitVerdict(info.SteamID, JoinResult.NotInGroup);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (MySandboxGame.ConfigDedicated.Reserved.Contains(info.SteamID))
|
||||||
|
internalAuth = JoinResult.OK;
|
||||||
|
//Admins can bypass member limit
|
||||||
|
else if (MySandboxGame.ConfigDedicated.Administrators.Contains(info.SteamID.ToString()) ||
|
||||||
|
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(info.SteamID)))
|
||||||
|
internalAuth = JoinResult.OK;
|
||||||
|
//Server counts as a client, so subtract 1 from MemberCount
|
||||||
|
else if (MyMultiplayer.Static.MemberLimit > 0 &&
|
||||||
|
MyMultiplayer.Static.MemberCount - 1 >= MyMultiplayer.Static.MemberLimit)
|
||||||
|
internalAuth = JoinResult.ServerFull;
|
||||||
|
else if (MySandboxGame.ConfigDedicated.GroupID == 0uL)
|
||||||
|
internalAuth = JoinResult.OK;
|
||||||
else
|
else
|
||||||
CommitVerdict(info.SteamID, info.SteamResponse);
|
{
|
||||||
|
if (MySandboxGame.ConfigDedicated.GroupID == info.Group && (info.Member || info.Officer))
|
||||||
return;
|
internalAuth = JoinResult.OK;
|
||||||
|
else
|
||||||
|
internalAuth = JoinResult.NotInGroup;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
internalAuth = info.SteamResponse;
|
||||||
|
|
||||||
|
info.FutureVerdict = Task.FromResult(internalAuth);
|
||||||
|
|
||||||
|
MultiplayerManagerDedicatedEventShim.RaiseValidateAuthTicket(ref info);
|
||||||
|
|
||||||
info.FutureVerdict.ContinueWith((task) =>
|
info.FutureVerdict.ContinueWith((task) =>
|
||||||
{
|
{
|
||||||
@@ -339,4 +342,4 @@ namespace Torch.Server.Managers
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
40
Torch.Server/Managers/RemoteAPIManager.cs
Normal file
40
Torch.Server/Managers/RemoteAPIManager.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using NLog;
|
||||||
|
using Sandbox;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.Managers;
|
||||||
|
using VRage.Dedicated.RemoteAPI;
|
||||||
|
|
||||||
|
namespace Torch.Server.Managers
|
||||||
|
{
|
||||||
|
public class RemoteAPIManager : Manager
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RemoteAPIManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
Torch.GameStateChanged += TorchOnGameStateChanged;
|
||||||
|
base.Attach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
Torch.GameStateChanged -= TorchOnGameStateChanged;
|
||||||
|
base.Detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TorchOnGameStateChanged(MySandboxGame game, TorchGameState newstate)
|
||||||
|
{
|
||||||
|
if (newstate == TorchGameState.Loading && MySandboxGame.ConfigDedicated.RemoteApiEnabled && !string.IsNullOrEmpty(MySandboxGame.ConfigDedicated.RemoteSecurityKey))
|
||||||
|
{
|
||||||
|
var myRemoteServer = new MyRemoteServer(MySandboxGame.ConfigDedicated.RemoteApiPort, MySandboxGame.ConfigDedicated.RemoteSecurityKey);
|
||||||
|
LogManager.GetCurrentClassLogger().Info($"Remote API started on port {myRemoteServer.Port}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
Torch.Server/Patches/WorldLoadExceptionPatch.cs
Normal file
48
Torch.Server/Patches/WorldLoadExceptionPatch.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox;
|
||||||
|
using Torch.Managers.PatchManager;
|
||||||
|
using Torch.Managers.PatchManager.MSIL;
|
||||||
|
|
||||||
|
namespace Torch.Patches
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Patches MySandboxGame.InitQuickLaunch to rethrow exceptions caught during session load.
|
||||||
|
/// </summary>
|
||||||
|
[PatchShim]
|
||||||
|
public static class WorldLoadExceptionPatch
|
||||||
|
{
|
||||||
|
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public static void Patch(PatchContext ctx)
|
||||||
|
{
|
||||||
|
ctx.GetPattern(typeof(MySandboxGame).GetMethod("InitQuickLaunch", BindingFlags.Instance | BindingFlags.NonPublic))
|
||||||
|
.Transpilers.Add(typeof(WorldLoadExceptionPatch).GetMethod(nameof(Transpile), BindingFlags.Static | BindingFlags.NonPublic));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<MsilInstruction> Transpile(IEnumerable<MsilInstruction> method)
|
||||||
|
{
|
||||||
|
var msil = method.ToList();
|
||||||
|
for (var i = 0; i < msil.Count; i++)
|
||||||
|
{
|
||||||
|
if (msil[i].TryCatchOperations.All(x => x.Type != MsilTryCatchOperationType.BeginClauseBlock))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (; i < msil.Count; i++)
|
||||||
|
{
|
||||||
|
if (msil[i].OpCode != OpCodes.Leave)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
msil[i] = new MsilInstruction(OpCodes.Rethrow);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,12 +2,15 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.ServiceProcess;
|
using System.ServiceProcess;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Microsoft.VisualBasic.Devices;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using NLog.Fluent;
|
||||||
using NLog.Targets;
|
using NLog.Targets;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
|
|
||||||
@@ -21,21 +24,46 @@ namespace Torch.Server
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
Target.Register<FlowDocumentTarget>("FlowDocument");
|
Target.Register<FlowDocumentTarget>("FlowDocument");
|
||||||
//Ensures that all the files are downloaded in the Torch directory.
|
//Ensures that all the files are downloaded in the Torch directory.
|
||||||
var workingDir = new FileInfo(typeof(Program).Assembly.Location).Directory.ToString();
|
var workingDir = new FileInfo(typeof(Program).Assembly.Location).Directory.ToString();
|
||||||
var binDir = Path.Combine(workingDir, "DedicatedServer64");
|
var binDir = Path.Combine(workingDir, "DedicatedServer64");
|
||||||
Directory.SetCurrentDirectory(workingDir);
|
Directory.SetCurrentDirectory(workingDir);
|
||||||
|
|
||||||
|
//HACK for block skins update
|
||||||
|
var badDlls = new[]
|
||||||
|
{
|
||||||
|
"System.Security.Principal.Windows.dll",
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var file in badDlls)
|
||||||
|
{
|
||||||
|
if (File.Exists(file))
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
var log = LogManager.GetCurrentClassLogger();
|
||||||
|
log.Error($"Error updating. Please delete the following files from the Torch root folder manually:\r\n{string.Join("\r\n", badDlls)}");
|
||||||
|
log.Error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!TorchLauncher.IsTorchWrapped())
|
if (!TorchLauncher.IsTorchWrapped())
|
||||||
{
|
{
|
||||||
TorchLauncher.Launch(Assembly.GetEntryAssembly().FullName, args, binDir);
|
TorchLauncher.Launch(Assembly.GetEntryAssembly().FullName, args, binDir);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Environment.UserInteractive)
|
// Breaks on Windows Server 2019
|
||||||
|
if (!new ComputerInfo().OSFullName.Contains("Server 2019") && !Environment.UserInteractive)
|
||||||
{
|
{
|
||||||
using (var service = new TorchService())
|
using (var service = new TorchService(args))
|
||||||
ServiceBase.Run(service);
|
ServiceBase.Run(service);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -47,4 +75,4 @@ namespace Torch.Server
|
|||||||
initializer.Run();
|
initializer.Run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -70,6 +70,9 @@
|
|||||||
<Reference Include="MahApps.Metro, Version=1.6.1.4, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="MahApps.Metro, Version=1.6.1.4, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\MahApps.Metro.1.6.1\lib\net45\MahApps.Metro.dll</HintPath>
|
<HintPath>..\packages\MahApps.Metro.1.6.1\lib\net45\MahApps.Metro.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Markdown.Xaml, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Markdown.Xaml.1.0.0\lib\net45\Markdown.Xaml.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Microsoft.CodeAnalysis, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
<Reference Include="Microsoft.CodeAnalysis, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.dll</HintPath>
|
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.dll</HintPath>
|
||||||
@@ -80,13 +83,25 @@
|
|||||||
<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">
|
<Reference Include="Microsoft.VisualBasic" />
|
||||||
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
<Reference Include="Microsoft.Win32.Registry, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Microsoft.Win32.Registry.4.4.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="mscorlib" />
|
||||||
|
<Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51">
|
||||||
|
<HintPath>..\GameBinaries\netstandard.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
|
||||||
|
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
</Reference>
|
</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.12\lib\net45\NLog.dll</HintPath>
|
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="protobuf-net, Version=2.4.0.0, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\protobuf-net.2.4.0\lib\net40\protobuf-net.dll</HintPath>
|
||||||
|
</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>
|
||||||
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
||||||
@@ -121,11 +136,24 @@
|
|||||||
<HintPath>..\GameBinaries\Steamworks.NET.dll</HintPath>
|
<HintPath>..\GameBinaries\Steamworks.NET.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.ComponentModel.Annotations, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
|
<HintPath>..\packages\System.ComponentModel.Annotations.4.5.0\lib\net461\System.ComponentModel.Annotations.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||||
|
<Reference Include="System.Configuration" />
|
||||||
<Reference Include="System.Configuration.Install" />
|
<Reference Include="System.Configuration.Install" />
|
||||||
<Reference Include="System.Data" />
|
<Reference Include="System.Data" />
|
||||||
<Reference Include="System.Drawing" />
|
<Reference Include="System.Drawing" />
|
||||||
<Reference Include="System.IO.Compression.FileSystem" />
|
<Reference Include="System.IO.Compression.FileSystem" />
|
||||||
|
<Reference Include="System.Runtime.Serialization" />
|
||||||
|
<Reference Include="System.Security.AccessControl, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\System.Security.AccessControl.4.4.0\lib\net461\System.Security.AccessControl.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Security.Principal.Windows, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\System.Security.Principal.Windows.4.4.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.ServiceModel" />
|
||||||
<Reference Include="System.ServiceProcess" />
|
<Reference Include="System.ServiceProcess" />
|
||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
@@ -180,15 +208,8 @@
|
|||||||
<HintPath>..\GameBinaries\VRage.Math.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Math.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Native, Version=0.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="VRage.Platform.Windows, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<HintPath>..\GameBinaries\VRage.Platform.Windows.dll</HintPath>
|
||||||
<HintPath>..\GameBinaries\VRage.Native.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="VRage.OpenVRWrapper, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\VRage.OpenVRWrapper.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Render, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="VRage.Render, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
@@ -225,9 +246,11 @@
|
|||||||
<Compile Include="Managers\InstanceManager.cs" />
|
<Compile Include="Managers\InstanceManager.cs" />
|
||||||
<Compile Include="Managers\MultiplayerManagerDedicatedEventShim.cs" />
|
<Compile Include="Managers\MultiplayerManagerDedicatedEventShim.cs" />
|
||||||
<Compile Include="Managers\MultiplayerManagerDedicatedPatchShim.cs" />
|
<Compile Include="Managers\MultiplayerManagerDedicatedPatchShim.cs" />
|
||||||
|
<Compile Include="Managers\RemoteAPIManager.cs" />
|
||||||
<Compile Include="NativeMethods.cs" />
|
<Compile Include="NativeMethods.cs" />
|
||||||
<Compile Include="Initializer.cs" />
|
<Compile Include="Initializer.cs" />
|
||||||
<Compile Include="Patches\PromotePatch.cs" />
|
<Compile Include="Patches\PromotePatch.cs" />
|
||||||
|
<Compile Include="Patches\WorldLoadExceptionPatch.cs" />
|
||||||
<Compile Include="Properties\Annotations.cs" />
|
<Compile Include="Properties\Annotations.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="TorchConfig.cs" />
|
<Compile Include="TorchConfig.cs" />
|
||||||
@@ -245,15 +268,29 @@
|
|||||||
<Compile Include="ViewModels\Entities\CharacterViewModel.cs" />
|
<Compile Include="ViewModels\Entities\CharacterViewModel.cs" />
|
||||||
<Compile Include="ViewModels\ConfigDedicatedViewModel.cs" />
|
<Compile Include="ViewModels\ConfigDedicatedViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Entities\EntityControlViewModel.cs" />
|
<Compile Include="ViewModels\Entities\EntityControlViewModel.cs" />
|
||||||
|
<Compile Include="ViewModels\ModItemInfo.cs" />
|
||||||
<Compile Include="ViewModels\SessionSettingsViewModel.cs" />
|
<Compile Include="ViewModels\SessionSettingsViewModel.cs" />
|
||||||
|
<Compile Include="ViewModels\WorldConfigurationViewModel.cs" />
|
||||||
<Compile Include="Views\Converters\DefinitionToIdConverter.cs" />
|
<Compile Include="Views\Converters\DefinitionToIdConverter.cs" />
|
||||||
<Compile Include="Views\Converters\BooleanAndConverter.cs" />
|
<Compile Include="Views\Converters\BooleanAndConverter.cs" />
|
||||||
<Compile Include="Views\Converters\ListConverter.cs" />
|
<Compile Include="Views\Converters\ListConverter.cs" />
|
||||||
<Compile Include="MultiTextWriter.cs" />
|
<Compile Include="MultiTextWriter.cs" />
|
||||||
<Compile Include="RichTextBoxWriter.cs" />
|
<Compile Include="RichTextBoxWriter.cs" />
|
||||||
|
<Compile Include="Views\Converters\ListConverterWorkshopId.cs" />
|
||||||
|
<Compile Include="Views\Converters\ModToIdConverter.cs" />
|
||||||
<Compile Include="Views\Entities\CharacterView.xaml.cs">
|
<Compile Include="Views\Entities\CharacterView.xaml.cs">
|
||||||
<DependentUpon>CharacterView.xaml</DependentUpon>
|
<DependentUpon>CharacterView.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Views\Extensions.cs" />
|
||||||
|
<Compile Include="Views\ModListControl.xaml.cs">
|
||||||
|
<DependentUpon>ModListControl.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Views\PluginBrowser.xaml.cs">
|
||||||
|
<DependentUpon>PluginBrowser.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Views\RoleEditor.xaml.cs">
|
||||||
|
<DependentUpon>RoleEditor.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Views\ThemeControl.xaml.cs">
|
<Compile Include="Views\ThemeControl.xaml.cs">
|
||||||
<DependentUpon>ThemeControl.xaml</DependentUpon>
|
<DependentUpon>ThemeControl.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -414,6 +451,14 @@
|
|||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Include="Views\ModListControl.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
<Page Include="Views\PluginBrowser.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
<Page Include="Views\PluginsControl.xaml">
|
<Page Include="Views\PluginsControl.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
@@ -430,6 +475,10 @@
|
|||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Include="Views\RoleEditor.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
<Page Include="Views\SessionSettingsView.xaml">
|
<Page Include="Views\SessionSettingsView.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
@@ -469,9 +518,10 @@
|
|||||||
<Install>false</Install>
|
<Install>false</Install>
|
||||||
</BootstrapperPackage>
|
</BootstrapperPackage>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"</PostBuildEvent>
|
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)" & copy "$(SolutionDir)NLog-user.config" "$(TargetDir)"</PostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
@@ -5,6 +5,7 @@ using System.Windows;
|
|||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using VRage.Game;
|
||||||
|
|
||||||
namespace Torch.Server
|
namespace Torch.Server
|
||||||
{
|
{
|
||||||
@@ -20,9 +21,38 @@ namespace Torch.Server
|
|||||||
[Arg("instancename", "The name of the Torch instance.")]
|
[Arg("instancename", "The name of the Torch instance.")]
|
||||||
public string InstanceName { get; set; }
|
public string InstanceName { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
private string _instancePath;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
[Arg("instancepath", "Server data folder where saves and mods are stored.")]
|
[Arg("instancepath", "Server data folder where saves and mods are stored.")]
|
||||||
public string InstancePath { get; set; }
|
public string InstancePath
|
||||||
|
{
|
||||||
|
get => _instancePath;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if(String.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
_instancePath = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(value.Contains("\""))
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
|
||||||
|
var s = Path.GetFullPath(value);
|
||||||
|
Console.WriteLine(s); //prevent compiler opitmization - just in case
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Error(ex, "Invalid path assigned to InstancePath! Please report this immediately! Value: " + value);
|
||||||
|
//throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
_instancePath = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
[XmlIgnore, Arg("noupdate", "Disable automatically downloading game and plugin updates.")]
|
[XmlIgnore, Arg("noupdate", "Disable automatically downloading game and plugin updates.")]
|
||||||
@@ -58,18 +88,37 @@ namespace Torch.Server
|
|||||||
public int TickTimeout { get; set; } = 60;
|
public int TickTimeout { get; set; } = 60;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public List<string> Plugins { get; set; } = new List<string>();
|
[Arg("plugins", "Starts Torch with the given plugin GUIDs (space delimited).")]
|
||||||
|
public List<Guid> Plugins { get; set; } = new List<Guid>();
|
||||||
|
|
||||||
|
[Arg("localplugins", "Loads all pluhins from disk, ignores the plugins defined in config.")]
|
||||||
|
public bool LocalPlugins { get; set; }
|
||||||
|
|
||||||
|
[Arg("disconnect", "When server restarts, all clients are rejected to main menu to prevent auto rejoin")]
|
||||||
|
public bool DisconnectOnRestart { get; set; }
|
||||||
|
|
||||||
|
public string ChatName { get; set; } = "Server";
|
||||||
|
|
||||||
|
public string ChatColor { get; set; } = "Red";
|
||||||
|
|
||||||
public bool EnableWhitelist { get; set; } = false;
|
public bool EnableWhitelist { get; set; } = false;
|
||||||
public HashSet<ulong> Whitelist { get; set; } = new HashSet<ulong>();
|
public HashSet<ulong> Whitelist { get; set; } = new HashSet<ulong>();
|
||||||
|
|
||||||
internal Point WindowSize { get; set; } = new Point(800, 600);
|
public Point WindowSize { get; set; } = new Point(800, 600);
|
||||||
internal Point WindowPosition { get; set; } = new Point();
|
public Point WindowPosition { get; set; } = new Point();
|
||||||
|
|
||||||
public string LastUsedTheme { get; set; } = "Torch Theme";
|
public string LastUsedTheme { get; set; } = "Torch Theme";
|
||||||
|
|
||||||
public bool EnableReservedSlots { get; set; } = false;
|
//Prevent reserved players being written to disk, but allow it to be read
|
||||||
public HashSet<ulong> ReservedPlayers { get; set; } = new HashSet<ulong>();
|
//remove this when ReservedPlayers is removed
|
||||||
|
private bool ShouldSerializeReservedPlayers() => false;
|
||||||
|
|
||||||
|
[Arg("console", "Keeps a separate console window open after the main UI loads.")]
|
||||||
|
public bool IndependentConsole { get; set; } = false;
|
||||||
|
|
||||||
|
[XmlIgnore]
|
||||||
|
[Arg("testplugin", "Path to a plugin to debug. For development use only.")]
|
||||||
|
public string TestPlugin { get; set; }
|
||||||
|
|
||||||
[XmlIgnore]
|
[XmlIgnore]
|
||||||
private string _path;
|
private string _path;
|
||||||
|
@@ -19,6 +19,7 @@ using Torch.API.Managers;
|
|||||||
using Torch.API.Session;
|
using Torch.API.Session;
|
||||||
using Torch.Commands;
|
using Torch.Commands;
|
||||||
using Torch.Mod;
|
using Torch.Mod;
|
||||||
|
using Torch.Mod.Messages;
|
||||||
using Torch.Server.Commands;
|
using Torch.Server.Commands;
|
||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
@@ -26,6 +27,7 @@ using VRage;
|
|||||||
using VRage.Dedicated;
|
using VRage.Dedicated;
|
||||||
using VRage.Dedicated.RemoteAPI;
|
using VRage.Dedicated.RemoteAPI;
|
||||||
using VRage.GameServices;
|
using VRage.GameServices;
|
||||||
|
using VRage.Scripting;
|
||||||
using VRage.Steam;
|
using VRage.Steam;
|
||||||
using Timer = System.Threading.Timer;
|
using Timer = System.Threading.Timer;
|
||||||
|
|
||||||
@@ -37,14 +39,18 @@ namespace Torch.Server
|
|||||||
{
|
{
|
||||||
public class TorchServer : TorchBase, ITorchServer
|
public class TorchServer : TorchBase, ITorchServer
|
||||||
{
|
{
|
||||||
|
private bool _hasRun;
|
||||||
private bool _canRun;
|
private bool _canRun;
|
||||||
private TimeSpan _elapsedPlayTime;
|
private TimeSpan _elapsedPlayTime;
|
||||||
private bool _hasRun;
|
|
||||||
private bool _isRunning;
|
private bool _isRunning;
|
||||||
private float _simRatio;
|
private float _simRatio;
|
||||||
private ServerState _state;
|
private ServerState _state;
|
||||||
private Stopwatch _uptime;
|
private Stopwatch _uptime;
|
||||||
private Timer _watchdog;
|
private Timer _watchdog;
|
||||||
|
private int _players;
|
||||||
|
private MultiplayerManagerDedicated _multiplayerManagerDedicated;
|
||||||
|
|
||||||
|
internal bool FatalException { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TorchServer(TorchConfig config = null)
|
public TorchServer(TorchConfig config = null)
|
||||||
@@ -52,12 +58,15 @@ namespace Torch.Server
|
|||||||
DedicatedInstance = new InstanceManager(this);
|
DedicatedInstance = new InstanceManager(this);
|
||||||
AddManager(DedicatedInstance);
|
AddManager(DedicatedInstance);
|
||||||
AddManager(new EntityControlManager(this));
|
AddManager(new EntityControlManager(this));
|
||||||
|
AddManager(new RemoteAPIManager(this));
|
||||||
Config = config ?? new TorchConfig();
|
Config = config ?? new TorchConfig();
|
||||||
|
|
||||||
var sessionManager = Managers.GetManager<ITorchSessionManager>();
|
var sessionManager = Managers.GetManager<ITorchSessionManager>();
|
||||||
sessionManager.AddFactory(x => new MultiplayerManagerDedicated(this));
|
sessionManager.AddFactory(x => new MultiplayerManagerDedicated(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasRun { get => _hasRun; set => SetValue(ref _hasRun, value); }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public float SimulationRatio { get => _simRatio; set => SetValue(ref _simRatio, value); }
|
public float SimulationRatio { get => _simRatio; set => SetValue(ref _simRatio, value); }
|
||||||
|
|
||||||
@@ -92,6 +101,8 @@ namespace Torch.Server
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string InstancePath => Config?.InstancePath;
|
public string InstancePath => Config?.InstancePath;
|
||||||
|
|
||||||
|
public int OnlinePlayers { get => _players; private set => SetValue(ref _players, value); }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Init()
|
public override void Init()
|
||||||
{
|
{
|
||||||
@@ -111,7 +122,7 @@ namespace Torch.Server
|
|||||||
if (State != ServerState.Stopped)
|
if (State != ServerState.Stopped)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_hasRun)
|
if (IsRunning || HasRun)
|
||||||
{
|
{
|
||||||
Restart();
|
Restart();
|
||||||
return;
|
return;
|
||||||
@@ -119,16 +130,11 @@ namespace Torch.Server
|
|||||||
|
|
||||||
State = ServerState.Starting;
|
State = ServerState.Starting;
|
||||||
IsRunning = true;
|
IsRunning = true;
|
||||||
|
HasRun = true;
|
||||||
CanRun = false;
|
CanRun = false;
|
||||||
_hasRun = true;
|
|
||||||
Log.Info("Starting server.");
|
Log.Info("Starting server.");
|
||||||
MySandboxGame.ConfigDedicated = DedicatedInstance.DedicatedConfig.Model;
|
MySandboxGame.ConfigDedicated = DedicatedInstance.DedicatedConfig.Model;
|
||||||
if (MySandboxGame.ConfigDedicated.RemoteApiEnabled && !string.IsNullOrEmpty(MySandboxGame.ConfigDedicated.RemoteSecurityKey))
|
|
||||||
{
|
|
||||||
var myRemoteServer = new MyRemoteServer(MySandboxGame.ConfigDedicated.RemoteApiPort, MySandboxGame.ConfigDedicated.RemoteSecurityKey);
|
|
||||||
Log.Info($"Remote API started on port {myRemoteServer.Port}");
|
|
||||||
}
|
|
||||||
|
|
||||||
_uptime = Stopwatch.StartNew();
|
_uptime = Stopwatch.StartNew();
|
||||||
base.Start();
|
base.Start();
|
||||||
}
|
}
|
||||||
@@ -150,9 +156,15 @@ namespace Torch.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restart the program.
|
/// Restart the program.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override void Restart()
|
public override void Restart(bool save = true)
|
||||||
{
|
{
|
||||||
if (IsRunning)
|
if (Config.DisconnectOnRestart)
|
||||||
|
{
|
||||||
|
ModCommunication.SendMessageToClients(new JoinServerMessage("0.0.0.0:25555"));
|
||||||
|
Log.Info("Ejected all players from server for restart.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsRunning && save)
|
||||||
Save().ContinueWith(DoRestart, this, TaskContinuationOptions.RunContinuationsAsynchronously);
|
Save().ContinueWith(DoRestart, this, TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||||
else
|
else
|
||||||
DoRestart(null, this);
|
DoRestart(null, this);
|
||||||
@@ -186,6 +198,7 @@ namespace Torch.Server
|
|||||||
|
|
||||||
if (newState == TorchSessionState.Loaded)
|
if (newState == TorchSessionState.Loaded)
|
||||||
{
|
{
|
||||||
|
_multiplayerManagerDedicated = CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>();
|
||||||
CurrentSession.Managers.GetManager<CommandManager>().RegisterCommandModule(typeof(WhitelistCommands));
|
CurrentSession.Managers.GetManager<CommandManager>().RegisterCommandModule(typeof(WhitelistCommands));
|
||||||
ModCommunication.Register();
|
ModCommunication.Register();
|
||||||
}
|
}
|
||||||
@@ -195,8 +208,7 @@ namespace Torch.Server
|
|||||||
public override void Init(object gameInstance)
|
public override void Init(object gameInstance)
|
||||||
{
|
{
|
||||||
base.Init(gameInstance);
|
base.Init(gameInstance);
|
||||||
var game = gameInstance as MySandboxGame;
|
if (gameInstance is MySandboxGame && MySession.Static != null)
|
||||||
if (game != null && MySession.Static != null)
|
|
||||||
State = ServerState.Running;
|
State = ServerState.Running;
|
||||||
else
|
else
|
||||||
State = ServerState.Stopped;
|
State = ServerState.Stopped;
|
||||||
@@ -210,6 +222,7 @@ namespace Torch.Server
|
|||||||
SimulationRatio = Math.Min(Sync.ServerSimulationRatio, 1);
|
SimulationRatio = Math.Min(Sync.ServerSimulationRatio, 1);
|
||||||
var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds));
|
var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds));
|
||||||
ElapsedPlayTime = elapsed;
|
ElapsedPlayTime = elapsed;
|
||||||
|
OnlinePlayers = _multiplayerManagerDedicated?.Players.Count ?? 0;
|
||||||
|
|
||||||
if (_watchdog == null && Config.TickTimeout > 0)
|
if (_watchdog == null && Config.TickTimeout > 0)
|
||||||
{
|
{
|
||||||
@@ -223,10 +236,16 @@ namespace Torch.Server
|
|||||||
|
|
||||||
private static void CheckServerResponding(object state)
|
private static void CheckServerResponding(object state)
|
||||||
{
|
{
|
||||||
|
var server = (TorchServer)state;
|
||||||
var mre = new ManualResetEvent(false);
|
var mre = new ManualResetEvent(false);
|
||||||
((TorchServer)state).Invoke(() => mre.Set());
|
server.Invoke(() => mre.Set());
|
||||||
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
|
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
|
||||||
{
|
{
|
||||||
|
if (server.FatalException)
|
||||||
|
{
|
||||||
|
server._watchdog.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Log.Error(
|
Log.Error(
|
||||||
$"Server watchdog detected that the server was frozen for at least {((TorchServer) state).Config.TickTimeout} seconds.");
|
$"Server watchdog detected that the server was frozen for at least {((TorchServer) state).Config.TickTimeout} seconds.");
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.ServiceProcess;
|
using System.ServiceProcess;
|
||||||
|
using System.Threading;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
|
||||||
@@ -12,12 +14,14 @@ namespace Torch.Server
|
|||||||
{
|
{
|
||||||
class TorchService : ServiceBase
|
class TorchService : ServiceBase
|
||||||
{
|
{
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
public const string Name = "Torch (SEDS)";
|
public const string Name = "Torch (SEDS)";
|
||||||
private TorchServer _server;
|
|
||||||
private Initializer _initializer;
|
private Initializer _initializer;
|
||||||
|
private string[] _args;
|
||||||
|
|
||||||
public TorchService()
|
public TorchService(string[] args)
|
||||||
{
|
{
|
||||||
|
_args = args;
|
||||||
var workingDir = new FileInfo(typeof(TorchService).Assembly.Location).Directory.ToString();
|
var workingDir = new FileInfo(typeof(TorchService).Assembly.Location).Directory.ToString();
|
||||||
Directory.SetCurrentDirectory(workingDir);
|
Directory.SetCurrentDirectory(workingDir);
|
||||||
_initializer = new Initializer(workingDir);
|
_initializer = new Initializer(workingDir);
|
||||||
@@ -29,19 +33,21 @@ namespace Torch.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void OnStart(string[] args)
|
protected override void OnStart(string[] _)
|
||||||
{
|
{
|
||||||
base.OnStart(args);
|
base.OnStart(_args);
|
||||||
|
|
||||||
_initializer.Initialize(args);
|
_initializer.Initialize(_args);
|
||||||
_initializer.Run();
|
_initializer.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void OnStop()
|
protected override void OnStop()
|
||||||
{
|
{
|
||||||
_server.Stop();
|
var mre = new ManualResetEvent(false);
|
||||||
base.OnStop();
|
Task.Run(() => _initializer.Server.Stop());
|
||||||
|
if (!mre.WaitOne(TimeSpan.FromMinutes(1)))
|
||||||
|
Process.GetCurrentProcess().Kill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,12 +16,12 @@ namespace Torch.Server.ViewModels
|
|||||||
public class CheckpointViewModel : ViewModel
|
public class CheckpointViewModel : ViewModel
|
||||||
{
|
{
|
||||||
private MyObjectBuilder_Checkpoint _checkpoint;
|
private MyObjectBuilder_Checkpoint _checkpoint;
|
||||||
private SessionSettingsViewModel _sessionSettings;
|
//private SessionSettingsViewModel _sessionSettings;
|
||||||
|
|
||||||
public CheckpointViewModel(MyObjectBuilder_Checkpoint checkpoint)
|
public CheckpointViewModel(MyObjectBuilder_Checkpoint checkpoint)
|
||||||
{
|
{
|
||||||
_checkpoint = checkpoint;
|
_checkpoint = checkpoint;
|
||||||
_sessionSettings = new SessionSettingsViewModel(_checkpoint.Settings);
|
//_sessionSettings = new SessionSettingsViewModel(_checkpoint.Settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static implicit operator MyObjectBuilder_Checkpoint(CheckpointViewModel model)
|
public static implicit operator MyObjectBuilder_Checkpoint(CheckpointViewModel model)
|
||||||
@@ -59,15 +59,15 @@ namespace Torch.Server.ViewModels
|
|||||||
|
|
||||||
public SerializableDictionary<long, MyObjectBuilder_Checkpoint.PlayerId> ControlledEntities { get => _checkpoint.ControlledEntities; set => SetValue(ref _checkpoint.ControlledEntities, value); }
|
public SerializableDictionary<long, MyObjectBuilder_Checkpoint.PlayerId> ControlledEntities { get => _checkpoint.ControlledEntities; set => SetValue(ref _checkpoint.ControlledEntities, value); }
|
||||||
|
|
||||||
public SessionSettingsViewModel Settings
|
//public SessionSettingsViewModel Settings
|
||||||
{
|
//{
|
||||||
get => _sessionSettings;
|
// get => _sessionSettings;
|
||||||
set
|
// set
|
||||||
{
|
// {
|
||||||
SetValue(ref _sessionSettings, value);
|
// SetValue(ref _sessionSettings, value);
|
||||||
_checkpoint.Settings = _sessionSettings;
|
// _checkpoint.Settings = _sessionSettings;
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
public MyObjectBuilder_ScriptManager ScriptManagerData => throw new NotImplementedException();
|
public MyObjectBuilder_ScriptManager ScriptManagerData => throw new NotImplementedException();
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ namespace Torch.Server.ViewModels
|
|||||||
|
|
||||||
public MyObjectBuilder_FactionCollection Factions => throw new NotImplementedException();
|
public MyObjectBuilder_FactionCollection Factions => throw new NotImplementedException();
|
||||||
|
|
||||||
public List<MyObjectBuilder_Checkpoint.ModItem> Mods { get => _checkpoint.Mods; set => SetValue(ref _checkpoint.Mods, value); }
|
//public List<MyObjectBuilder_Checkpoint.ModItem> Mods { get => _checkpoint.Mods; set => SetValue(ref _checkpoint.Mods, value); }
|
||||||
|
|
||||||
public SerializableDictionary<ulong, MyPromoteLevel> PromotedUsers { get => _checkpoint.PromotedUsers; set => SetValue(ref _checkpoint.PromotedUsers, value); }
|
public SerializableDictionary<ulong, MyPromoteLevel> PromotedUsers { get => _checkpoint.PromotedUsers; set => SetValue(ref _checkpoint.PromotedUsers, value); }
|
||||||
|
|
||||||
|
@@ -10,6 +10,8 @@ using Torch.Collections;
|
|||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
|
using Torch.Utils.SteamWorkshopTools;
|
||||||
|
using Torch.Collections;
|
||||||
|
|
||||||
namespace Torch.Server.ViewModels
|
namespace Torch.Server.ViewModels
|
||||||
{
|
{
|
||||||
@@ -27,8 +29,9 @@ namespace Torch.Server.ViewModels
|
|||||||
public ConfigDedicatedViewModel(MyConfigDedicated<MyObjectBuilder_SessionSettings> configDedicated)
|
public ConfigDedicatedViewModel(MyConfigDedicated<MyObjectBuilder_SessionSettings> configDedicated)
|
||||||
{
|
{
|
||||||
_config = configDedicated;
|
_config = configDedicated;
|
||||||
_config.IgnoreLastSession = true;
|
//_config.IgnoreLastSession = true;
|
||||||
SessionSettings = new SessionSettingsViewModel(_config.SessionSettings);
|
SessionSettings = new SessionSettingsViewModel(_config.SessionSettings);
|
||||||
|
Task.Run(() => UpdateAllModInfosAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save(string path = null)
|
public void Save(string path = null)
|
||||||
@@ -37,7 +40,7 @@ namespace Torch.Server.ViewModels
|
|||||||
|
|
||||||
_config.SessionSettings = _sessionSettings;
|
_config.SessionSettings = _sessionSettings;
|
||||||
// Never ever
|
// Never ever
|
||||||
_config.IgnoreLastSession = true;
|
//_config.IgnoreLastSession = true;
|
||||||
_config.Save(path);
|
_config.Save(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,12 +76,61 @@ namespace Torch.Server.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAllModInfosAsync(Action<string> messageHandler = null)
|
||||||
|
{
|
||||||
|
if (Mods.Count() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ids = Mods.Select(m => m.PublishedFileId);
|
||||||
|
var workshopService = WebAPI.Instance;
|
||||||
|
Dictionary<ulong, PublishedItemDetails> modInfos = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
modInfos = (await workshopService.GetPublishedFileDetails(ids.ToArray()));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info($"Mods Info successfully retrieved!");
|
||||||
|
|
||||||
|
foreach (var mod in Mods)
|
||||||
|
{
|
||||||
|
if (!modInfos.ContainsKey(mod.PublishedFileId) || modInfos[mod.PublishedFileId] == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Failed to retrieve info for mod with workshop id '{mod.PublishedFileId}'!");
|
||||||
|
}
|
||||||
|
//else if (!modInfo.Tags.Contains(""))
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mod.FriendlyName = modInfos[mod.PublishedFileId].Title;
|
||||||
|
mod.Description = modInfos[mod.PublishedFileId].Description;
|
||||||
|
//mod.Name = modInfos[mod.PublishedFileId].FileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public List<string> Administrators { get => _config.Administrators; set => SetValue(x => _config.Administrators = x, value); }
|
public List<string> Administrators { get => _config.Administrators; set => SetValue(x => _config.Administrators = x, value); }
|
||||||
|
|
||||||
public List<ulong> Banned { get => _config.Banned; set => SetValue(x => _config.Banned = x, value); }
|
public List<ulong> Banned { get => _config.Banned; set => SetValue(x => _config.Banned = x, value); }
|
||||||
|
|
||||||
private List<ulong> _mods = new List<ulong>();
|
private MtObservableList<ModItemInfo> _mods = new MtObservableList<ModItemInfo>();
|
||||||
public List<ulong> Mods { get => _mods; set => SetValue(x => _mods = x, value); }
|
public MtObservableList<ModItemInfo> Mods
|
||||||
|
{
|
||||||
|
get => _mods;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetValue(x => _mods = x, value);
|
||||||
|
Task.Run(() => UpdateAllModInfosAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ulong> Reserved { get => _config.Reserved; set => SetValue(x => _config.Reserved = x, value); }
|
||||||
|
|
||||||
|
|
||||||
public int AsteroidAmount { get => _config.AsteroidAmount; set => SetValue(x => _config.AsteroidAmount = x, value); }
|
public int AsteroidAmount { get => _config.AsteroidAmount; set => SetValue(x => _config.AsteroidAmount = x, value); }
|
||||||
|
|
||||||
@@ -90,8 +142,12 @@ namespace Torch.Server.ViewModels
|
|||||||
|
|
||||||
public string ServerName { get => _config.ServerName; set => SetValue(x => _config.ServerName = x, value); }
|
public string ServerName { get => _config.ServerName; set => SetValue(x => _config.ServerName = x, value); }
|
||||||
|
|
||||||
|
public string ServerDescription { get => _config.ServerDescription; set => SetValue(x => _config.ServerDescription = x, value); }
|
||||||
|
|
||||||
public bool PauseGameWhenEmpty { get => _config.PauseGameWhenEmpty; set => SetValue(x => _config.PauseGameWhenEmpty = x, value); }
|
public bool PauseGameWhenEmpty { get => _config.PauseGameWhenEmpty; set => SetValue(x => _config.PauseGameWhenEmpty = x, value); }
|
||||||
|
|
||||||
|
public bool AutodetectDependencies { get => _config.AutodetectDependencies; set => SetValue(x => _config.AutodetectDependencies = x, value); }
|
||||||
|
|
||||||
public string PremadeCheckpointPath { get => _config.PremadeCheckpointPath; set => SetValue(x => _config.PremadeCheckpointPath = x, value); }
|
public string PremadeCheckpointPath { get => _config.PremadeCheckpointPath; set => SetValue(x => _config.PremadeCheckpointPath = x, value); }
|
||||||
|
|
||||||
public string LoadWorld { get => _config.LoadWorld; set => SetValue(x => _config.LoadWorld = x, value); }
|
public string LoadWorld { get => _config.LoadWorld; set => SetValue(x => _config.LoadWorld = x, value); }
|
||||||
|
@@ -4,14 +4,24 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
{
|
{
|
||||||
public class CharacterViewModel : EntityViewModel
|
public class CharacterViewModel : EntityViewModel
|
||||||
{
|
{
|
||||||
|
private MyCharacter _character;
|
||||||
public CharacterViewModel(MyCharacter character, EntityTreeViewModel tree) : base(character, tree)
|
public CharacterViewModel(MyCharacter character, EntityTreeViewModel tree) : base(character, tree)
|
||||||
{
|
{
|
||||||
character.ControllerInfo.ControlAcquired += (x) => { OnPropertyChanged(nameof(Name)); };
|
_character = character;
|
||||||
character.ControllerInfo.ControlReleased += (x) => { OnPropertyChanged(nameof(Name)); };
|
character.ControllerInfo.ControlAcquired += ControllerInfo_ControlAcquired;
|
||||||
|
character.ControllerInfo.ControlReleased += ControllerInfo_ControlAcquired;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ControllerInfo_ControlAcquired(Sandbox.Game.World.MyEntityController obj)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(Name));
|
||||||
|
OnPropertyChanged(nameof(CanDelete));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharacterViewModel()
|
public CharacterViewModel()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool CanDelete => _character.ControllerInfo?.Controller?.Player == null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,14 @@
|
|||||||
using System.Windows.Controls;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox.Game.Entities;
|
||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
using Torch.Collections;
|
using Torch.Collections;
|
||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
|
using Torch.Utils;
|
||||||
using VRage.Game.Entity;
|
using VRage.Game.Entity;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
using VRage.ModAPI;
|
using VRage.ModAPI;
|
||||||
@@ -14,6 +20,8 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
{
|
{
|
||||||
protected EntityTreeViewModel Tree { get; }
|
protected EntityTreeViewModel Tree { get; }
|
||||||
|
|
||||||
|
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
private IMyEntity _backing;
|
private IMyEntity _backing;
|
||||||
public IMyEntity Entity
|
public IMyEntity Entity
|
||||||
{
|
{
|
||||||
@@ -43,6 +51,86 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string _descriptiveName;
|
||||||
|
public string DescriptiveName
|
||||||
|
{
|
||||||
|
get => _descriptiveName ?? (_descriptiveName = GetSortedName(EntityTreeViewModel.SortEnum.Name));
|
||||||
|
set => _descriptiveName = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetSortedName(EntityTreeViewModel.SortEnum sort)
|
||||||
|
{
|
||||||
|
switch (sort)
|
||||||
|
{
|
||||||
|
case EntityTreeViewModel.SortEnum.Name:
|
||||||
|
return Name;
|
||||||
|
case EntityTreeViewModel.SortEnum.Size:
|
||||||
|
return $"{Name} ({Entity.WorldVolume.Radius * 2:N}m)";
|
||||||
|
case EntityTreeViewModel.SortEnum.Speed:
|
||||||
|
return $"{Name} ({Entity.Physics?.LinearVelocity.Length() ?? 0:N}m/s)";
|
||||||
|
case EntityTreeViewModel.SortEnum.BlockCount:
|
||||||
|
if (Entity is MyCubeGrid grid)
|
||||||
|
return $"{Name} ({grid.BlocksCount} blocks)";
|
||||||
|
return Name;
|
||||||
|
case EntityTreeViewModel.SortEnum.DistFromCenter:
|
||||||
|
return $"{Name} ({Entity.GetPosition().Length():N}m)";
|
||||||
|
case EntityTreeViewModel.SortEnum.Owner:
|
||||||
|
if (Entity is MyCubeGrid g)
|
||||||
|
return $"{Name} ({g.GetGridOwnerName()})";
|
||||||
|
return Name;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(sort), sort, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual int CompareToSort(EntityViewModel other, EntityTreeViewModel.SortEnum sort)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
switch (sort)
|
||||||
|
{
|
||||||
|
case EntityTreeViewModel.SortEnum.Name:
|
||||||
|
if (Name == null)
|
||||||
|
{
|
||||||
|
if (other.Name == null)
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (other.Name == null)
|
||||||
|
return -1;
|
||||||
|
return string.Compare(Name, other.Name, StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
case EntityTreeViewModel.SortEnum.Size:
|
||||||
|
return Entity.WorldVolume.Radius.CompareTo(other.Entity.WorldVolume.Radius);
|
||||||
|
case EntityTreeViewModel.SortEnum.Speed:
|
||||||
|
if (Entity.Physics == null)
|
||||||
|
{
|
||||||
|
if (other.Entity.Physics == null)
|
||||||
|
return 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (other.Entity.Physics == null)
|
||||||
|
return 1;
|
||||||
|
return Entity.Physics.LinearVelocity.LengthSquared().CompareTo(other.Entity.Physics.LinearVelocity.LengthSquared());
|
||||||
|
case EntityTreeViewModel.SortEnum.BlockCount:
|
||||||
|
{
|
||||||
|
if (Entity is MyCubeGrid ga && other.Entity is MyCubeGrid gb)
|
||||||
|
return ga.BlocksCount.CompareTo(gb.BlocksCount);
|
||||||
|
goto case EntityTreeViewModel.SortEnum.Name;
|
||||||
|
}
|
||||||
|
case EntityTreeViewModel.SortEnum.DistFromCenter:
|
||||||
|
return Entity.GetPosition().LengthSquared().CompareTo(other.Entity.GetPosition().LengthSquared());
|
||||||
|
case EntityTreeViewModel.SortEnum.Owner:
|
||||||
|
{
|
||||||
|
if (Entity is MyCubeGrid ga && other.Entity is MyCubeGrid gb)
|
||||||
|
return string.Compare(ga.GetGridOwnerName(), gb.GetGridOwnerName(), StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
goto case EntityTreeViewModel.SortEnum.Name;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(sort), sort, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public virtual string Position
|
public virtual string Position
|
||||||
{
|
{
|
||||||
get => Entity?.GetPosition().ToString();
|
get => Entity?.GetPosition().ToString();
|
||||||
@@ -59,7 +147,7 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
|
|
||||||
public virtual bool CanStop => Entity.Physics?.Enabled ?? false;
|
public virtual bool CanStop => Entity.Physics?.Enabled ?? false;
|
||||||
|
|
||||||
public virtual bool CanDelete => !(Entity is IMyCharacter);
|
public virtual bool CanDelete => true;
|
||||||
|
|
||||||
public virtual void Delete()
|
public virtual void Delete()
|
||||||
{
|
{
|
||||||
@@ -76,5 +164,20 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Comparer : IComparer<EntityViewModel>
|
||||||
|
{
|
||||||
|
private EntityTreeViewModel.SortEnum _sort;
|
||||||
|
|
||||||
|
public Comparer(EntityTreeViewModel.SortEnum sort)
|
||||||
|
{
|
||||||
|
_sort = sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Compare(EntityViewModel x, EntityViewModel y)
|
||||||
|
{
|
||||||
|
return x.CompareToSort(y, _sort);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -50,17 +50,14 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
Blocks { get; } =
|
Blocks { get; } =
|
||||||
new MtObservableSortedDictionary<MyCubeBlockDefinition, MtObservableSortedDictionary<long, BlockViewModel>>(
|
new MtObservableSortedDictionary<MyCubeBlockDefinition, MtObservableSortedDictionary<long, BlockViewModel>>(
|
||||||
CubeBlockDefinitionComparer.Default);
|
CubeBlockDefinitionComparer.Default);
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
DescriptiveName = $"{grid.DisplayName} ({grid.BlocksCount} blocks)";
|
//DescriptiveName = $"{grid.DisplayName} ({grid.BlocksCount} blocks)";
|
||||||
Blocks.Add(_fillerDefinition, new MtObservableSortedDictionary<long, BlockViewModel>());
|
Blocks.Add(_fillerDefinition, new MtObservableSortedDictionary<long, BlockViewModel>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,11 +12,21 @@ using VRage.ModAPI;
|
|||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Torch.Collections;
|
using Torch.Collections;
|
||||||
|
using Torch.Server.Views.Entities;
|
||||||
|
|
||||||
namespace Torch.Server.ViewModels
|
namespace Torch.Server.ViewModels
|
||||||
{
|
{
|
||||||
public class EntityTreeViewModel : ViewModel
|
public class EntityTreeViewModel : ViewModel
|
||||||
{
|
{
|
||||||
|
public enum SortEnum
|
||||||
|
{
|
||||||
|
Name,
|
||||||
|
Size,
|
||||||
|
Speed,
|
||||||
|
Owner,
|
||||||
|
BlockCount,
|
||||||
|
DistFromCenter,
|
||||||
|
}
|
||||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
//TODO: these should be sorted sets for speed
|
//TODO: these should be sorted sets for speed
|
||||||
@@ -26,7 +36,13 @@ namespace Torch.Server.ViewModels
|
|||||||
public MtObservableSortedDictionary<long, VoxelMapViewModel> VoxelMaps { get; set; } = new MtObservableSortedDictionary<long, VoxelMapViewModel>();
|
public MtObservableSortedDictionary<long, VoxelMapViewModel> VoxelMaps { get; set; } = new MtObservableSortedDictionary<long, VoxelMapViewModel>();
|
||||||
public Dispatcher ControlDispatcher => _control.Dispatcher;
|
public Dispatcher ControlDispatcher => _control.Dispatcher;
|
||||||
|
|
||||||
|
public SortedView<GridViewModel> SortedGrids { get; }
|
||||||
|
public SortedView<CharacterViewModel> SortedCharacters { get; }
|
||||||
|
public SortedView<EntityViewModel> SortedFloatingObjects { get; }
|
||||||
|
public SortedView<VoxelMapViewModel> SortedVoxelMaps { get; }
|
||||||
|
|
||||||
private EntityViewModel _currentEntity;
|
private EntityViewModel _currentEntity;
|
||||||
|
private SortEnum _currentSort;
|
||||||
private UserControl _control;
|
private UserControl _control;
|
||||||
|
|
||||||
public EntityViewModel CurrentEntity
|
public EntityViewModel CurrentEntity
|
||||||
@@ -35,6 +51,12 @@ namespace Torch.Server.ViewModels
|
|||||||
set { _currentEntity = value; OnPropertyChanged(nameof(CurrentEntity)); }
|
set { _currentEntity = value; OnPropertyChanged(nameof(CurrentEntity)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SortEnum CurrentSort
|
||||||
|
{
|
||||||
|
get => _currentSort;
|
||||||
|
set => SetValue(ref _currentSort, value);
|
||||||
|
}
|
||||||
|
|
||||||
// I hate you today WPF
|
// I hate you today WPF
|
||||||
public EntityTreeViewModel() : this(null)
|
public EntityTreeViewModel() : this(null)
|
||||||
{
|
{
|
||||||
@@ -43,6 +65,11 @@ namespace Torch.Server.ViewModels
|
|||||||
public EntityTreeViewModel(UserControl control)
|
public EntityTreeViewModel(UserControl control)
|
||||||
{
|
{
|
||||||
_control = control;
|
_control = control;
|
||||||
|
var comparer = new EntityViewModel.Comparer(_currentSort);
|
||||||
|
SortedGrids = new SortedView<GridViewModel>(Grids.Values, comparer);
|
||||||
|
SortedCharacters = new SortedView<CharacterViewModel>(Characters.Values, comparer);
|
||||||
|
SortedFloatingObjects = new SortedView<EntityViewModel>(FloatingObjects.Values, comparer);
|
||||||
|
SortedVoxelMaps = new SortedView<VoxelMapViewModel>(VoxelMaps.Values, comparer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
@@ -85,16 +112,16 @@ namespace Torch.Server.ViewModels
|
|||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
case MyCubeGrid grid:
|
case MyCubeGrid grid:
|
||||||
Grids.Add(obj.EntityId, new GridViewModel(grid, this));
|
Grids.Add(grid.EntityId, new GridViewModel(grid, this));
|
||||||
break;
|
break;
|
||||||
case MyCharacter character:
|
case MyCharacter character:
|
||||||
Characters.Add(obj.EntityId, new CharacterViewModel(character, this));
|
Characters.Add(character.EntityId, new CharacterViewModel(character, this));
|
||||||
break;
|
break;
|
||||||
case MyFloatingObject floating:
|
case MyFloatingObject floating:
|
||||||
FloatingObjects.Add(obj.EntityId, new FloatingObjectViewModel(floating, this));
|
FloatingObjects.Add(floating.EntityId, new FloatingObjectViewModel(floating, this));
|
||||||
break;
|
break;
|
||||||
case MyVoxelBase voxel:
|
case MyVoxelBase voxel:
|
||||||
VoxelMaps.Add(obj.EntityId, new VoxelMapViewModel(voxel, this));
|
VoxelMaps.Add(voxel.EntityId, new VoxelMapViewModel(voxel, this));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
131
Torch.Server/ViewModels/ModItemInfo.cs
Normal file
131
Torch.Server/ViewModels/ModItemInfo.cs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using NLog;
|
||||||
|
using VRage.Game;
|
||||||
|
using Torch.Server.Annotations;
|
||||||
|
using Torch.Utils.SteamWorkshopTools;
|
||||||
|
|
||||||
|
namespace Torch.Server.ViewModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper around VRage.Game.Objectbuilder_Checkpoint.ModItem
|
||||||
|
/// that holds additional meta information
|
||||||
|
/// (e.g. workshop description)
|
||||||
|
/// </summary>
|
||||||
|
public class ModItemInfo : ViewModel
|
||||||
|
{
|
||||||
|
MyObjectBuilder_Checkpoint.ModItem _modItem;
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Human friendly name of the mod
|
||||||
|
/// </summary>
|
||||||
|
public string FriendlyName
|
||||||
|
{
|
||||||
|
get { return _modItem.FriendlyName; }
|
||||||
|
set {
|
||||||
|
SetValue(ref _modItem.FriendlyName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Workshop ID of the mod
|
||||||
|
/// </summary>
|
||||||
|
public ulong PublishedFileId
|
||||||
|
{
|
||||||
|
get { return _modItem.PublishedFileId; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetValue(ref _modItem.PublishedFileId, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Local filename of the mod
|
||||||
|
/// </summary>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return _modItem.Name; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetValue(ref _modItem.FriendlyName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the mod was added
|
||||||
|
/// because another mod depends on it
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDependency
|
||||||
|
{
|
||||||
|
get { return _modItem.IsDependency; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetValue(ref _modItem.IsDependency, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _description;
|
||||||
|
/// <summary>
|
||||||
|
/// Workshop description of the mod
|
||||||
|
/// </summary>
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get { return _description; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetValue(ref _description, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor, returns a new ModItemInfo instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mod">The wrapped mod</param>
|
||||||
|
public ModItemInfo(MyObjectBuilder_Checkpoint.ModItem mod)
|
||||||
|
{
|
||||||
|
_modItem = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve information about the
|
||||||
|
/// wrapped mod from the workhop asynchronously
|
||||||
|
/// via the Steam web API.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> UpdateModInfoAsync()
|
||||||
|
{
|
||||||
|
var msg = "";
|
||||||
|
var workshopService = WebAPI.Instance;
|
||||||
|
PublishedItemDetails modInfo = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
modInfo = (await workshopService.GetPublishedFileDetails(new ulong[] { PublishedFileId }))?[PublishedFileId];
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
Log.Error(e.Message);
|
||||||
|
}
|
||||||
|
if (modInfo == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Failed to retrieve mod with workshop id '{PublishedFileId}'!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//else if (!modInfo.Tags.Contains(""))
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Info($"Mod Info successfully retrieved!");
|
||||||
|
FriendlyName = modInfo.Title;
|
||||||
|
Description = modInfo.Description;
|
||||||
|
//Name = modInfo.FileName;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,10 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using NLog;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Plugins;
|
using Torch.API.Plugins;
|
||||||
using Torch.Server.Views;
|
using Torch.Server.Views;
|
||||||
@@ -17,13 +19,25 @@ namespace Torch.Server.ViewModels
|
|||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public ITorchPlugin Plugin { get; }
|
public ITorchPlugin Plugin { get; }
|
||||||
|
|
||||||
|
private static Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
public PluginViewModel(ITorchPlugin plugin)
|
public PluginViewModel(ITorchPlugin plugin)
|
||||||
{
|
{
|
||||||
Plugin = plugin;
|
Plugin = plugin;
|
||||||
|
|
||||||
if (Plugin is IWpfPlugin p)
|
if (Plugin is IWpfPlugin p)
|
||||||
Control = p.GetControl();
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Control = p.GetControl();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Error(ex, $"Exception loading interface for plugin {Plugin.Name}! Plugin interface will not be available!");
|
||||||
|
Control = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Name = $"{plugin.Name} ({plugin.Version})";
|
Name = $"{plugin.Name} ({plugin.Version})";
|
||||||
|
|
||||||
ThemeControl.UpdateDynamicControls += UpdateResourceDict;
|
ThemeControl.UpdateDynamicControls += UpdateResourceDict;
|
||||||
@@ -38,5 +52,55 @@ namespace Torch.Server.ViewModels
|
|||||||
this.Control.Resources.MergedDictionaries.Clear();
|
this.Control.Resources.MergedDictionaries.Clear();
|
||||||
this.Control.Resources.MergedDictionaries.Add(dictionary);
|
this.Control.Resources.MergedDictionaries.Add(dictionary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Brush Color
|
||||||
|
{
|
||||||
|
get {
|
||||||
|
switch (Plugin.State)
|
||||||
|
{
|
||||||
|
case PluginState.NotInitialized:
|
||||||
|
case PluginState.MissingDependency:
|
||||||
|
case PluginState.DisabledError:
|
||||||
|
return Brushes.Red;
|
||||||
|
case PluginState.UpdateRequired:
|
||||||
|
return Brushes.DodgerBlue;
|
||||||
|
case PluginState.UninstallRequested:
|
||||||
|
return Brushes.Gold;
|
||||||
|
case PluginState.NotInstalled:
|
||||||
|
case PluginState.DisabledUser:
|
||||||
|
return Brushes.Gray;
|
||||||
|
case PluginState.Enabled:
|
||||||
|
return Brushes.Transparent;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToolTip
|
||||||
|
{
|
||||||
|
get { switch (Plugin.State)
|
||||||
|
{
|
||||||
|
case PluginState.NotInitialized:
|
||||||
|
return "Error during load.";
|
||||||
|
case PluginState.DisabledError:
|
||||||
|
return "Disabled due to error on load.";
|
||||||
|
case PluginState.DisabledUser:
|
||||||
|
return "Disabled.";
|
||||||
|
case PluginState.UpdateRequired:
|
||||||
|
return "Update required.";
|
||||||
|
case PluginState.UninstallRequested:
|
||||||
|
return "Marked for uninstall.";
|
||||||
|
case PluginState.NotInstalled:
|
||||||
|
return "Not installed. Click 'Enable'";
|
||||||
|
case PluginState.Enabled:
|
||||||
|
return string.Empty;
|
||||||
|
case PluginState.MissingDependency:
|
||||||
|
return "Dependency missing. Check the log.";
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,8 +21,11 @@ namespace Torch.Server.ViewModels
|
|||||||
[Torch.Views.Display(Description = "The type of the game online mode.", Name = "Online Mode", GroupName = "Others")]
|
[Torch.Views.Display(Description = "The type of the game online mode.", Name = "Online Mode", GroupName = "Others")]
|
||||||
public MyOnlineModeEnum OnlineMode { get => _settings.OnlineMode; set => SetValue(ref _settings.OnlineMode, value); }
|
public MyOnlineModeEnum OnlineMode { get => _settings.OnlineMode; set => SetValue(ref _settings.OnlineMode, value); }
|
||||||
|
|
||||||
[Torch.Views.Display(Description = "The multiplier for inventory size.", Name = "Inventory Size", GroupName = "Multipliers")]
|
[Torch.Views.Display(Description = "The multiplier for character inventory size.", Name = "Character Inventory Size", GroupName = "Multipliers")]
|
||||||
public float InventorySizeMultiplier { get => _settings.InventorySizeMultiplier; set => SetValue(ref _settings.InventorySizeMultiplier, value); }
|
public float CharacterInventorySizeMultiplier { get => _settings.InventorySizeMultiplier; set => SetValue(ref _settings.InventorySizeMultiplier, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "The multiplier for block inventory size.", Name = "Block Inventory Size", GroupName = "Multipliers")]
|
||||||
|
public float BlockInventorySizeMultiplier { get => _settings.BlocksInventorySizeMultiplier; set => SetValue(ref _settings.BlocksInventorySizeMultiplier, value); }
|
||||||
|
|
||||||
[Torch.Views.Display(Description = "The multiplier for assembler speed.", Name = "Assembler Speed", GroupName = "Multipliers")]
|
[Torch.Views.Display(Description = "The multiplier for assembler speed.", Name = "Assembler Speed", GroupName = "Multipliers")]
|
||||||
public float AssemblerSpeedMultiplier { get => _settings.AssemblerSpeedMultiplier; set => SetValue(ref _settings.AssemblerSpeedMultiplier, value); }
|
public float AssemblerSpeedMultiplier { get => _settings.AssemblerSpeedMultiplier; set => SetValue(ref _settings.AssemblerSpeedMultiplier, value); }
|
||||||
@@ -120,8 +123,14 @@ namespace Torch.Server.ViewModels
|
|||||||
public bool EnableSaving { get => _settings.EnableSaving; set => SetValue(ref _settings.EnableSaving, value); }
|
public bool EnableSaving { get => _settings.EnableSaving; set => SetValue(ref _settings.EnableSaving, value); }
|
||||||
|
|
||||||
[Torch.Views.Display(Description = "Enables respawn screen.", Name = "Enable Respawn Screen in the Game", GroupName = "Players")]
|
[Torch.Views.Display(Description = "Enables respawn screen.", Name = "Enable Respawn Screen in the Game", GroupName = "Players")]
|
||||||
public bool EnableRespawnScreen { get => _settings.EnableRespawnScreen; set => SetValue(ref _settings.EnableRespawnScreen, value); }
|
public bool StartInRespawnScreen { get => _settings.StartInRespawnScreen; set => SetValue(ref _settings.StartInRespawnScreen, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "Enables research.", Name = "Enable Research", GroupName = "Players")]
|
||||||
|
public bool EnableResearch { get => _settings.EnableResearch; set => SetValue(ref _settings.EnableResearch, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "Enables Good.bot hints.", Name = "Enable Good.bot hints", GroupName = "Players")]
|
||||||
|
public bool EnableGoodBotHints { get => _settings.EnableGoodBotHints; set => SetValue(ref _settings.EnableGoodBotHints, value); }
|
||||||
|
|
||||||
[Torch.Views.Display(Description = "Enables infinite ammunition in survival game mode.", Name = "Enable Infinite Ammunition in Survival", GroupName = "Others")]
|
[Torch.Views.Display(Description = "Enables infinite ammunition in survival game mode.", Name = "Enable Infinite Ammunition in Survival", GroupName = "Others")]
|
||||||
public bool InfiniteAmmo { get => _settings.InfiniteAmmo; set => SetValue(ref _settings.InfiniteAmmo, value); }
|
public bool InfiniteAmmo { get => _settings.InfiniteAmmo; set => SetValue(ref _settings.InfiniteAmmo, value); }
|
||||||
|
|
||||||
@@ -255,6 +264,45 @@ namespace Torch.Server.ViewModels
|
|||||||
[Torch.Views.Display(Description = "Defines character removal threshold for trash removal system. If player disconnects it will remove his character after this time.\n Set to 0 to disable.", Name = "Character Removal Threshold [mins]", GroupName = "Trash Removal")]
|
[Torch.Views.Display(Description = "Defines character removal threshold for trash removal system. If player disconnects it will remove his character after this time.\n Set to 0 to disable.", Name = "Character Removal Threshold [mins]", GroupName = "Trash Removal")]
|
||||||
public int PlayerCharacterRemovalThreshold { get => _settings.PlayerCharacterRemovalThreshold; set => SetValue(ref _settings.PlayerCharacterRemovalThreshold, value); }
|
public int PlayerCharacterRemovalThreshold { get => _settings.PlayerCharacterRemovalThreshold; set => SetValue(ref _settings.PlayerCharacterRemovalThreshold, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "Sets optimal distance in meters when spawning new players near others.", Name = "Optimal Spawn Distance", GroupName = "Players")]
|
||||||
|
public float OptimalSpawnDistance { get => _settings.OptimalSpawnDistance; set => SetValue(ref _settings.OptimalSpawnDistance, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "Enables automatic respawn at nearest available respawn point.", Name = "Enable Auto Respawn", GroupName = "Players")]
|
||||||
|
public bool EnableAutoRespawn { get => _settings.EnableAutorespawn; set => SetValue(ref _settings.EnableAutorespawn, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "The number of NPC factions generated on the start of the world.", Name = "NPC Factions Count", GroupName = "NPCs")]
|
||||||
|
public int TradeFactionsCount { get => _settings.TradeFactionsCount; set => SetValue(ref _settings.TradeFactionsCount, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "The inner radius [m] (center is in 0,0,0), where stations can spawn. Does not affect planet-bound stations (surface Outposts and Orbital stations).", Name = "Stations Inner Radius", GroupName = "NPCs")]
|
||||||
|
public double StationsDistanceInnerRadius { get => _settings.StationsDistanceInnerRadius; set => SetValue(ref _settings.StationsDistanceInnerRadius, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "The outer radius [m] (center is in 0,0,0), where stations can spawn. Does not affect planet-bound stations (surface Outposts and Orbital stations).", Name = "Stations Outer Radius Start", GroupName = "NPCs")]
|
||||||
|
public double StationsDistanceOuterRadiusStart { get => _settings.StationsDistanceOuterRadiusStart; set => SetValue(ref _settings.StationsDistanceOuterRadiusStart, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "The outer radius [m] (center is in 0,0,0), where stations can spawn. Does not affect planet-bound stations (surface Outposts and Orbital stations).", Name = "Stations Outer Radius End", GroupName = "NPCs")]
|
||||||
|
public double StationsDistanceOuterRadiusEnd { get => _settings.StationsDistanceOuterRadiusEnd; set => SetValue(ref _settings.StationsDistanceOuterRadiusEnd, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "Time period between two economy updates in seconds.", Name = "Economy tick time", GroupName = "NPCs")]
|
||||||
|
public int EconomyTickInSeconds { get => _settings.EconomyTickInSeconds; set => SetValue(ref _settings.EconomyTickInSeconds, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "If enabled bounty contracts will be available on stations.", Name = "Enable Bounty Contracts", GroupName = "Players")]
|
||||||
|
public bool EnableBountyContracts { get => _settings.EnableBountyContracts; set => SetValue(ref _settings.EnableBountyContracts, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "Resource deposits count coefficient for generated world content (voxel generator version > 2).", Name = "Deposits Count Coefficient", GroupName = "Environment")]
|
||||||
|
public float DepositsCountCoefficient { get => _settings.DepositsCountCoefficient; set => SetValue(ref _settings.DepositsCountCoefficient, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "Resource deposit size denominator for generated world content (voxel generator version > 2).", Name = "Deposit Size Denominator", GroupName = "Environment")]
|
||||||
|
public float DepositSideDenominator { get => _settings.DepositSizeDenominator; set => SetValue(ref _settings.DepositSizeDenominator, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "Enables economy features.", Name = "Enable Economy", GroupName = "NPCs")]
|
||||||
|
public bool EnableEconomy { get => _settings.EnableEconomy; set => SetValue(ref _settings.EnableEconomy, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "Enables system for voxel reverting.", Name = "Enable Voxel Reverting", GroupName = "Trash Removal")]
|
||||||
|
public bool VoxelTrashRemovalEnabled { get => _settings.VoxelTrashRemovalEnabled; set => SetValue(ref _settings.VoxelTrashRemovalEnabled, value); }
|
||||||
|
|
||||||
|
[Torch.Views.Display(Description = "Allows super gridding exploit to be used.", Name = "Enable Supergridding", GroupName = "Others")]
|
||||||
|
public bool EnableSupergridding { get => _settings.EnableSupergridding; set => SetValue(ref _settings.EnableSupergridding, value); }
|
||||||
|
|
||||||
public SessionSettingsViewModel(MyObjectBuilder_SessionSettings settings)
|
public SessionSettingsViewModel(MyObjectBuilder_SessionSettings settings)
|
||||||
{
|
{
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
|
34
Torch.Server/ViewModels/WorldConfigurationViewModel.cs
Normal file
34
Torch.Server/ViewModels/WorldConfigurationViewModel.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using VRage.Game;
|
||||||
|
|
||||||
|
namespace Torch.Server.ViewModels
|
||||||
|
{
|
||||||
|
public class WorldConfigurationViewModel : ViewModel
|
||||||
|
{
|
||||||
|
private readonly MyObjectBuilder_WorldConfiguration _worldConfiguration;
|
||||||
|
private SessionSettingsViewModel _sessionSettings;
|
||||||
|
|
||||||
|
public WorldConfigurationViewModel(MyObjectBuilder_WorldConfiguration worldConfiguration)
|
||||||
|
{
|
||||||
|
_worldConfiguration = worldConfiguration;
|
||||||
|
_sessionSettings = new SessionSettingsViewModel(worldConfiguration.Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator MyObjectBuilder_WorldConfiguration(WorldConfigurationViewModel model)
|
||||||
|
{
|
||||||
|
return model._worldConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MyObjectBuilder_Checkpoint.ModItem> Mods { get => _worldConfiguration.Mods; set => SetValue(ref _worldConfiguration.Mods, value); }
|
||||||
|
|
||||||
|
public SessionSettingsViewModel Settings
|
||||||
|
{
|
||||||
|
get => _sessionSettings;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetValue(ref _sessionSettings, value);
|
||||||
|
_worldConfiguration.Settings = _sessionSettings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -17,8 +17,8 @@
|
|||||||
<ColumnDefinition/>
|
<ColumnDefinition/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
</Grid.ColumnDefinitions>
|
</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>
|
<Button Grid.Column="1" Content="Send" DockPanel.Dock="Right" Width="50" Margin="5" Click="SendButton_Click"></Button>
|
||||||
<TextBox Grid.Column="0" x:Name="Message" DockPanel.Dock="Left" Margin="5,5,5,5" KeyDown="Message_OnKeyDown"></TextBox>
|
<TextBox Grid.Column="0" x:Name="Message" Margin="5" KeyDown="Message_OnKeyDown"></TextBox>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@@ -20,6 +20,8 @@ using NLog;
|
|||||||
using Torch;
|
using Torch;
|
||||||
using Sandbox;
|
using Sandbox;
|
||||||
using Sandbox.Engine.Multiplayer;
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Game.Gui;
|
||||||
|
using Sandbox.Game.Multiplayer;
|
||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
@@ -131,6 +133,15 @@ namespace Torch.Server
|
|||||||
bool atBottom = ChatScroller.VerticalOffset + 8 > ChatScroller.ScrollableHeight;
|
bool atBottom = ChatScroller.VerticalOffset + 8 > ChatScroller.ScrollableHeight;
|
||||||
var span = new Span();
|
var span = new Span();
|
||||||
span.Inlines.Add($"{msg.Timestamp} ");
|
span.Inlines.Add($"{msg.Timestamp} ");
|
||||||
|
switch (msg.Channel)
|
||||||
|
{
|
||||||
|
case ChatChannel.Faction:
|
||||||
|
span.Inlines.Add(new Run($"[{MySession.Static.Factions.TryGetFactionById(msg.Target)?.Tag ?? "???"}] ") { Foreground = Brushes.Green });
|
||||||
|
break;
|
||||||
|
case ChatChannel.Private:
|
||||||
|
span.Inlines.Add(new Run($"[to {MySession.Static.Players.TryGetIdentity(msg.Target)?.DisplayName ?? "???"}] ") { Foreground = Brushes.DeepPink });
|
||||||
|
break;
|
||||||
|
}
|
||||||
span.Inlines.Add(new Run(msg.Author) { Foreground = LookupBrush(msg.Font) });
|
span.Inlines.Add(new Run(msg.Author) { Foreground = LookupBrush(msg.Font) });
|
||||||
span.Inlines.Add($": {msg.Message}");
|
span.Inlines.Add($": {msg.Message}");
|
||||||
span.Inlines.Add(new LineBreak());
|
span.Inlines.Add(new LineBreak());
|
||||||
@@ -163,10 +174,18 @@ namespace Torch.Server
|
|||||||
var commands = _server.CurrentSession?.Managers.GetManager<Torch.Commands.CommandManager>();
|
var commands = _server.CurrentSession?.Managers.GetManager<Torch.Commands.CommandManager>();
|
||||||
if (commands != null && commands.IsCommand(text))
|
if (commands != null && commands.IsCommand(text))
|
||||||
{
|
{
|
||||||
InsertMessage(new TorchChatMessage("Server", text, MyFontEnum.DarkBlue));
|
InsertMessage(new TorchChatMessage(TorchBase.Instance.Config.ChatName, text, TorchBase.Instance.Config.ChatColor));
|
||||||
_server.Invoke(() =>
|
_server.Invoke(() =>
|
||||||
{
|
{
|
||||||
commands.HandleCommandFromServer(text);
|
var responses = commands.HandleCommandFromServer(text);
|
||||||
|
if (responses == null)
|
||||||
|
{
|
||||||
|
InsertMessage(new TorchChatMessage(TorchBase.Instance.Config.ChatName, "Invalid command.", TorchBase.Instance.Config.ChatColor));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var response in responses)
|
||||||
|
InsertMessage(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
<DockPanel Grid.Row="0">
|
<DockPanel Grid.Row="0">
|
||||||
<Label Content="World:" DockPanel.Dock="Left" />
|
<Label Content="World:" DockPanel.Dock="Left" />
|
||||||
<Button Content="Import World Config" Margin="3" Padding="3" DockPanel.Dock="Right" Click="ImportConfig_OnClick" ToolTip="Override the DS config with the one from the selected world." IsEnabled="{Binding ElementName=WorldList, Path=Items.Count, Mode=OneWay}"/>
|
<Button Content="Import World Config" Margin="3" Padding="3" DockPanel.Dock="Right" Click="ImportConfig_OnClick" ToolTip="Override the DS config with the one from the selected world." IsEnabled="{Binding ElementName=WorldList, Path=Items.Count, Mode=OneWay}"/>
|
||||||
|
<Button Content="New World" Margin="3" Padding="3" DockPanel.Dock="Right" Click="NewWorld_OnClick"/>
|
||||||
<ComboBox x:Name="WorldList" ItemsSource="{Binding Worlds}" SelectedItem="{Binding SelectedWorld}" Margin="3"
|
<ComboBox x:Name="WorldList" ItemsSource="{Binding Worlds}" SelectedItem="{Binding SelectedWorld}" Margin="3"
|
||||||
SelectionChanged="Selector_OnSelectionChanged">
|
SelectionChanged="Selector_OnSelectionChanged">
|
||||||
<ComboBox.ItemTemplate>
|
<ComboBox.ItemTemplate>
|
||||||
@@ -57,70 +58,77 @@
|
|||||||
<RowDefinition />
|
<RowDefinition />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid>
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid>
|
||||||
<ColumnDefinition Width="Auto" />
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
<ColumnDefinition />
|
||||||
<StackPanel Grid.Column="0" Margin="3" DockPanel.Dock="Left">
|
</Grid.ColumnDefinitions>
|
||||||
<Label Content="Server Name" />
|
<StackPanel Grid.Column="0" Margin="3" DockPanel.Dock="Left">
|
||||||
<TextBox Text="{Binding ServerName}" Margin="3,0,3,3" Width="160" />
|
<Label Content="Server Name" />
|
||||||
<Label Content="World Name" />
|
<TextBox Text="{Binding ServerName}" Margin="3,0,3,3" Width="160" />
|
||||||
<TextBox Text="{Binding WorldName}" Margin="3,0,3,3" Width="160" />
|
<Label Content="World Name" />
|
||||||
<Label Content="Whitelist Group ID" />
|
<TextBox Text="{Binding WorldName}" Margin="3,0,3,3" Width="160" />
|
||||||
<TextBox Margin="3,0,3,3" Width="160" Style="{StaticResource ValidatedTextBox}">
|
<Label Content="Server Description" />
|
||||||
<TextBox.Text>
|
<TextBox Text="{Binding ServerDescription}" Margin="3,0,3,3" Width="160" Height="100"
|
||||||
<Binding Path="GroupId" UpdateSourceTrigger="PropertyChanged"
|
AcceptsReturn="true" VerticalScrollBarVisibility="Auto"/>
|
||||||
ValidatesOnDataErrors="True" NotifyOnValidationError="True">
|
<Label Content="Whitelist Group ID" />
|
||||||
<Binding.ValidationRules>
|
<TextBox Margin="3,0,3,3" Width="160" Style="{StaticResource ValidatedTextBox}">
|
||||||
<validationRules:NumberValidationRule />
|
<TextBox.Text>
|
||||||
</Binding.ValidationRules>
|
<Binding Path="GroupId" UpdateSourceTrigger="PropertyChanged"
|
||||||
</Binding>
|
ValidatesOnDataErrors="True" NotifyOnValidationError="True">
|
||||||
</TextBox.Text>
|
<Binding.ValidationRules>
|
||||||
</TextBox>
|
<validationRules:NumberValidationRule />
|
||||||
<Label Content="Server IP" />
|
</Binding.ValidationRules>
|
||||||
<StackPanel Orientation="Horizontal" Margin="3,0,3,3">
|
</Binding>
|
||||||
<TextBox Text="{Binding IP}" Width="100" Height="20" />
|
</TextBox.Text>
|
||||||
<Label Content=":" Width="12" />
|
</TextBox>
|
||||||
<TextBox Text="{Binding Port}" Width="48" Height="20" />
|
<Label Content="Server IP" />
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="3,0,3,3">
|
||||||
|
<TextBox Text="{Binding IP}" Width="100" Height="20" />
|
||||||
|
<Label Content=":" Width="12" />
|
||||||
|
<TextBox Text="{Binding Port}" Width="48" Height="20" />
|
||||||
|
</StackPanel>
|
||||||
|
<Label Content="Server Password"/>
|
||||||
|
<TextBox Text="{Binding Password}" Width="160"/>
|
||||||
|
<CheckBox IsChecked="{Binding PauseGameWhenEmpty}" Content="Pause When Empty" Margin="3" />
|
||||||
|
<CheckBox IsChecked="{Binding AutodetectDependencies}" Content="Auto Detect Dependencies" Margin="3" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Label Content="Server Password"/>
|
<StackPanel Grid.Column="1" Margin="3">
|
||||||
<TextBox Text="{Binding Password}" Width="160"/>
|
<Label Content="Administrators" />
|
||||||
<CheckBox IsChecked="{Binding PauseGameWhenEmpty}" Content="Pause When Empty" Margin="3" />
|
<TextBox Text="{Binding Administrators, Converter={StaticResource ListConverterString}}"
|
||||||
</StackPanel>
|
Margin="3"
|
||||||
<StackPanel Grid.Column="1" Margin="3">
|
Height="100" AcceptsReturn="true" VerticalScrollBarVisibility="Auto" />
|
||||||
<Label Content="Mods" />
|
<Button Content="Edit Roles" Click="RoleEdit_Onlick" Margin="3"/>
|
||||||
<TextBox Margin="3" Height="100" AcceptsReturn="true" VerticalScrollBarVisibility="Auto"
|
<Label Content="Reserved Players" />
|
||||||
Style="{StaticResource ValidatedTextBox}">
|
<TextBox Margin="3" Height="100" AcceptsReturn="true" VerticalScrollBarVisibility="Auto"
|
||||||
<TextBox.Text>
|
Style="{StaticResource ValidatedTextBox}">
|
||||||
<Binding Path="Mods" UpdateSourceTrigger="PropertyChanged"
|
<TextBox.Text>
|
||||||
ValidatesOnDataErrors="True" NotifyOnValidationError="True"
|
<Binding Path="Reserved" UpdateSourceTrigger="PropertyChanged"
|
||||||
Converter="{StaticResource ListConverterUInt64}">
|
ValidatesOnDataErrors="True" NotifyOnValidationError="True"
|
||||||
<Binding.ValidationRules>
|
Converter="{StaticResource ListConverterUInt64}">
|
||||||
<validationRules:ListConverterValidationRule Type="system:UInt64" />
|
<Binding.ValidationRules>
|
||||||
</Binding.ValidationRules>
|
<validationRules:ListConverterValidationRule Type="system:UInt64" />
|
||||||
</Binding>
|
</Binding.ValidationRules>
|
||||||
</TextBox.Text>
|
</Binding>
|
||||||
</TextBox>
|
</TextBox.Text>
|
||||||
<Label Content="Administrators" />
|
</TextBox>
|
||||||
<TextBox Text="{Binding Administrators, Converter={StaticResource ListConverterString}}"
|
<Label Content="Banned Players" />
|
||||||
Margin="3"
|
<TextBox Margin="3" Height="100" AcceptsReturn="true" VerticalScrollBarVisibility="Auto"
|
||||||
Height="100" AcceptsReturn="true" VerticalScrollBarVisibility="Auto" />
|
Style="{StaticResource ValidatedTextBox}">
|
||||||
<Label Content="Banned Players" />
|
<TextBox.Text>
|
||||||
<TextBox Margin="3" Height="100" AcceptsReturn="true" VerticalScrollBarVisibility="Auto"
|
<Binding Path="Banned" UpdateSourceTrigger="PropertyChanged"
|
||||||
Style="{StaticResource ValidatedTextBox}">
|
ValidatesOnDataErrors="True" NotifyOnValidationError="True"
|
||||||
<TextBox.Text>
|
Converter="{StaticResource ListConverterUInt64}">
|
||||||
<Binding Path="Banned" UpdateSourceTrigger="PropertyChanged"
|
<Binding.ValidationRules>
|
||||||
ValidatesOnDataErrors="True" NotifyOnValidationError="True"
|
<validationRules:ListConverterValidationRule Type="system:UInt64" />
|
||||||
Converter="{StaticResource ListConverterUInt64}">
|
</Binding.ValidationRules>
|
||||||
<Binding.ValidationRules>
|
</Binding>
|
||||||
<validationRules:ListConverterValidationRule Type="system:UInt64" />
|
</TextBox.Text>
|
||||||
</Binding.ValidationRules>
|
</TextBox>
|
||||||
</Binding>
|
</StackPanel>
|
||||||
</TextBox.Text>
|
</Grid>
|
||||||
</TextBox>
|
</ScrollViewer>
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
<Button Grid.Row="1" Content="Save Config" Margin="3" Click="Save_OnClick" />
|
<Button Grid.Row="1" Content="Save Config" Margin="3" Click="Save_OnClick" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<views:PropertyGrid Grid.Column="1" Margin="3" DataContext="{Binding SessionSettings}" IgnoreDisplay ="True" />
|
<views:PropertyGrid Grid.Column="1" Margin="3" DataContext="{Binding SessionSettings}" IgnoreDisplay ="True" />
|
||||||
|
@@ -12,6 +12,8 @@ using Torch.API.Managers;
|
|||||||
using Torch.Server.Annotations;
|
using Torch.Server.Annotations;
|
||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
using Torch.Server.ViewModels;
|
using Torch.Server.ViewModels;
|
||||||
|
using Torch.Views;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
namespace Torch.Server.Views
|
namespace Torch.Server.Views
|
||||||
{
|
{
|
||||||
@@ -116,5 +118,28 @@ namespace Torch.Server.Views
|
|||||||
{
|
{
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NewWorld_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var c = new WorldGeneratorDialog(_instanceManager);
|
||||||
|
c.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RoleEdit_Onlick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
//var w = new RoleEditor(_instanceManager.DedicatedConfig.SelectedWorld);
|
||||||
|
//w.Show();
|
||||||
|
var d = new RoleEditor();
|
||||||
|
var w = _instanceManager.DedicatedConfig.SelectedWorld;
|
||||||
|
|
||||||
|
if (w == null)
|
||||||
|
{
|
||||||
|
MessageBox.Show("A world is not selected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Edit(w.Checkpoint.PromotedUsers.Dictionary);
|
||||||
|
_instanceManager.DedicatedConfig.Administrators = w.Checkpoint.PromotedUsers.Dictionary.Where(k => k.Value >= MyPromoteLevel.Admin).Select(k => k.Key.ToString()).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
77
Torch.Server/Views/Converters/ListConverterWorkshopId.cs
Normal file
77
Torch.Server/Views/Converters/ListConverterWorkshopId.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using Torch.Server.ViewModels;
|
||||||
|
using VRage.Game;
|
||||||
|
|
||||||
|
namespace Torch.Server.Views.Converters
|
||||||
|
{
|
||||||
|
class ListConverterWorkshopId : IValueConverter
|
||||||
|
{
|
||||||
|
public Type Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a list of ModItemInfo objects into a list of their workshop IDs (PublishedFileIds).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="valueList">
|
||||||
|
/// Expected to contain a list of ModItemInfo objects
|
||||||
|
/// </param>
|
||||||
|
/// <param name="targetType">This parameter will be ignored</param>
|
||||||
|
/// <param name="parameter">This parameter will be ignored</param>
|
||||||
|
/// <param name="culture"> This parameter will be ignored</param>
|
||||||
|
/// <returns>A string containing the workshop ids of all mods, one per line</returns>
|
||||||
|
public object Convert(object valueList, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (!(valueList is IList list))
|
||||||
|
throw new InvalidOperationException("Value is not the proper type.");
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var item in list)
|
||||||
|
{
|
||||||
|
sb.AppendLine(((ModItemInfo) item).PublishedFileId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a list of workshop ids into a list of ModItemInfo objects
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">A string containing workshop ids separated by new lines</param>
|
||||||
|
/// <param name="targetType">This parameter will be ignored</param>
|
||||||
|
/// <param name="parameter">
|
||||||
|
/// A list of ModItemInfos which should
|
||||||
|
/// contain the requestted mods
|
||||||
|
/// (or they will be dropped)
|
||||||
|
/// </param>
|
||||||
|
/// <param name="culture">This parameter will be ignored</param>
|
||||||
|
/// <returns>A list of ModItemInfo objects</returns>
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(Type));
|
||||||
|
var mods = parameter as ICollection<ModItemInfo>;
|
||||||
|
if (mods == null)
|
||||||
|
throw new ArgumentException("parameter needs to be of type ICollection<ModItemInfo>!");
|
||||||
|
var input = ((string)value).Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var item in input)
|
||||||
|
{
|
||||||
|
if( ulong.TryParse(item, out ulong id))
|
||||||
|
{
|
||||||
|
var mod = mods.FirstOrDefault((m) => m.PublishedFileId == id);
|
||||||
|
if (mod != null)
|
||||||
|
list.Add(mod);
|
||||||
|
else
|
||||||
|
list.Add(new MyObjectBuilder_Checkpoint.ModItem(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
Torch.Server/Views/Converters/ModToIdConverter.cs
Normal file
53
Torch.Server/Views/Converters/ModToIdConverter.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Torch.Server.ViewModels;
|
||||||
|
using NLog;
|
||||||
|
using Torch.Collections;
|
||||||
|
|
||||||
|
namespace Torch.Server.Views.Converters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A converter to get the index of a ModItemInfo object within a collection of ModItemInfo objects
|
||||||
|
/// </summary>
|
||||||
|
public class ModToListIdConverter : IMultiValueConverter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a ModItemInfo object into its index within a Collection of ModItemInfo objects
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">
|
||||||
|
/// Expected to contain a ModItemInfo object at index 0
|
||||||
|
/// and a Collection of ModItemInfo objects at index 1
|
||||||
|
/// </param>
|
||||||
|
/// <param name="targetType">This parameter will be ignored</param>
|
||||||
|
/// <param name="parameter">This parameter will be ignored</param>
|
||||||
|
/// <param name="culture"> This parameter will be ignored</param>
|
||||||
|
/// <returns>the index of the mod within the provided mod list.</returns>
|
||||||
|
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
//if (targetType != typeof(int))
|
||||||
|
// throw new NotSupportedException("ModToIdConverter can only convert mods into int values or vise versa!");
|
||||||
|
var mod = (ModItemInfo) values[0];
|
||||||
|
var theModList = (MtObservableList<ModItemInfo>) values[1];
|
||||||
|
return theModList.IndexOf(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// It is not supported to reverse this converter
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns>Raises a NotSupportedException</returns>
|
||||||
|
public object[] ConvertBack(object values, Type[] targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModToIdConverter can not convert back!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -22,10 +22,11 @@
|
|||||||
<Grid Grid.Column="0">
|
<Grid Grid.Column="0">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition />
|
<RowDefinition />
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TreeView Grid.Row="0" Margin="3" DockPanel.Dock="Top" SelectedItemChanged="TreeView_OnSelectedItemChanged"
|
<TreeView Grid.Row="0" Margin="3" DockPanel.Dock="Top" SelectedItemChanged="TreeView_OnSelectedItemChanged"
|
||||||
TreeViewItem.Expanded="TreeViewItem_OnExpanded">
|
TreeViewItem.Expanded="TreeViewItem_OnExpanded" Name="EntityTree">
|
||||||
<TreeView.Resources>
|
<TreeView.Resources>
|
||||||
<HierarchicalDataTemplate DataType="{x:Type entities:GridViewModel}"
|
<HierarchicalDataTemplate DataType="{x:Type entities:GridViewModel}"
|
||||||
ItemsSource="{Binding Path=Blocks}">
|
ItemsSource="{Binding Path=Blocks}">
|
||||||
@@ -46,46 +47,47 @@
|
|||||||
</HierarchicalDataTemplate>
|
</HierarchicalDataTemplate>
|
||||||
<HierarchicalDataTemplate DataType="{x:Type entities:VoxelMapViewModel}"
|
<HierarchicalDataTemplate DataType="{x:Type entities:VoxelMapViewModel}"
|
||||||
ItemsSource="{Binding AttachedGrids}">
|
ItemsSource="{Binding AttachedGrids}">
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Text="{Binding DescriptiveName}" />
|
||||||
</HierarchicalDataTemplate>
|
</HierarchicalDataTemplate>
|
||||||
</TreeView.Resources>
|
</TreeView.Resources>
|
||||||
<TreeViewItem ItemsSource="{Binding Path=Grids.Values}">
|
<TreeViewItem ItemsSource="{Binding Path=SortedGrids}">
|
||||||
<TreeViewItem.Header>
|
<TreeViewItem.Header>
|
||||||
<TextBlock Text="{Binding Grids.Count, StringFormat=Grids ({0})}" />
|
<TextBlock Text="{Binding Grids.Count, StringFormat=Grids ({0})}" />
|
||||||
</TreeViewItem.Header>
|
</TreeViewItem.Header>
|
||||||
</TreeViewItem>
|
</TreeViewItem>
|
||||||
<TreeViewItem ItemsSource="{Binding Characters.Values}">
|
<TreeViewItem ItemsSource="{Binding SortedCharacters}">
|
||||||
<TreeViewItem.Header>
|
<TreeViewItem.Header>
|
||||||
<TextBlock Text="{Binding Characters.Count, StringFormat=Characters ({0})}" />
|
<TextBlock Text="{Binding Characters.Count, StringFormat=Characters ({0})}" />
|
||||||
</TreeViewItem.Header>
|
</TreeViewItem.Header>
|
||||||
<TreeViewItem.ItemTemplate>
|
<TreeViewItem.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Text="{Binding DescriptiveName}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</TreeViewItem.ItemTemplate>
|
</TreeViewItem.ItemTemplate>
|
||||||
</TreeViewItem>
|
</TreeViewItem>
|
||||||
<TreeViewItem ItemsSource="{Binding VoxelMaps.Values}">
|
<TreeViewItem ItemsSource="{Binding SortedVoxelMaps}">
|
||||||
<TreeViewItem.Header>
|
<TreeViewItem.Header>
|
||||||
<TextBlock Text="{Binding VoxelMaps.Count, StringFormat=Voxel Maps ({0})}" />
|
<TextBlock Text="{Binding VoxelMaps.Count, StringFormat=Voxel Maps ({0})}" />
|
||||||
</TreeViewItem.Header>
|
</TreeViewItem.Header>
|
||||||
<TreeViewItem.ItemTemplate>
|
<TreeViewItem.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Text="{Binding DescriptiveName}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</TreeViewItem.ItemTemplate>
|
</TreeViewItem.ItemTemplate>
|
||||||
</TreeViewItem>
|
</TreeViewItem>
|
||||||
<TreeViewItem ItemsSource="{Binding FloatingObjects.Values}">
|
<TreeViewItem ItemsSource="{Binding SortedFloatingObjects}">
|
||||||
<TreeViewItem.Header>
|
<TreeViewItem.Header>
|
||||||
<TextBlock Text="{Binding FloatingObjects.Count, StringFormat=Floating Objects ({0})}" />
|
<TextBlock Text="{Binding FloatingObjects.Count, StringFormat=Floating Objects ({0})}" />
|
||||||
</TreeViewItem.Header>
|
</TreeViewItem.Header>
|
||||||
<TreeViewItem.ItemTemplate>
|
<TreeViewItem.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Text="{Binding DescriptiveName}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</TreeViewItem.ItemTemplate>
|
</TreeViewItem.ItemTemplate>
|
||||||
</TreeViewItem>
|
</TreeViewItem>
|
||||||
</TreeView>
|
</TreeView>
|
||||||
<StackPanel Grid.Row="1" DockPanel.Dock="Bottom">
|
<ComboBox Grid.Row="1" Margin="3" Name="SortCombo" SelectionChanged="SortCombo_SelectionChanged"/>
|
||||||
|
<StackPanel Grid.Row="2" DockPanel.Dock="Bottom">
|
||||||
<Button Content="Delete" Click="Delete_OnClick" IsEnabled="{Binding CurrentEntity.CanDelete}"
|
<Button Content="Delete" Click="Delete_OnClick" IsEnabled="{Binding CurrentEntity.CanDelete}"
|
||||||
Margin="3" />
|
Margin="3" />
|
||||||
<Button Content="Stop" Click="Stop_OnClick" IsEnabled="{Binding CurrentEntity.CanStop}" Margin="3" />
|
<Button Content="Stop" Click="Stop_OnClick" IsEnabled="{Binding CurrentEntity.CanStop}" Margin="3" />
|
||||||
|
@@ -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;
|
||||||
@@ -13,6 +14,7 @@ using System.Windows.Media.Imaging;
|
|||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using Torch.Collections;
|
||||||
using Torch.Server.ViewModels;
|
using Torch.Server.ViewModels;
|
||||||
using Torch.Server.ViewModels.Blocks;
|
using Torch.Server.ViewModels.Blocks;
|
||||||
using Torch.Server.ViewModels.Entities;
|
using Torch.Server.ViewModels.Entities;
|
||||||
@@ -29,14 +31,17 @@ namespace Torch.Server.Views
|
|||||||
{
|
{
|
||||||
public EntityTreeViewModel Entities { get; set; }
|
public EntityTreeViewModel Entities { get; set; }
|
||||||
|
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
public EntitiesControl()
|
public EntitiesControl()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Entities = new EntityTreeViewModel(this);
|
Entities = new EntityTreeViewModel(this);
|
||||||
DataContext = Entities;
|
DataContext = Entities;
|
||||||
Entities.Init();
|
Entities.Init();
|
||||||
|
SortCombo.ItemsSource = Enum.GetNames(typeof(EntityTreeViewModel.SortEnum));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||||
{
|
{
|
||||||
if (e.NewValue is EntityViewModel vm)
|
if (e.NewValue is EntityViewModel vm)
|
||||||
@@ -77,5 +82,30 @@ namespace Torch.Server.Views
|
|||||||
if (item.DataContext is ILazyLoad l)
|
if (item.DataContext is ILazyLoad l)
|
||||||
l.Load();
|
l.Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SortCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var sort = (EntityTreeViewModel.SortEnum)SortCombo.SelectedIndex;
|
||||||
|
|
||||||
|
var comparer = new EntityViewModel.Comparer(sort);
|
||||||
|
|
||||||
|
Task[] sortTasks = new Task[4];
|
||||||
|
|
||||||
|
Entities.CurrentSort = sort;
|
||||||
|
Entities.SortedCharacters.SetComparer(comparer);
|
||||||
|
Entities.SortedFloatingObjects.SetComparer(comparer);
|
||||||
|
Entities.SortedGrids.SetComparer(comparer);
|
||||||
|
Entities.SortedVoxelMaps.SetComparer(comparer);
|
||||||
|
|
||||||
|
foreach (var i in Entities.SortedCharacters)
|
||||||
|
i.DescriptiveName = i.GetSortedName(sort);
|
||||||
|
foreach (var i in Entities.SortedFloatingObjects)
|
||||||
|
i.DescriptiveName = i.GetSortedName(sort);
|
||||||
|
foreach (var i in Entities.SortedGrids)
|
||||||
|
i.DescriptiveName = i.GetSortedName(sort);
|
||||||
|
foreach (var i in Entities.SortedVoxelMaps)
|
||||||
|
i.DescriptiveName = i.GetSortedName(sort);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
Torch.Server/Views/Extensions.cs
Normal file
24
Torch.Server/Views/Extensions.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Torch.Server.Views
|
||||||
|
{
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty ScrollContainerProperty = DependencyProperty.RegisterAttached("ScrollContainer", typeof(bool), typeof(Extensions), new PropertyMetadata(true));
|
||||||
|
|
||||||
|
public static bool GetScrollContainer(this UIElement ui)
|
||||||
|
{
|
||||||
|
return (bool)ui.GetValue(ScrollContainerProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetScrollContainer(this UIElement ui, bool value)
|
||||||
|
{
|
||||||
|
ui.SetValue(ScrollContainerProperty, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
Torch.Server/Views/ModListControl.xaml
Normal file
127
Torch.Server/Views/ModListControl.xaml
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<UserControl x:Class="Torch.Server.Views.ModListControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:viewModels="clr-namespace:Torch.Server.ViewModels"
|
||||||
|
xmlns:s="clr-namespace:System"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
MouseMove="UserControl_MouseMove">
|
||||||
|
<!--<UserControl.DataContext>
|
||||||
|
<viewModels:ConfigDedicatedViewModel />
|
||||||
|
</UserControl.DataContext>-->
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="Resources.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
<Style TargetType="Grid" x:Key="RootGridStyle">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Mode=OneWay, UpdateSourceTrigger=PropertyChanged, BindingGroupName=RootEnabledBinding}" Value="{x:Null}">
|
||||||
|
<Setter Property="IsEnabled" Value="False"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<UserControl.DataContext>
|
||||||
|
<viewModels:ConfigDedicatedViewModel />
|
||||||
|
</UserControl.DataContext>
|
||||||
|
<Grid Style="{StaticResource RootGridStyle}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="500px"/>
|
||||||
|
<ColumnDefinition Width="10px"/>
|
||||||
|
<ColumnDefinition Width="*" MinWidth="200px"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="80px"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<DataGrid Name="ModList" Grid.Column="0" Grid.ColumnSpan="1" ItemsSource="{Binding UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Sorting="ModList_Sorting"
|
||||||
|
SelectionMode="Single"
|
||||||
|
SelectionUnit="FullRow"
|
||||||
|
AllowDrop="True"
|
||||||
|
CanUserReorderColumns="False"
|
||||||
|
CanUserSortColumns="True"
|
||||||
|
PreviewMouseLeftButtonDown="ModList_MouseLeftButtonDown"
|
||||||
|
MouseLeftButtonUp="ModList_MouseLeftButtonUp"
|
||||||
|
SelectedCellsChanged="ModList_Selected"
|
||||||
|
AutoGenerateColumns="False">
|
||||||
|
<!--:DesignSource="{d:DesignInstance Type={x:Type MyObjectBuilder_Checkpoint:ModItem, CreateList=True}}">-->
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="Load Order"
|
||||||
|
Width="Auto"
|
||||||
|
IsReadOnly="True">
|
||||||
|
<DataGridTextColumn.Binding>
|
||||||
|
<MultiBinding Converter="{StaticResource ModToListIdConverter}" StringFormat="{}{0}">
|
||||||
|
<Binding />
|
||||||
|
<Binding ElementName="ModList" Path="DataContext"></Binding>
|
||||||
|
</MultiBinding>
|
||||||
|
</DataGridTextColumn.Binding>
|
||||||
|
</DataGridTextColumn>
|
||||||
|
<DataGridTextColumn Header="Workshop Id"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Binding="{Binding PublishedFileId, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}">
|
||||||
|
|
||||||
|
</DataGridTextColumn>
|
||||||
|
<DataGridTextColumn Header="Name"
|
||||||
|
Width="*"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Binding="{Binding FriendlyName, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}">
|
||||||
|
</DataGridTextColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
<DataGrid.ItemContainerStyle>
|
||||||
|
<Style TargetType="DataGridRow">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsDependency}" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="#222222"/>
|
||||||
|
<Setter Property="Background" Value="#FFCCAA"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding IsDependency}" Value="True"/>
|
||||||
|
<Condition Binding="{Binding ElementName=ShowDependencyModsCheckBox, Path=IsChecked}" Value="False"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter Property="Height" Value="0px"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.ItemContainerStyle>
|
||||||
|
</DataGrid>
|
||||||
|
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Background="#1b2838">
|
||||||
|
<TextBlock Name="ModDescription" TextWrapping="Wrap" Foreground="White" Padding="2px"
|
||||||
|
Text="{Binding ElementName=ModList, Path=SelectedItem.Description}">
|
||||||
|
</TextBlock>
|
||||||
|
</ScrollViewer>
|
||||||
|
<Grid Grid.Row="2" Margin="0 0 0 6px">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition MinHeight="40px"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<CheckBox Name="ShowDependencyModsCheckBox" VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Left" Margin="6px 0" Grid.Column="0" Grid.Row="0"/>
|
||||||
|
<Label Content="Show Dependency Mods" Padding="0" Margin="6px 0" Grid.Column="1" VerticalAlignment="Center"/>
|
||||||
|
<Label Content="ID/URL:" Padding="0" Margin="6px 0" HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center" Grid.Column="0" Grid.Row="1"/>
|
||||||
|
<TextBox Name="AddModIDTextBox" Grid.Column="1" VerticalContentAlignment="Center"
|
||||||
|
HorizontalAlignment="Stretch" MinWidth="100px" Margin="6px 4px" Grid.Row="1"/>
|
||||||
|
<Button Content="Add" Grid.Column="2" Margin="6px 0" Width="60px" Height="40px" Click="AddBtn_OnClick" Grid.Row="1"/>
|
||||||
|
<Button Content="Remove" Grid.Column="3" Margin="6px 0" Width="60px" Height="40px" Click="RemoveBtn_OnClick" Grid.Row="1"
|
||||||
|
IsEnabled="{Binding ElementName=ModList, Path=SelectedItems.Count}"/>
|
||||||
|
<Button Content="Bulk Edit" Grid.Column="4" Margin="6px 0" Width="60px" Height="40px" Click="BulkButton_OnClick" Grid.Row="1"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Button Content="Save Config" Grid.Row="2" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="6px" Grid.Column="3" Width="80px" Height="40px" Click="SaveBtn_OnClick"/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
284
Torch.Server/Views/ModListControl.xaml.cs
Normal file
284
Torch.Server/Views/ModListControl.xaml.cs
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using VRage.Game;
|
||||||
|
using NLog;
|
||||||
|
using Torch.Server.Managers;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Server.ViewModels;
|
||||||
|
using Torch.Server.Annotations;
|
||||||
|
using Torch.Collections;
|
||||||
|
using Torch.Views;
|
||||||
|
|
||||||
|
namespace Torch.Server.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for ModListControl.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class ModListControl : UserControl, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private static Logger Log = LogManager.GetLogger(nameof(ModListControl));
|
||||||
|
private InstanceManager _instanceManager;
|
||||||
|
ModItemInfo _draggedMod;
|
||||||
|
bool _hasOrderChanged = false;
|
||||||
|
bool _isSortedByLoadOrder = true;
|
||||||
|
|
||||||
|
//private List<BindingExpression> _bindingExpressions = new List<BindingExpression>();
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for ModListControl
|
||||||
|
/// </summary>
|
||||||
|
public ModListControl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>();
|
||||||
|
_instanceManager.InstanceLoaded += _instanceManager_InstanceLoaded;
|
||||||
|
//var mods = _instanceManager.DedicatedConfig?.Mods;
|
||||||
|
//if( mods != null)
|
||||||
|
// DataContext = new ObservableCollection<MyObjectBuilder_Checkpoint.ModItem>();
|
||||||
|
DataContext = _instanceManager.DedicatedConfig?.Mods;
|
||||||
|
|
||||||
|
// Gets called once all children are loaded
|
||||||
|
//Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(ApplyStyles));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ModListControl_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetSorting()
|
||||||
|
{
|
||||||
|
CollectionViewSource.GetDefaultView(ModList.ItemsSource).SortDescriptions.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void _instanceManager_InstanceLoaded(ConfigDedicatedViewModel obj)
|
||||||
|
{
|
||||||
|
Log.Info("Instance loaded.");
|
||||||
|
Dispatcher.Invoke(() => {
|
||||||
|
DataContext = obj?.Mods ?? new MtObservableList<ModItemInfo>();
|
||||||
|
UpdateLayout();
|
||||||
|
((MtObservableList<ModItemInfo>)DataContext).CollectionChanged += OnModlistUpdate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnModlistUpdate(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
ModList.Items.Refresh();
|
||||||
|
//if (e.Action == NotifyCollectionChangedAction.Remove)
|
||||||
|
// _instanceManager.SaveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveBtn_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_instanceManager.SaveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void AddBtn_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (TryExtractId(AddModIDTextBox.Text, out ulong id))
|
||||||
|
{
|
||||||
|
var mod = new ModItemInfo(new MyObjectBuilder_Checkpoint.ModItem(id));
|
||||||
|
//mod.PublishedFileId = id;
|
||||||
|
_instanceManager.DedicatedConfig.Mods.Add(mod);
|
||||||
|
Task.Run(mod.UpdateModInfoAsync)
|
||||||
|
.ContinueWith((t) =>
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
_instanceManager.DedicatedConfig.Save();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
AddModIDTextBox.Text = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModIDTextBox.BorderBrush = Brushes.Red;
|
||||||
|
Log.Warn("Invalid mod id!");
|
||||||
|
MessageBox.Show("Invalid mod id!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void RemoveBtn_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var modList = ((MtObservableList<ModItemInfo>)DataContext);
|
||||||
|
if (ModList.SelectedItem is ModItemInfo mod && modList.Contains(mod))
|
||||||
|
modList.Remove(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryExtractId(string input, out ulong result)
|
||||||
|
{
|
||||||
|
var match = Regex.Match(input, @"(?<=id=)\d+").Value;
|
||||||
|
|
||||||
|
bool success;
|
||||||
|
if (string.IsNullOrEmpty(match))
|
||||||
|
success = ulong.TryParse(input, out result);
|
||||||
|
else
|
||||||
|
success = ulong.TryParse(match, out result);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ModList_Sorting(object sender, DataGridSortingEventArgs e)
|
||||||
|
{
|
||||||
|
Log.Info($"Sorting by '{e.Column.Header}'");
|
||||||
|
if (e.Column == ModList.Columns[0])
|
||||||
|
{
|
||||||
|
var dataView = CollectionViewSource.GetDefaultView(ModList.ItemsSource);
|
||||||
|
dataView.SortDescriptions.Clear();
|
||||||
|
dataView.Refresh();
|
||||||
|
_isSortedByLoadOrder = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_isSortedByLoadOrder = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ModList_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
//return;
|
||||||
|
|
||||||
|
_draggedMod = (ModItemInfo) TryFindRowAtPoint((UIElement) sender, e.GetPosition(ModList))?.DataContext;
|
||||||
|
|
||||||
|
//DraggedMod = (ModItemInfo) ModList.SelectedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataGridRow TryFindRowAtPoint(UIElement reference, Point point)
|
||||||
|
{
|
||||||
|
var element = reference.InputHitTest(point) as DependencyObject;
|
||||||
|
if (element == null)
|
||||||
|
return null;
|
||||||
|
if (element is DataGridRow row)
|
||||||
|
return row;
|
||||||
|
else
|
||||||
|
return TryFindParent<DataGridRow>(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T TryFindParent<T>(DependencyObject child) where T : DependencyObject
|
||||||
|
{
|
||||||
|
DependencyObject parent;
|
||||||
|
if (child == null)
|
||||||
|
return null;
|
||||||
|
if (child is ContentElement contentElement)
|
||||||
|
{
|
||||||
|
parent = ContentOperations.GetParent(contentElement);
|
||||||
|
if (parent == null && child is FrameworkContentElement fce)
|
||||||
|
parent = fce.Parent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parent = VisualTreeHelper.GetParent(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent is T result)
|
||||||
|
return result;
|
||||||
|
else
|
||||||
|
return TryFindParent<T>(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UserControl_MouseMove(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (_draggedMod == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_isSortedByLoadOrder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var targetMod = (ModItemInfo)TryFindRowAtPoint((UIElement)sender, e.GetPosition(ModList))?.DataContext;
|
||||||
|
if( targetMod != null && !ReferenceEquals(_draggedMod, targetMod))
|
||||||
|
{
|
||||||
|
_hasOrderChanged = true;
|
||||||
|
var modList = (MtObservableList<ModItemInfo>)DataContext;
|
||||||
|
//modList.Move(modList.IndexOf(_draggedMod), modList.IndexOf(targetMod));
|
||||||
|
modList.RemoveAt(modList.IndexOf(_draggedMod));
|
||||||
|
modList.Insert(modList.IndexOf(targetMod), _draggedMod);
|
||||||
|
ModList.Items.Refresh();
|
||||||
|
ModList.SelectedItem = _draggedMod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ModList_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isSortedByLoadOrder)
|
||||||
|
{
|
||||||
|
var targetMod = (ModItemInfo)TryFindRowAtPoint((UIElement)sender, e.GetPosition(ModList))?.DataContext;
|
||||||
|
if (targetMod != null && !ReferenceEquals(_draggedMod, targetMod))
|
||||||
|
{
|
||||||
|
var msg = "Drag and drop is only available when sorted by load order!";
|
||||||
|
Log.Warn(msg);
|
||||||
|
MessageBox.Show(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//if (DraggedMod != null && HasOrderChanged)
|
||||||
|
//Log.Info("Dragging over, saving...");
|
||||||
|
//_instanceManager.SaveConfig();
|
||||||
|
_draggedMod = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
[NotifyPropertyChangedInvocator]
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ModList_Selected(object sender, SelectedCellsChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_draggedMod != null)
|
||||||
|
ModList.SelectedItem = _draggedMod;
|
||||||
|
else if( e.AddedCells.Count > 0)
|
||||||
|
ModList.SelectedItem = e.AddedCells[0].Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BulkButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var editor = new CollectionEditor();
|
||||||
|
|
||||||
|
//let's see just how poorly we can do this
|
||||||
|
var modList = ((MtObservableList<ModItemInfo>)DataContext).ToList();
|
||||||
|
var idList = modList.Select(m => m.PublishedFileId).ToList();
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
//blocking
|
||||||
|
editor.Edit<ulong>(idList, "Mods");
|
||||||
|
|
||||||
|
modList.RemoveAll(m => !idList.Contains(m.PublishedFileId));
|
||||||
|
foreach (var id in idList)
|
||||||
|
{
|
||||||
|
if (!modList.Any(m => m.PublishedFileId == id))
|
||||||
|
{
|
||||||
|
var mod = new ModItemInfo(new MyObjectBuilder_Checkpoint.ModItem(id));
|
||||||
|
tasks.Add(Task.Run(mod.UpdateModInfoAsync));
|
||||||
|
modList.Add(mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_instanceManager.DedicatedConfig.Mods.Clear();
|
||||||
|
foreach (var mod in modList)
|
||||||
|
_instanceManager.DedicatedConfig.Mods.Add(mod);
|
||||||
|
|
||||||
|
if (tasks.Any())
|
||||||
|
Task.WaitAll(tasks.ToArray());
|
||||||
|
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
_instanceManager.DedicatedConfig.Save();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -80,6 +80,9 @@ namespace Torch.Server
|
|||||||
|
|
||||||
private void KickButton_Click(object sender, RoutedEventArgs e)
|
private void KickButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (PlayerList.SelectedItem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -93,6 +96,9 @@ namespace Torch.Server
|
|||||||
|
|
||||||
private void BanButton_Click(object sender, RoutedEventArgs e)
|
private void BanButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (PlayerList.SelectedItem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -106,6 +112,9 @@ namespace Torch.Server
|
|||||||
|
|
||||||
private void PromoteButton_OnClick(object sender, RoutedEventArgs e)
|
private void PromoteButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (PlayerList.SelectedItem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -119,6 +128,9 @@ namespace Torch.Server
|
|||||||
|
|
||||||
private void DemoteButton_OnClick(object sender, RoutedEventArgs e)
|
private void DemoteButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (PlayerList.SelectedItem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
124
Torch.Server/Views/PluginBrowser.xaml
Normal file
124
Torch.Server/Views/PluginBrowser.xaml
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<Window x:Class="Torch.Server.Views.PluginBrowser"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:Markdown.Xaml;assembly=Markdown.Xaml"
|
||||||
|
xmlns:system="clr-namespace:System;assembly=mscorlib"
|
||||||
|
xmlns:views="clr-namespace:Torch.Server.Views"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="PluginBrowser" Height="400" Width="600"
|
||||||
|
DataContext="{Binding RelativeSource={RelativeSource Self}}">
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<Style TargetType="FlowDocument" x:Key="DocumentStyle">
|
||||||
|
<Setter Property="FontFamily"
|
||||||
|
Value="Calibri" />
|
||||||
|
<Setter Property="TextAlignment"
|
||||||
|
Value="Left" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="H1Style"
|
||||||
|
TargetType="Paragraph">
|
||||||
|
<Setter Property="FontSize"
|
||||||
|
Value="42" />
|
||||||
|
<Setter Property="Foreground"
|
||||||
|
Value="#ff000000" />
|
||||||
|
<Setter Property="FontWeight"
|
||||||
|
Value="Light" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="H2Style"
|
||||||
|
TargetType="Paragraph">
|
||||||
|
<Setter Property="FontSize"
|
||||||
|
Value="20" />
|
||||||
|
<Setter Property="Foreground"
|
||||||
|
Value="#ff000000" />
|
||||||
|
<Setter Property="FontWeight"
|
||||||
|
Value="Light" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="H3Style"
|
||||||
|
TargetType="Paragraph">
|
||||||
|
<Setter Property="FontSize"
|
||||||
|
Value="20" />
|
||||||
|
<Setter Property="Foreground"
|
||||||
|
Value="#99000000" />
|
||||||
|
<Setter Property="FontWeight"
|
||||||
|
Value="Light" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="H4Style"
|
||||||
|
TargetType="Paragraph">
|
||||||
|
<Setter Property="FontSize"
|
||||||
|
Value="14" />
|
||||||
|
<Setter Property="Foreground"
|
||||||
|
Value="#99000000" />
|
||||||
|
<Setter Property="FontWeight"
|
||||||
|
Value="Light" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="LinkStyle"
|
||||||
|
TargetType="Hyperlink">
|
||||||
|
<Setter Property="TextDecorations"
|
||||||
|
Value="None" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="ImageStyle"
|
||||||
|
TargetType="Image">
|
||||||
|
<Setter Property="RenderOptions.BitmapScalingMode"
|
||||||
|
Value="NearestNeighbor" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="Tag"
|
||||||
|
Value="imageright">
|
||||||
|
<Setter Property="Margin"
|
||||||
|
Value="20,0,0,0" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="SeparatorStyle"
|
||||||
|
TargetType="Line">
|
||||||
|
<Setter Property="X2"
|
||||||
|
Value="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=FlowDocumentScrollViewer}}" />
|
||||||
|
<Setter Property="Stroke"
|
||||||
|
Value="#99000000" />
|
||||||
|
<Setter Property="StrokeThickness"
|
||||||
|
Value="2" />
|
||||||
|
</Style>
|
||||||
|
<local:Markdown x:Key="Markdown"
|
||||||
|
DocumentStyle="{StaticResource DocumentStyle}"
|
||||||
|
Heading1Style="{StaticResource H1Style}"
|
||||||
|
Heading2Style="{StaticResource H2Style}"
|
||||||
|
Heading3Style="{StaticResource H3Style}"
|
||||||
|
Heading4Style="{StaticResource H4Style}"
|
||||||
|
LinkStyle="{StaticResource LinkStyle}"
|
||||||
|
ImageStyle="{StaticResource ImageStyle}"
|
||||||
|
SeparatorStyle="{StaticResource SeparatorStyle}"
|
||||||
|
AssetPathRoot="{x:Static system:Environment.CurrentDirectory}"/>
|
||||||
|
|
||||||
|
<local:TextToFlowDocumentConverter x:Key="TextToFlowDocumentConverter"
|
||||||
|
Markdown="{StaticResource Markdown}"/>
|
||||||
|
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<ListView Name="PluginsList" Width="150" Height="Auto" Margin="3" ItemsSource="{Binding Plugins}" SelectionChanged="PluginsList_SelectionChanged">
|
||||||
|
|
||||||
|
</ListView>
|
||||||
|
<Button Name="DownloadButton" Grid.Row ="1" Content="Download" Margin="3" Height="30" Click="DownloadButton_OnClick" IsEnabled="False"/>
|
||||||
|
</Grid>
|
||||||
|
<FlowDocumentScrollViewer Grid.Column="1" Name="MarkdownFlow" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="3" Document="{Binding CurrentDescription, Converter={StaticResource TextToFlowDocumentConverter}}"/>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
|
|
||||||
|
|
114
Torch.Server/Views/PluginBrowser.xaml.cs
Normal file
114
Torch.Server/Views/PluginBrowser.xaml.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NLog;
|
||||||
|
using Torch.API.WebAPI;
|
||||||
|
using Torch.Collections;
|
||||||
|
using Torch.Server.Annotations;
|
||||||
|
|
||||||
|
namespace Torch.Server.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for PluginBrowser.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class PluginBrowser : Window, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public MtObservableList<PluginItem> Plugins { get; set; } = new MtObservableList<PluginItem>();
|
||||||
|
public PluginItem CurrentItem { get; set; }
|
||||||
|
|
||||||
|
private string _description = "Loading data from server, please wait..";
|
||||||
|
public string CurrentDescription
|
||||||
|
{
|
||||||
|
get { return _description; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_description = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginBrowser()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var res = await PluginQuery.Instance.QueryAll();
|
||||||
|
if (res == null)
|
||||||
|
return;
|
||||||
|
foreach (var item in res.Plugins)
|
||||||
|
Plugins.Add(item);
|
||||||
|
PluginsList.Dispatcher.Invoke(() => PluginsList.SelectedIndex = 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
MarkdownFlow.CommandBindings.Add(new CommandBinding(NavigationCommands.GoToPage, (sender, e) => OpenUri((string)e.Parameter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsValidUri(string uri)
|
||||||
|
{
|
||||||
|
if (!Uri.IsWellFormedUriString(uri, UriKind.Absolute))
|
||||||
|
return false;
|
||||||
|
Uri tmp;
|
||||||
|
if (!Uri.TryCreate(uri, UriKind.Absolute, out tmp))
|
||||||
|
return false;
|
||||||
|
return tmp.Scheme == Uri.UriSchemeHttp || tmp.Scheme == Uri.UriSchemeHttps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool OpenUri(string uri)
|
||||||
|
{
|
||||||
|
if (!IsValidUri(uri))
|
||||||
|
return false;
|
||||||
|
System.Diagnostics.Process.Start(uri);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PluginsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
CurrentItem = (PluginItem)PluginsList.SelectedItem;
|
||||||
|
CurrentDescription = CurrentItem.Description;
|
||||||
|
DownloadButton.IsEnabled = !string.IsNullOrEmpty(CurrentItem.LatestVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DownloadButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var item = CurrentItem;
|
||||||
|
TorchBase.Instance.Config.Plugins.Add(new Guid(item.ID));
|
||||||
|
TorchBase.Instance.Config.Save();
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var result = await PluginQuery.Instance.DownloadPlugin(item.ID);
|
||||||
|
MessageBox.Show(result ? "Plugin downloaded successfully! Please restart the server to load changes."
|
||||||
|
: "Plugin failed to download! See log for details.",
|
||||||
|
"Plugin Downloader",
|
||||||
|
MessageBoxButton.OK);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
[NotifyPropertyChangedInvocator]
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -22,11 +22,18 @@
|
|||||||
<ListView Grid.Row="0" ItemsSource="{Binding Plugins}" SelectedItem="{Binding SelectedPlugin}" Margin="3">
|
<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}" Background="{Binding Color}" ToolTip="{Binding ToolTip}"/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView.ItemTemplate>
|
||||||
</ListView>
|
</ListView>
|
||||||
<Button Grid.Row="1" Content="Open Folder" Margin="3" DockPanel.Dock="Bottom" Click="OpenFolder_OnClick"/>
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Button Grid.Row="0" Grid.Column="0" Content="Open Folder" Margin="3" Click="OpenFolder_OnClick"/>
|
||||||
|
<Button Grid.Row="0" Grid.Column="1" Content="Browse Plugins" Margin="3" Click="BrowsPlugins_OnClick"/>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<ScrollViewer Name="PScroll" Grid.Column="1" Margin="3">
|
<ScrollViewer Name="PScroll" Grid.Column="1" Margin="3">
|
||||||
<Frame NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/>
|
<Frame NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/>
|
||||||
|
@@ -41,7 +41,9 @@ namespace Torch.Server.Views
|
|||||||
{
|
{
|
||||||
if (propertyChangedEventArgs.PropertyName == nameof(PluginManagerViewModel.SelectedPlugin))
|
if (propertyChangedEventArgs.PropertyName == nameof(PluginManagerViewModel.SelectedPlugin))
|
||||||
{
|
{
|
||||||
if (((PluginManagerViewModel)DataContext).SelectedPlugin.Control is PropertyGrid)
|
var plugin = ((PluginManagerViewModel)DataContext).SelectedPlugin;
|
||||||
|
|
||||||
|
if (plugin.Control is PropertyGrid || !plugin.Control.GetScrollContainer())
|
||||||
PScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
|
PScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
|
||||||
else
|
else
|
||||||
PScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
|
PScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
|
||||||
@@ -71,5 +73,11 @@ namespace Torch.Server.Views
|
|||||||
if (_plugins?.PluginDir != null)
|
if (_plugins?.PluginDir != null)
|
||||||
Process.Start(_plugins.PluginDir);
|
Process.Start(_plugins.PluginDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BrowsPlugins_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var browser = new PluginBrowser();
|
||||||
|
browser.Show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,5 +18,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
<converters:ListConverter x:Key="ListConverterString" Type="system:String"/>
|
<converters:ListConverter x:Key="ListConverterString" Type="system:String"/>
|
||||||
<converters:ListConverter x:Key="ListConverterUInt64" Type="system:UInt64"/>
|
<converters:ListConverter x:Key="ListConverterUInt64" Type="system:UInt64"/>
|
||||||
|
<converters:ModToListIdConverter x:Key="ModToListIdConverter"/>
|
||||||
|
<converters:ListConverterWorkshopId x:Key="ListConverterWorkshopId"/>
|
||||||
<converters:BooleanAndConverter x:Key="BooleanAndConverter"/>
|
<converters:BooleanAndConverter x:Key="BooleanAndConverter"/>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
52
Torch.Server/Views/RoleEditor.xaml
Normal file
52
Torch.Server/Views/RoleEditor.xaml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<Window x:Class="Torch.Server.Views.RoleEditor"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:Torch.Server.Views"
|
||||||
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
|
xmlns:modApi="clr-namespace:VRage.Game.ModAPI;assembly=VRage.Game"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="RoleEditor" Height="300" Width="300">
|
||||||
|
<Window.Resources>
|
||||||
|
<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="GetEnumValues">
|
||||||
|
<ObjectDataProvider.MethodParameters>
|
||||||
|
<x:Type TypeName="modApi:MyPromoteLevel"/>
|
||||||
|
</ObjectDataProvider.MethodParameters>
|
||||||
|
</ObjectDataProvider>
|
||||||
|
</Window.Resources>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<DataGrid x:Name="ItemGrid" AutoGenerateColumns="false" CanUserAddRows="true" Grid.Row="0">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Width="5*" Header="Key" Binding="{Binding Key}"/>
|
||||||
|
<DataGridComboBoxColumn Width ="5*" Header="Value" ItemsSource="{Binding Source={StaticResource GetEnumValues}}" SelectedValueBinding="{Binding Value, Mode=TwoWay}"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
<Button Grid.Row="1" Content="Add New" Margin="5" Click="AddNew_OnClick"></Button>
|
||||||
|
<Grid Grid.Row="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Button Grid.Column="0" Content="Cancel" Margin="5" Click="Cancel_OnClick" />
|
||||||
|
<Button Grid.Column="1" Content="OK" Margin="5" Click="Ok_OnClick" />
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Row="3">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<ComboBox Name="BulkSelect" ItemsSource="{Binding Source={StaticResource GetEnumValues}}" SelectedValue ="{Binding
|
||||||
|
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RoleEditor}},
|
||||||
|
Path = BulkPromote, Mode=TwoWay}" Margin="5"/>
|
||||||
|
<Button Grid.Column="1" Content="Bulk edit" Margin ="5" Click="BulkEdit"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
|
|
123
Torch.Server/Views/RoleEditor.xaml.cs
Normal file
123
Torch.Server/Views/RoleEditor.xaml.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using Torch.Server.Managers;
|
||||||
|
using Torch.Views;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
|
namespace Torch.Server.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for RoleEditor.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class RoleEditor : Window
|
||||||
|
{
|
||||||
|
public RoleEditor()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = Items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<IDictionaryItem> Items { get; } = new ObservableCollection<IDictionaryItem>();
|
||||||
|
private Type _itemType;
|
||||||
|
|
||||||
|
private Action _commitChanges;
|
||||||
|
public MyPromoteLevel BulkPromote { get; set; } = MyPromoteLevel.Scripter;
|
||||||
|
|
||||||
|
public void Edit(IDictionary dict)
|
||||||
|
{
|
||||||
|
Items.Clear();
|
||||||
|
var dictType = dict.GetType();
|
||||||
|
_itemType = typeof(DictionaryItem<,>).MakeGenericType(dictType.GenericTypeArguments[0], dictType.GenericTypeArguments[1]);
|
||||||
|
|
||||||
|
foreach (var key in dict.Keys)
|
||||||
|
{
|
||||||
|
Items.Add((IDictionaryItem)Activator.CreateInstance(_itemType, key, dict[key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemGrid.ItemsSource = Items;
|
||||||
|
|
||||||
|
_commitChanges = () =>
|
||||||
|
{
|
||||||
|
dict.Clear();
|
||||||
|
foreach (var item in Items)
|
||||||
|
{
|
||||||
|
dict[item.Key] = item.Value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Ok_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_commitChanges?.Invoke();
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDictionaryItem
|
||||||
|
{
|
||||||
|
object Key { get; set; }
|
||||||
|
object Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DictionaryItem<TKey, TValue> : ViewModel, IDictionaryItem
|
||||||
|
{
|
||||||
|
private TKey _key;
|
||||||
|
private TValue _value;
|
||||||
|
|
||||||
|
object IDictionaryItem.Key { get => _key; set => SetValue(ref _key, (TKey)value); }
|
||||||
|
object IDictionaryItem.Value { get => _value; set => SetValue(ref _value, (TValue)value); }
|
||||||
|
|
||||||
|
public TKey Key { get => _key; set => SetValue(ref _key, value); }
|
||||||
|
public TValue Value { get => _value; set => SetValue(ref _value, value); }
|
||||||
|
|
||||||
|
public DictionaryItem()
|
||||||
|
{
|
||||||
|
_key = default(TKey);
|
||||||
|
_value = default(TValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DictionaryItem(TKey key, TValue value)
|
||||||
|
{
|
||||||
|
_key = key;
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddNew_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Items.Add((IDictionaryItem)Activator.CreateInstance(_itemType));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BulkEdit(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
List<ulong> l = Items.Where(i => i.Value.Equals(BulkPromote)).Select(i => (ulong)i.Key).ToList();
|
||||||
|
var w = new CollectionEditor();
|
||||||
|
w.Edit((ICollection<ulong>)l, "Bulk edit");
|
||||||
|
var r = Items.Where(j => j.Value.Equals(BulkPromote) || l.Contains((ulong)j.Key)).ToList();
|
||||||
|
foreach (var k in r)
|
||||||
|
Items.Remove(k);
|
||||||
|
foreach (var m in l)
|
||||||
|
Items.Add(new DictionaryItem<ulong, MyPromoteLevel>(m, BulkPromote));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -57,7 +57,7 @@
|
|||||||
</Label>
|
</Label>
|
||||||
<Label x:Name="LabelPlayers">
|
<Label x:Name="LabelPlayers">
|
||||||
<Label.Content>
|
<Label.Content>
|
||||||
<TextBlock ></TextBlock>
|
<TextBlock Text="{Binding OnlinePlayers, StringFormat=Players: {0}}"/>
|
||||||
</Label.Content>
|
</Label.Content>
|
||||||
</Label>
|
</Label>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
<RichTextBox x:Name="ConsoleText" VerticalScrollBarVisibility="Visible" FontFamily="Consolas" IsReadOnly="True"/>
|
<RichTextBox x:Name="ConsoleText" VerticalScrollBarVisibility="Visible" FontFamily="Consolas" IsReadOnly="True"/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Configuration">
|
<TabItem Header="Configuration">
|
||||||
<Grid IsEnabled="{Binding CanRun}">
|
<Grid IsEnabled="{Binding Path=HasRun, Converter={StaticResource InverseBool}}">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition/>
|
<RowDefinition/>
|
||||||
@@ -80,6 +80,9 @@
|
|||||||
<views:ConfigControl Grid.Row="1" x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" IsEnabled="{Binding CanRun}"/>
|
<views:ConfigControl Grid.Row="1" x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" IsEnabled="{Binding CanRun}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
<TabItem Header="Mods">
|
||||||
|
<views:ModListControl/>
|
||||||
|
</TabItem>
|
||||||
<TabItem Header="Chat/Players">
|
<TabItem Header="Chat/Players">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
@@ -10,6 +10,7 @@ using System.Windows.Controls;
|
|||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using System.Windows.Documents;
|
using System.Windows.Documents;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
@@ -31,8 +32,13 @@ namespace Torch.Server
|
|||||||
private TorchServer _server;
|
private TorchServer _server;
|
||||||
private TorchConfig _config;
|
private TorchConfig _config;
|
||||||
|
|
||||||
|
private bool _autoscrollLog = true;
|
||||||
|
|
||||||
public TorchUI(TorchServer server)
|
public TorchUI(TorchServer server)
|
||||||
{
|
{
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||||
|
Width = 800;
|
||||||
|
Height = 600;
|
||||||
_config = (TorchConfig)server.Config;
|
_config = (TorchConfig)server.Config;
|
||||||
_server = server;
|
_server = server;
|
||||||
//TODO: data binding for whole server
|
//TODO: data binding for whole server
|
||||||
@@ -41,10 +47,10 @@ namespace Torch.Server
|
|||||||
|
|
||||||
AttachConsole();
|
AttachConsole();
|
||||||
|
|
||||||
Left = _config.WindowPosition.X;
|
//Left = _config.WindowPosition.X;
|
||||||
Top = _config.WindowPosition.Y;
|
//Top = _config.WindowPosition.Y;
|
||||||
Width = _config.WindowSize.X;
|
//Width = _config.WindowSize.X;
|
||||||
Height = _config.WindowSize.Y;
|
//Height = _config.WindowSize.Y;
|
||||||
|
|
||||||
Chat.BindServer(server);
|
Chat.BindServer(server);
|
||||||
PlayerList.BindServer(server);
|
PlayerList.BindServer(server);
|
||||||
@@ -54,6 +60,14 @@ namespace Torch.Server
|
|||||||
Themes.uiSource = this;
|
Themes.uiSource = this;
|
||||||
Themes.SetConfig(_config);
|
Themes.SetConfig(_config);
|
||||||
Title = $"{_config.InstanceName} - Torch {server.TorchVersion}, SE {server.GameVersion}";
|
Title = $"{_config.InstanceName} - Torch {server.TorchVersion}, SE {server.GameVersion}";
|
||||||
|
|
||||||
|
Loaded += TorchUI_Loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TorchUI_Loaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var scrollViewer = FindDescendant<ScrollViewer>(ConsoleText);
|
||||||
|
scrollViewer.ScrollChanged += ConsoleText_OnScrollChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AttachConsole()
|
private void AttachConsole()
|
||||||
@@ -66,7 +80,52 @@ namespace Torch.Server
|
|||||||
doc = (wrapped?.WrappedTarget as FlowDocumentTarget)?.Document;
|
doc = (wrapped?.WrappedTarget as FlowDocumentTarget)?.Document;
|
||||||
}
|
}
|
||||||
ConsoleText.Document = doc ?? new FlowDocument(new Paragraph(new Run("No target!")));
|
ConsoleText.Document = doc ?? new FlowDocument(new Paragraph(new Run("No target!")));
|
||||||
ConsoleText.TextChanged += (sender, args) => ConsoleText.ScrollToEnd();
|
ConsoleText.TextChanged += ConsoleText_OnTextChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T FindDescendant<T>(DependencyObject obj) where T : DependencyObject
|
||||||
|
{
|
||||||
|
if (obj == null) return default(T);
|
||||||
|
int numberChildren = VisualTreeHelper.GetChildrenCount(obj);
|
||||||
|
if (numberChildren == 0) return default(T);
|
||||||
|
|
||||||
|
for (int i = 0; i < numberChildren; i++)
|
||||||
|
{
|
||||||
|
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
|
||||||
|
if (child is T)
|
||||||
|
{
|
||||||
|
return (T)child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < numberChildren; i++)
|
||||||
|
{
|
||||||
|
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
|
||||||
|
var potentialMatch = FindDescendant<T>(child);
|
||||||
|
if (potentialMatch != default(T))
|
||||||
|
{
|
||||||
|
return potentialMatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConsoleText_OnTextChanged(object sender, TextChangedEventArgs args)
|
||||||
|
{
|
||||||
|
var textBox = (RichTextBox) sender;
|
||||||
|
if (_autoscrollLog)
|
||||||
|
ConsoleText.ScrollToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConsoleText_OnScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var scrollViewer = (ScrollViewer) sender;
|
||||||
|
if (e.ExtentHeightChange == 0)
|
||||||
|
{
|
||||||
|
// User change.
|
||||||
|
_autoscrollLog = scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadConfig(TorchConfig config)
|
public void LoadConfig(TorchConfig config)
|
||||||
@@ -97,10 +156,14 @@ namespace Torch.Server
|
|||||||
|
|
||||||
protected override void OnClosing(CancelEventArgs e)
|
protected override void OnClosing(CancelEventArgs e)
|
||||||
{
|
{
|
||||||
var newSize = new Point((int)Width, (int)Height);
|
// Can't save here or you'll persist all the command line arguments
|
||||||
_config.WindowSize = newSize;
|
//
|
||||||
var newPos = new Point((int)Left, (int)Top);
|
//var newSize = new Point((int)Width, (int)Height);
|
||||||
_config.WindowPosition = newPos;
|
//_config.WindowSize = newSize;
|
||||||
|
//var newPos = new Point((int)Left, (int)Top);
|
||||||
|
//_config.WindowPosition = newPos;
|
||||||
|
|
||||||
|
//_config.Save(); //you idiot
|
||||||
|
|
||||||
if (_server?.State == ServerState.Running)
|
if (_server?.State == ServerState.Running)
|
||||||
_server.Stop();
|
_server.Stop();
|
||||||
|
@@ -5,14 +5,15 @@
|
|||||||
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:views1="clr-namespace:Torch.Views;assembly=Torch"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="WorldGeneratorDialog" Height="300" Width="700">
|
Title="WorldGeneratorDialog" Height="500" Width="700">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="250"/>
|
<ColumnDefinition Width="250"/>
|
||||||
<ColumnDefinition Width="440"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<ListView Grid.Column="0" x:Name="PremadeCheckpoints" ScrollViewer.CanContentScroll="False" HorizontalContentAlignment="Center" Margin="3">
|
<ListView Grid.Column="0" x:Name="PremadeCheckpoints" ScrollViewer.CanContentScroll="False" HorizontalContentAlignment="Center" Margin="3" SelectionChanged="PremadeCheckpoints_SelectionChanged">
|
||||||
<ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
<DataTemplate DataType="local:PremadeCheckpointItem">
|
<DataTemplate DataType="local:PremadeCheckpointItem">
|
||||||
<StackPanel HorizontalAlignment="Center">
|
<StackPanel HorizontalAlignment="Center">
|
||||||
@@ -26,13 +27,18 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView.ItemTemplate>
|
||||||
</ListView>
|
</ListView>
|
||||||
<StackPanel Grid.Column="1" Margin="3">
|
<Grid Grid.Column="1" Margin="3">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height ="Auto"/>
|
||||||
|
<RowDefinition Height ="*"/>
|
||||||
|
<RowDefinition Height ="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Label Content="World Name: "/>
|
<Label Content="World Name: "/>
|
||||||
<TextBox x:Name="WorldName" Width="300" Margin="3"/>
|
<TextBox x:Name="WorldName" Width="300" Margin="3"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<views:SessionSettingsView/>
|
<views1:PropertyGrid Grid.Row="1" x:Name="SettingsView" Margin="3"/>
|
||||||
<Button Content="Create World" Click="ButtonBase_OnClick"/>
|
<Button Grid.Row="2" Content="Create World" Click="ButtonBase_OnClick" Margin ="3"/>
|
||||||
</StackPanel>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
@@ -13,7 +13,15 @@ using System.Windows.Media;
|
|||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Sandbox.Definitions;
|
using Sandbox.Definitions;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
|
using Sandbox.Game.World;
|
||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
|
using Torch.Server.ViewModels;
|
||||||
|
using Torch.Utils;
|
||||||
|
using VRage;
|
||||||
|
using VRage.Dedicated;
|
||||||
|
using VRage.FileSystem;
|
||||||
|
using VRage.Game;
|
||||||
using VRage.Game.Localization;
|
using VRage.Game.Localization;
|
||||||
using VRage.Utils;
|
using VRage.Utils;
|
||||||
|
|
||||||
@@ -26,19 +34,25 @@ namespace Torch.Server
|
|||||||
{
|
{
|
||||||
private InstanceManager _instanceManager;
|
private InstanceManager _instanceManager;
|
||||||
private List<PremadeCheckpointItem> _checkpoints = new List<PremadeCheckpointItem>();
|
private List<PremadeCheckpointItem> _checkpoints = new List<PremadeCheckpointItem>();
|
||||||
|
private PremadeCheckpointItem _currentItem;
|
||||||
|
|
||||||
|
[ReflectedStaticMethod(Type = typeof(ConfigForm), Name = "LoadLocalization")]
|
||||||
|
private static Action _loadLocalization;
|
||||||
|
|
||||||
public WorldGeneratorDialog(InstanceManager instanceManager)
|
public WorldGeneratorDialog(InstanceManager instanceManager)
|
||||||
{
|
{
|
||||||
_instanceManager = instanceManager;
|
_instanceManager = instanceManager;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_loadLocalization();
|
||||||
MyDefinitionManager.Static.LoadScenarios();
|
var scenarios = MyLocalCache.GetAvailableWorldInfos(Path.Combine(MyFileSystem.ContentPath, "CustomWorlds"));
|
||||||
var scenarios = MyDefinitionManager.Static.GetScenarioDefinitions();
|
foreach (var tup in scenarios)
|
||||||
MyDefinitionManager.Static.UnloadData();
|
|
||||||
foreach (var scenario in scenarios)
|
|
||||||
{
|
{
|
||||||
//TODO: Load localization
|
string directory = tup.Item1;
|
||||||
_checkpoints.Add(new PremadeCheckpointItem { Name = scenario.DisplayNameText, Icon = @"C:\Users\jgross\Documents\Projects\TorchAPI\Torch\bin\x64\Release\Content\CustomWorlds\Empty World\thumb.jpg" });
|
MyWorldInfo info = tup.Item2;
|
||||||
|
string localizedName = MyTexts.GetString(MyStringId.GetOrCompute(info.SessionName));
|
||||||
|
var checkpoint = MyLocalCache.LoadCheckpoint(directory, out _);
|
||||||
|
checkpoint.OnlineMode = MyOnlineModeEnum.PUBLIC;
|
||||||
|
_checkpoints.Add(new PremadeCheckpointItem { Name = localizedName, Icon = Path.Combine(directory, "thumb.jpg"), Path = directory, Checkpoint = checkpoint});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -56,23 +70,39 @@ namespace Torch.Server
|
|||||||
}*/
|
}*/
|
||||||
PremadeCheckpoints.ItemsSource = _checkpoints;
|
PremadeCheckpoints.ItemsSource = _checkpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
|
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
/*
|
string worldName = string.IsNullOrEmpty(WorldName.Text) ? _currentItem.Name : WorldName.Text;
|
||||||
var worldPath = Path.Combine("Instance", "Saves", WorldName.Text);
|
|
||||||
var checkpointItem = (PremadeCheckpointItem)PremadeCheckpoints.SelectedItem;
|
var worldPath = Path.Combine(TorchBase.Instance.Config.InstancePath, "Saves", worldName);
|
||||||
|
var checkpoint = _currentItem.Checkpoint;
|
||||||
if (Directory.Exists(worldPath))
|
if (Directory.Exists(worldPath))
|
||||||
{
|
{
|
||||||
MessageBox.Show("World already exists with that name.");
|
MessageBox.Show("World already exists with that name.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Directory.CreateDirectory(worldPath);
|
Directory.CreateDirectory(worldPath);
|
||||||
foreach (var file in Directory.EnumerateFiles(checkpointItem.Path, "*", SearchOption.AllDirectories))
|
foreach (var file in Directory.EnumerateFiles(_currentItem.Path, "*", SearchOption.AllDirectories))
|
||||||
{
|
{
|
||||||
File.Copy(file, Path.Combine(worldPath, file.Replace($"{checkpointItem.Path}\\", "")));
|
File.Copy(file, Path.Combine(worldPath, file.Replace($"{_currentItem.Path}\\", "")));
|
||||||
}
|
}
|
||||||
_instanceManager.SelectWorld(worldPath, false);*/
|
|
||||||
|
checkpoint.SessionName = worldName;
|
||||||
|
|
||||||
|
MyLocalCache.SaveCheckpoint(checkpoint, worldPath);
|
||||||
|
|
||||||
|
|
||||||
|
_instanceManager.SelectWorld(worldPath, false);
|
||||||
|
_instanceManager.ImportSelectedWorldConfig();
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PremadeCheckpoints_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var selected = (PremadeCheckpointItem)PremadeCheckpoints.SelectedItem;
|
||||||
|
_currentItem = selected;
|
||||||
|
SettingsView.DataContext = new SessionSettingsViewModel(_currentItem.Checkpoint.Settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,5 +111,6 @@ namespace Torch.Server
|
|||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Icon { get; set; }
|
public string Icon { get; set; }
|
||||||
|
public MyObjectBuilder_Checkpoint Checkpoint { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,14 @@
|
|||||||
<packages>
|
<packages>
|
||||||
<package id="ControlzEx" version="3.0.2.4" targetFramework="net461" />
|
<package id="ControlzEx" version="3.0.2.4" targetFramework="net461" />
|
||||||
<package id="MahApps.Metro" version="1.6.1" targetFramework="net461" />
|
<package id="MahApps.Metro" version="1.6.1" targetFramework="net461" />
|
||||||
|
<package id="Markdown.Xaml" version="1.0.0" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Win32.Registry" version="4.4.0" targetFramework="net461" />
|
||||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net461" />
|
||||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||||
|
<package id="protobuf-net" version="2.4.0" targetFramework="net461" />
|
||||||
|
<package id="SteamKit2" version="2.1.0" targetFramework="net461" />
|
||||||
|
<package id="System.ComponentModel.Annotations" version="4.5.0" targetFramework="net461" />
|
||||||
|
<package id="System.Security.AccessControl" version="4.4.0" targetFramework="net461" />
|
||||||
|
<package id="System.Security.Principal.Windows" version="4.4.0" targetFramework="net461" />
|
||||||
</packages>
|
</packages>
|
@@ -82,6 +82,7 @@
|
|||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Include="app.config" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
11
Torch.Tests/app.config
Normal file
11
Torch.Tests/app.config
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="protobuf-net" publicKeyToken="257b51d87d2e4d67" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-2.4.0.0" newVersion="2.4.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
@@ -14,6 +14,7 @@ EndProject
|
|||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AD02A71-1D4C-48F9-A8C1-789A5512424F}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AD02A71-1D4C-48F9-A8C1-789A5512424F}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
NLog.config = NLog.config
|
NLog.config = NLog.config
|
||||||
|
NLog-user.config = NLog-user.config
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Tests", "Torch.Tests\Torch.Tests.csproj", "{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Tests", "Torch.Tests\Torch.Tests.csproj", "{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}"
|
||||||
@@ -55,10 +56,8 @@ Global
|
|||||||
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|x64.Build.0 = Release|x64
|
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|x64.Build.0 = Release|x64
|
||||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.ActiveCfg = Debug|x64
|
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|Any CPU.ActiveCfg = Release|x64
|
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.ActiveCfg = Release|x64
|
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.ActiveCfg = Release|x64
|
||||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.Build.0 = Release|x64
|
|
||||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.ActiveCfg = Debug|x64
|
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.Build.0 = Debug|x64
|
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.Build.0 = Debug|x64
|
||||||
@@ -79,10 +78,8 @@ Global
|
|||||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.Build.0 = Release|x64
|
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.Build.0 = Release|x64
|
||||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|Any CPU.ActiveCfg = Debug|x64
|
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|x64.ActiveCfg = Debug|x64
|
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|Any CPU.ActiveCfg = Release|x64
|
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.ActiveCfg = Release|x64
|
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.ActiveCfg = Release|x64
|
||||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.Build.0 = Release|x64
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
272
Torch/Collections/BinaryMinHeap.cs
Normal file
272
Torch/Collections/BinaryMinHeap.cs
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Torch.Collections
|
||||||
|
{
|
||||||
|
public class BinaryMinHeap<TKey, TValue> where TKey : IComparable
|
||||||
|
{
|
||||||
|
private struct HeapItem
|
||||||
|
{
|
||||||
|
public TKey Key { get; }
|
||||||
|
public TValue Value { get; }
|
||||||
|
|
||||||
|
public HeapItem(TKey key, TValue value)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HeapItem[] _store;
|
||||||
|
private readonly IComparer<TKey> _comparer;
|
||||||
|
|
||||||
|
public int Capacity { get; private set; }
|
||||||
|
public int Count { get; private set; }
|
||||||
|
|
||||||
|
public bool Full => Count == Capacity;
|
||||||
|
|
||||||
|
public BinaryMinHeap(int initialCapacity = 32, IComparer<TKey> comparer = null)
|
||||||
|
{
|
||||||
|
_store = new HeapItem[initialCapacity];
|
||||||
|
Count = 0;
|
||||||
|
Capacity = initialCapacity;
|
||||||
|
_comparer = comparer ?? Comparer<TKey>.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(TValue value, TKey key)
|
||||||
|
{
|
||||||
|
EnsureCapacity(Capacity + 1);
|
||||||
|
|
||||||
|
var item = new HeapItem(key, value);
|
||||||
|
|
||||||
|
_store[Count] = item;
|
||||||
|
|
||||||
|
Up(Count);
|
||||||
|
Count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue Min()
|
||||||
|
{
|
||||||
|
return _store[0].Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TKey MinKey()
|
||||||
|
{
|
||||||
|
return _store[0].Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue RemoveMin()
|
||||||
|
{
|
||||||
|
TValue toReturn = _store[0].Value;
|
||||||
|
|
||||||
|
if (Count != 1)
|
||||||
|
{
|
||||||
|
SwapIndices(Count - 1, 0);
|
||||||
|
_store[Count - 1] = default(HeapItem);
|
||||||
|
Count--;
|
||||||
|
Down(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Count--;
|
||||||
|
_store[0] = default(HeapItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue RemoveMax()
|
||||||
|
{
|
||||||
|
Debug.Assert(Count > 0);
|
||||||
|
|
||||||
|
var maxIndex = 0;
|
||||||
|
|
||||||
|
var maxItem = _store[0];
|
||||||
|
|
||||||
|
for (var i = 1; i < Count; ++i)
|
||||||
|
{
|
||||||
|
var c = _store[i];
|
||||||
|
if (_comparer.Compare(maxItem.Key, c.Key) < 0)
|
||||||
|
{
|
||||||
|
maxIndex = i;
|
||||||
|
maxItem = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxIndex != Count)
|
||||||
|
{
|
||||||
|
SwapIndices(Count - 1, maxIndex);
|
||||||
|
Up(maxIndex);
|
||||||
|
}
|
||||||
|
Count--;
|
||||||
|
|
||||||
|
return maxItem.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue Remove(TValue value, IEqualityComparer<TValue> comparer = null)
|
||||||
|
{
|
||||||
|
if (Count == 0)
|
||||||
|
return default(TValue);
|
||||||
|
|
||||||
|
if (comparer == null)
|
||||||
|
comparer = EqualityComparer<TValue>.Default;
|
||||||
|
|
||||||
|
var itemIndex = -1;
|
||||||
|
|
||||||
|
for (var i = 0; i < Count; ++i)
|
||||||
|
{
|
||||||
|
if (comparer.Equals(value, _store[i].Value))
|
||||||
|
{
|
||||||
|
itemIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex != Count && itemIndex != -1)
|
||||||
|
{
|
||||||
|
TValue removed = _store[itemIndex].Value;
|
||||||
|
|
||||||
|
SwapIndices(Count - 1, itemIndex);
|
||||||
|
Up(itemIndex);
|
||||||
|
Down(itemIndex);
|
||||||
|
|
||||||
|
Count--;
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return default(TValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue Remove(TKey key)
|
||||||
|
{
|
||||||
|
Debug.Assert(Count > 0);
|
||||||
|
|
||||||
|
var itemIndex = 0;
|
||||||
|
|
||||||
|
for (var i = 1; i < Count; ++i)
|
||||||
|
{
|
||||||
|
if (_comparer.Compare(key, _store[i].Key) == 0)
|
||||||
|
itemIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
TValue removed;
|
||||||
|
|
||||||
|
if (itemIndex != Count)
|
||||||
|
{
|
||||||
|
removed = _store[itemIndex].Value;
|
||||||
|
|
||||||
|
SwapIndices(Count - 1, itemIndex);
|
||||||
|
Up(itemIndex);
|
||||||
|
Down(itemIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
removed = default(TValue);
|
||||||
|
|
||||||
|
Count--;
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Array.Clear(_store, 0, Capacity);
|
||||||
|
Count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Up(int index)
|
||||||
|
{
|
||||||
|
if (index == 0)
|
||||||
|
return;
|
||||||
|
int parentIndex = (index - 1) / 2;
|
||||||
|
HeapItem swap = _store[index];
|
||||||
|
if (_comparer.Compare(_store[parentIndex].Key, swap.Key) <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
SwapIndices(parentIndex, index);
|
||||||
|
index = parentIndex;
|
||||||
|
|
||||||
|
if (index == 0)
|
||||||
|
break;
|
||||||
|
parentIndex = (index - 1) / 2;
|
||||||
|
if (_comparer.Compare(_store[parentIndex].Key, swap.Key) <= 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
InsertItem(ref swap, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Down(int index)
|
||||||
|
{
|
||||||
|
if (Count == index + 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int left = index * 2 + 1;
|
||||||
|
int right = left + 1;
|
||||||
|
|
||||||
|
HeapItem swap = _store[index];
|
||||||
|
|
||||||
|
while (right <= Count) // While the current node has children
|
||||||
|
{
|
||||||
|
var nLeft = _store[left];
|
||||||
|
var nRight = _store[right];
|
||||||
|
|
||||||
|
if (right == Count || _comparer.Compare(nLeft.Key, nRight.Key) < 0) // Only the left child exists or the left child is smaller
|
||||||
|
{
|
||||||
|
if (_comparer.Compare(swap.Key, nLeft.Key) <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
SwapIndices(left, index);
|
||||||
|
|
||||||
|
index = left;
|
||||||
|
left = index * 2 + 1;
|
||||||
|
right = left + 1;
|
||||||
|
}
|
||||||
|
else // Right child exists and is smaller
|
||||||
|
{
|
||||||
|
if (_comparer.Compare(swap.Key, nRight.Key) <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
SwapIndices(right, index);
|
||||||
|
|
||||||
|
index = right;
|
||||||
|
left = index * 2 + 1;
|
||||||
|
right = left + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InsertItem(ref swap, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SwapIndices(int fromIndex, int toIndex)
|
||||||
|
{
|
||||||
|
_store[toIndex] = _store[fromIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertItem(ref HeapItem fromItem, int toIndex)
|
||||||
|
{
|
||||||
|
_store[toIndex] = fromItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnsureCapacity(int capacity)
|
||||||
|
{
|
||||||
|
if (_store.Length >= capacity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//double capacity until we reach the minimum requested capacity (or greater)
|
||||||
|
int newcap = Capacity * 2;
|
||||||
|
while (newcap < capacity)
|
||||||
|
newcap *= 2;
|
||||||
|
|
||||||
|
var newArray = new HeapItem[newcap];
|
||||||
|
Array.Copy(_store, newArray, Capacity);
|
||||||
|
|
||||||
|
_store = newArray;
|
||||||
|
Capacity = newcap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -123,10 +124,10 @@ namespace Torch.Collections
|
|||||||
private readonly Timer _flushEventQueue;
|
private readonly Timer _flushEventQueue;
|
||||||
private const int _eventRaiseDelay = 50;
|
private const int _eventRaiseDelay = 50;
|
||||||
|
|
||||||
private readonly Queue<NotifyCollectionChangedEventArgs> _collectionEventQueue =
|
private readonly ConcurrentQueue<NotifyCollectionChangedEventArgs> _collectionEventQueue =
|
||||||
new Queue<NotifyCollectionChangedEventArgs>();
|
new ConcurrentQueue<NotifyCollectionChangedEventArgs>();
|
||||||
|
|
||||||
private readonly Queue<string> _propertyEventQueue = new Queue<string>();
|
private readonly ConcurrentQueue<string> _propertyEventQueue = new ConcurrentQueue<string>();
|
||||||
|
|
||||||
private void FlushEventQueue(object data)
|
private void FlushEventQueue(object data)
|
||||||
{
|
{
|
||||||
@@ -137,7 +138,8 @@ namespace Torch.Collections
|
|||||||
// :/, but works better
|
// :/, but works better
|
||||||
bool reset = _collectionEventQueue.Count > 0;
|
bool reset = _collectionEventQueue.Count > 0;
|
||||||
if (reset)
|
if (reset)
|
||||||
_collectionEventQueue.Clear();
|
while (_collectionEventQueue.Count > 0)
|
||||||
|
_collectionEventQueue.TryDequeue(out _);
|
||||||
else
|
else
|
||||||
while (_collectionEventQueue.TryDequeue(out NotifyCollectionChangedEventArgs e))
|
while (_collectionEventQueue.TryDequeue(out NotifyCollectionChangedEventArgs e))
|
||||||
_collectionChangedEvent.Raise(this, e);
|
_collectionChangedEvent.Raise(this, e);
|
||||||
|
@@ -13,7 +13,7 @@ namespace Torch.Collections
|
|||||||
/// Multithread safe, observable list
|
/// Multithread safe, observable list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Value type</typeparam>
|
/// <typeparam name="T">Value type</typeparam>
|
||||||
public class MtObservableList<T> : MtObservableCollection<IList<T>, T>, IList<T>, IList
|
public class MtObservableList<T> : MtObservableCollection<List<T>, T>, IList<T>, IList
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the MtObservableList class that is empty and has the default initial capacity.
|
/// Initializes a new instance of the MtObservableList class that is empty and has the default initial capacity.
|
||||||
@@ -114,16 +114,34 @@ namespace Torch.Collections
|
|||||||
using (Lock.WriteUsing())
|
using (Lock.WriteUsing())
|
||||||
{
|
{
|
||||||
comparer = comparer ?? Comparer<TKey>.Default;
|
comparer = comparer ?? Comparer<TKey>.Default;
|
||||||
if (Backing is List<T> lst)
|
Backing.Sort(new TransformComparer<T, TKey>(selector, comparer));
|
||||||
lst.Sort(new TransformComparer<T, TKey>(selector, comparer));
|
|
||||||
else
|
|
||||||
{
|
|
||||||
List<T> sortedItems = Backing.OrderBy(selector, comparer).ToList();
|
|
||||||
Backing.Clear();
|
|
||||||
foreach (T v in sortedItems)
|
|
||||||
Backing.Add(v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sorts the list using the given comparer./>
|
||||||
|
/// </summary>
|
||||||
|
public void Sort(IComparer<T> comparer)
|
||||||
|
{
|
||||||
|
using (DeferredUpdate())
|
||||||
|
using (Lock.WriteUsing())
|
||||||
|
{
|
||||||
|
Backing.Sort(comparer);
|
||||||
|
}
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Searches the entire list for an element using the specified comparer and returns the zero-based index of the element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <param name="comparer"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int BinarySearch(T item, IComparer<T> comparer = null)
|
||||||
|
{
|
||||||
|
using(Lock.ReadUsing())
|
||||||
|
return Backing.BinarySearch(item, comparer ?? Comparer<T>.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
144
Torch/Collections/SortedView.cs
Normal file
144
Torch/Collections/SortedView.cs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
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.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Havok;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
|
namespace Torch.Collections
|
||||||
|
{
|
||||||
|
public class SortedView<T>: IReadOnlyCollection<T>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
private readonly MtObservableCollectionBase<T> _backing;
|
||||||
|
private IComparer<T> _comparer;
|
||||||
|
private readonly List<T> _store;
|
||||||
|
|
||||||
|
public SortedView(MtObservableCollectionBase<T> backing, IComparer<T> comparer)
|
||||||
|
{
|
||||||
|
_comparer = comparer;
|
||||||
|
_backing = backing;
|
||||||
|
_store = new List<T>(_backing.Count);
|
||||||
|
_store.AddRange(_backing);
|
||||||
|
_backing.CollectionChanged += backing_CollectionChanged;
|
||||||
|
_backing.PropertyChanged += backing_PropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void backing_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(e.PropertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void backing_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case NotifyCollectionChangedAction.Add:
|
||||||
|
InsertSorted(e.NewItems);
|
||||||
|
CollectionChanged?.Invoke(this, e);
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
_store.RemoveAll(r => e.OldItems.Contains(r));
|
||||||
|
CollectionChanged?.Invoke(this, e);
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Move:
|
||||||
|
case NotifyCollectionChangedAction.Replace:
|
||||||
|
case NotifyCollectionChangedAction.Reset:
|
||||||
|
Refresh();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _store.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => _backing.Count;
|
||||||
|
|
||||||
|
private void InsertSorted(IEnumerable items)
|
||||||
|
{
|
||||||
|
foreach (var t in items)
|
||||||
|
InsertSorted((T)t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int InsertSorted(T item, IComparer<T> comparer = null)
|
||||||
|
{
|
||||||
|
if (comparer == null)
|
||||||
|
comparer = _comparer;
|
||||||
|
|
||||||
|
if (_store.Count == 0 || comparer == null)
|
||||||
|
{
|
||||||
|
_store.Add(item);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(comparer.Compare(_store[_store.Count - 1], item) <= 0)
|
||||||
|
{
|
||||||
|
_store.Add(item);
|
||||||
|
return _store.Count - 1;
|
||||||
|
}
|
||||||
|
if(comparer.Compare(_store[0], item) >= 0)
|
||||||
|
{
|
||||||
|
_store.Insert(0, item);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int index = _store.BinarySearch(item);
|
||||||
|
if (index < 0)
|
||||||
|
index = ~index;
|
||||||
|
_store.Insert(index, item);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sort(IComparer<T> comparer = null)
|
||||||
|
{
|
||||||
|
if (comparer == null)
|
||||||
|
comparer = _comparer;
|
||||||
|
|
||||||
|
if (comparer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_store.Sort(comparer);
|
||||||
|
|
||||||
|
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
_store.Clear();
|
||||||
|
//_store.AddRange(_backing);
|
||||||
|
_store.EnsureCapacity(_backing.Count);
|
||||||
|
foreach (var e in _backing)
|
||||||
|
_store.Add(e);
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetComparer(IComparer<T> comparer, bool resort = true)
|
||||||
|
{
|
||||||
|
_comparer = comparer;
|
||||||
|
if(resort)
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
Torch/Collections/SystemSortedView.cs
Normal file
132
Torch/Collections/SystemSortedView.cs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Collections
|
||||||
|
{
|
||||||
|
public class SystemSortedView<T> : IReadOnlyCollection<T>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private readonly ObservableCollection<T> _backing;
|
||||||
|
private IComparer<T> _comparer;
|
||||||
|
private readonly List<T> _store;
|
||||||
|
|
||||||
|
public SystemSortedView(ObservableCollection<T> backing, IComparer<T> comparer)
|
||||||
|
{
|
||||||
|
_comparer = comparer;
|
||||||
|
_backing = backing;
|
||||||
|
_store = new List<T>(_backing.Count);
|
||||||
|
_store.AddRange(_backing);
|
||||||
|
_backing.CollectionChanged += backing_CollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void backing_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case NotifyCollectionChangedAction.Add:
|
||||||
|
InsertSorted(e.NewItems);
|
||||||
|
CollectionChanged?.Invoke(this, e);
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
_store.RemoveAll(r => e.OldItems.Contains(r));
|
||||||
|
CollectionChanged?.Invoke(this, e);
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Move:
|
||||||
|
case NotifyCollectionChangedAction.Replace:
|
||||||
|
case NotifyCollectionChangedAction.Reset:
|
||||||
|
Refresh();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _store.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => _backing.Count;
|
||||||
|
|
||||||
|
private void InsertSorted(IEnumerable items)
|
||||||
|
{
|
||||||
|
foreach (var t in items)
|
||||||
|
InsertSorted((T)t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int InsertSorted(T item, IComparer<T> comparer = null)
|
||||||
|
{
|
||||||
|
if (comparer == null)
|
||||||
|
comparer = _comparer;
|
||||||
|
|
||||||
|
if (_store.Count == 0 || comparer == null)
|
||||||
|
{
|
||||||
|
_store.Add(item);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (comparer.Compare(_store[_store.Count - 1], item) <= 0)
|
||||||
|
{
|
||||||
|
_store.Add(item);
|
||||||
|
return _store.Count - 1;
|
||||||
|
}
|
||||||
|
if (comparer.Compare(_store[0], item) >= 0)
|
||||||
|
{
|
||||||
|
_store.Insert(0, item);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int index = _store.BinarySearch(item);
|
||||||
|
if (index < 0)
|
||||||
|
index = ~index;
|
||||||
|
_store.Insert(index, item);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sort(IComparer<T> comparer = null)
|
||||||
|
{
|
||||||
|
if (comparer == null)
|
||||||
|
comparer = _comparer;
|
||||||
|
|
||||||
|
if (comparer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_store.Sort(comparer);
|
||||||
|
|
||||||
|
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
_store.Clear();
|
||||||
|
_store.AddRange(_backing);
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetComparer(IComparer<T> comparer, bool resort = true)
|
||||||
|
{
|
||||||
|
_comparer = comparer;
|
||||||
|
if (resort)
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
namespace Torch
|
namespace Torch
|
||||||
{
|
{
|
||||||
@@ -12,6 +13,7 @@ namespace Torch
|
|||||||
{
|
{
|
||||||
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>();
|
||||||
|
private readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
protected CommandLine(string argPrefix = "-")
|
protected CommandLine(string argPrefix = "-")
|
||||||
{
|
{
|
||||||
@@ -89,6 +91,24 @@ namespace Torch
|
|||||||
|
|
||||||
if (property.Value.PropertyType == typeof(string))
|
if (property.Value.PropertyType == typeof(string))
|
||||||
property.Value.SetValue(this, args[++i]);
|
property.Value.SetValue(this, args[++i]);
|
||||||
|
|
||||||
|
if (property.Value.PropertyType == typeof(List<Guid>))
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
var l = new List<Guid>(16);
|
||||||
|
while (i < args.Length && !args[i].StartsWith(_argPrefix))
|
||||||
|
{
|
||||||
|
if (Guid.TryParse(args[i], out Guid g))
|
||||||
|
{
|
||||||
|
l.Add(g);
|
||||||
|
_log.Info($"added plugin {g}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_log.Warn($"Failed to parse GUID {args[i]}");
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
property.Value.SetValue(this, l);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
@@ -55,10 +55,17 @@ namespace Torch.Commands
|
|||||||
Args = args ?? new List<string>();
|
Args = args ?? new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Respond(string message, string sender = "Server", string font = MyFontEnum.Blue)
|
public virtual void Respond(string message, string sender = null, string font = null)
|
||||||
{
|
{
|
||||||
Torch.CurrentSession.Managers.GetManager<IChatManagerServer>()
|
//hack: Backwards compatibility 20190416
|
||||||
?.SendMessageAsOther(sender, message, font, _steamIdSender);
|
if (sender == "Server")
|
||||||
|
{
|
||||||
|
sender = null;
|
||||||
|
font = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chat = Torch.CurrentSession.Managers.GetManager<IChatManagerServer>();
|
||||||
|
chat?.SendMessageAsOther(sender, message, font, _steamIdSender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -81,22 +81,22 @@ namespace Torch.Commands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HandleCommandFromServer(string message)
|
public List<TorchChatMessage> HandleCommandFromServer(string message)
|
||||||
{
|
{
|
||||||
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)
|
if (command == null)
|
||||||
return false;
|
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();
|
||||||
_log.Trace($"Invoking {cmdPath} for server.");
|
_log.Trace($"Invoking {cmdPath} for server.");
|
||||||
var context = new CommandContext(Torch, command.Plugin, Sync.MyId, argText, splitArgs);
|
var context = new ConsoleCommandContext(Torch, command.Plugin, Sync.MyId, argText, splitArgs);
|
||||||
if (command.TryInvoke(context))
|
if (command.TryInvoke(context))
|
||||||
_log.Info($"Server ran command '{message}'");
|
_log.Info($"Server ran command '{message}'");
|
||||||
else
|
else
|
||||||
context.Respond($"Invalid Syntax: {command.SyntaxHelp}");
|
context.Respond($"Invalid Syntax: {command.SyntaxHelp}");
|
||||||
return true;
|
return context.Responses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleCommand(TorchChatMessage msg, ref bool consumed)
|
public void HandleCommand(TorchChatMessage msg, ref bool consumed)
|
||||||
@@ -130,7 +130,7 @@ namespace Torch.Commands
|
|||||||
if (!HasPermission(steamId, command))
|
if (!HasPermission(steamId, command))
|
||||||
{
|
{
|
||||||
_log.Info($"{player.DisplayName} tried to use command {cmdPath} without permission");
|
_log.Info($"{player.DisplayName} tried to use command {cmdPath} without permission");
|
||||||
_chatManager.SendMessageAsOther("Server", $"You need to be a {command.MinimumPromoteLevel} or higher to use that command.", MyFontEnum.Red, steamId);
|
_chatManager.SendMessageAsOther(Torch.Config.ChatName, $"You need to be a {command.MinimumPromoteLevel} or higher to use that command.", Torch.Config.ChatColor, steamId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
Torch/Commands/ConsoleCommandContext.cs
Normal file
30
Torch/Commands/ConsoleCommandContext.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.API.Plugins;
|
||||||
|
|
||||||
|
namespace Torch.Commands
|
||||||
|
{
|
||||||
|
public class ConsoleCommandContext : CommandContext
|
||||||
|
{
|
||||||
|
public List<TorchChatMessage> Responses = new List<TorchChatMessage>();
|
||||||
|
private bool _flag;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ConsoleCommandContext(ITorchBase torch, ITorchPlugin plugin, ulong steamIdSender, string rawArgs = null, List<string> args = null)
|
||||||
|
: base(torch, plugin, steamIdSender, rawArgs, args) { }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Respond(string message, string sender = null, string font = null)
|
||||||
|
{
|
||||||
|
if (sender == "Server")
|
||||||
|
{
|
||||||
|
sender = null;
|
||||||
|
font = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Responses.Add(new TorchChatMessage(sender ?? TorchBase.Instance.Config.ChatName, message, font ?? TorchBase.Instance.Config.ChatColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -8,6 +8,7 @@ using System.Text;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
|
using NLog;
|
||||||
using Sandbox.Game.Multiplayer;
|
using Sandbox.Game.Multiplayer;
|
||||||
using Sandbox.ModAPI;
|
using Sandbox.ModAPI;
|
||||||
using Steamworks;
|
using Steamworks;
|
||||||
@@ -28,6 +29,9 @@ namespace Torch.Commands
|
|||||||
{
|
{
|
||||||
private static bool _restartPending = false;
|
private static bool _restartPending = false;
|
||||||
private static bool _cancelRestart = false;
|
private static bool _cancelRestart = false;
|
||||||
|
private bool _stopPending = false;
|
||||||
|
private bool _cancelStop = false;
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
[Command("whatsmyip")]
|
[Command("whatsmyip")]
|
||||||
[Permission(MyPromoteLevel.None)]
|
[Permission(MyPromoteLevel.None)]
|
||||||
@@ -35,9 +39,9 @@ namespace Torch.Commands
|
|||||||
{
|
{
|
||||||
if (steamId == 0)
|
if (steamId == 0)
|
||||||
steamId = Context.Player.SteamUserId;
|
steamId = Context.Player.SteamUserId;
|
||||||
|
|
||||||
VRage.GameServices.MyP2PSessionState statehack = new VRage.GameServices.MyP2PSessionState();
|
VRage.GameServices.MyP2PSessionState statehack = new VRage.GameServices.MyP2PSessionState();
|
||||||
VRage.Steam.MySteamService.Static.Peer2Peer.GetSessionState(steamId, ref statehack);
|
MySteamServiceWrapper.Static.Peer2Peer.GetSessionState(steamId, ref statehack);
|
||||||
var ip = new IPAddress(BitConverter.GetBytes(statehack.RemoteIP).Reverse().ToArray());
|
var ip = new IPAddress(BitConverter.GetBytes(statehack.RemoteIP).Reverse().ToArray());
|
||||||
Context.Respond($"Your IP is {ip}");
|
Context.Respond($"Your IP is {ip}");
|
||||||
}
|
}
|
||||||
@@ -57,10 +61,16 @@ namespace Torch.Commands
|
|||||||
if (node != null)
|
if (node != null)
|
||||||
{
|
{
|
||||||
var command = node.Command;
|
var command = node.Command;
|
||||||
var children = node.Subcommands.Select(x => x.Key);
|
var children = node.Subcommands.Where(e => Context.Player == null || e.Value.Command?.MinimumPromoteLevel <= Context.Player.PromoteLevel).Select(x => x.Key);
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
if (Context.Player != null && command?.MinimumPromoteLevel > Context.Player.PromoteLevel)
|
||||||
|
{
|
||||||
|
Context.Respond("You are not authorized to use this command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (command != null)
|
if (command != null)
|
||||||
{
|
{
|
||||||
sb.AppendLine($"Syntax: {command.SyntaxHelp}");
|
sb.AppendLine($"Syntax: {command.SyntaxHelp}");
|
||||||
@@ -94,11 +104,11 @@ namespace Torch.Commands
|
|||||||
if (node != null)
|
if (node != null)
|
||||||
{
|
{
|
||||||
var command = node.Command;
|
var command = node.Command;
|
||||||
var children = node.Subcommands.Select(x => x.Key);
|
var children = node.Subcommands.Where(e => e.Value.Command?.MinimumPromoteLevel <= Context.Player.PromoteLevel).Select(x => x.Key);
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
if (command != null)
|
if (command != null && (Context.Player == null || command.MinimumPromoteLevel <= Context.Player.PromoteLevel))
|
||||||
{
|
{
|
||||||
sb.AppendLine($"Syntax: {command.SyntaxHelp}");
|
sb.AppendLine($"Syntax: {command.SyntaxHelp}");
|
||||||
sb.Append(command.HelpText);
|
sb.Append(command.HelpText);
|
||||||
@@ -114,7 +124,7 @@ namespace Torch.Commands
|
|||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
foreach (var command in commandManager.Commands.WalkTree())
|
foreach (var command in commandManager.Commands.WalkTree())
|
||||||
{
|
{
|
||||||
if (command.IsCommand)
|
if (command.IsCommand && (Context.Player == null || command.Command.MinimumPromoteLevel <= Context.Player.PromoteLevel))
|
||||||
sb.AppendLine($"{command.Command.SyntaxHelp}\n {command.Command.HelpText}");
|
sb.AppendLine($"{command.Command.SyntaxHelp}\n {command.Command.HelpText}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,9 +156,25 @@ namespace Torch.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Command("stop", "Stops the server.")]
|
[Command("stop", "Stops the server.")]
|
||||||
public void Stop(bool save = true)
|
public void Stop(bool save = true, int countdownSeconds = 0)
|
||||||
{
|
{
|
||||||
Context.Respond("Stopping server.");
|
if (_stopPending)
|
||||||
|
{
|
||||||
|
Context.Respond("A stop is already pending.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_stopPending = true;
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
var countdown = StopCountdown(countdownSeconds, save).GetEnumerator();
|
||||||
|
while (countdown.MoveNext())
|
||||||
|
{
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*Context.Respond("Stopping server.");
|
||||||
if (save)
|
if (save)
|
||||||
DoSave()?.ContinueWith((a, mod) =>
|
DoSave()?.ContinueWith((a, mod) =>
|
||||||
{
|
{
|
||||||
@@ -157,7 +183,7 @@ namespace Torch.Commands
|
|||||||
torch.Stop();
|
torch.Stop();
|
||||||
}, this, TaskContinuationOptions.RunContinuationsAsynchronously);
|
}, this, TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||||
else
|
else
|
||||||
Context.Torch.Stop();
|
Context.Torch.Stop();*/
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("restart", "Restarts the server.")]
|
[Command("restart", "Restarts the server.")]
|
||||||
@@ -170,6 +196,7 @@ namespace Torch.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
_restartPending = true;
|
_restartPending = true;
|
||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
var countdown = RestartCountdown(countdownSeconds, save).GetEnumerator();
|
var countdown = RestartCountdown(countdownSeconds, save).GetEnumerator();
|
||||||
@@ -195,6 +222,68 @@ namespace Torch.Commands
|
|||||||
else
|
else
|
||||||
Context.Respond("A restart is not pending.");
|
Context.Respond("A restart is not pending.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command("stop cancel", "Cancel a pending stop.")]
|
||||||
|
public void CancelStop()
|
||||||
|
{
|
||||||
|
if (_restartPending)
|
||||||
|
_cancelStop = true;
|
||||||
|
else
|
||||||
|
Context.Respond("Server Stop is not pending.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable StopCountdown(int countdown, bool save)
|
||||||
|
{
|
||||||
|
for (var i = countdown; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (_cancelStop)
|
||||||
|
{
|
||||||
|
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
|
||||||
|
.SendMessageAsSelf($"Stop cancelled.");
|
||||||
|
|
||||||
|
_stopPending = false;
|
||||||
|
_cancelStop = false;
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= 60 && i % 60 == 0)
|
||||||
|
{
|
||||||
|
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
|
||||||
|
.SendMessageAsSelf($"Stopping server in {i / 60} minute{Pluralize(i / 60)}.");
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
else if (i > 0)
|
||||||
|
{
|
||||||
|
if (i < 11)
|
||||||
|
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
|
||||||
|
.SendMessageAsSelf($"Stopping server in {i} second{Pluralize(i)}.");
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (save)
|
||||||
|
{
|
||||||
|
Log.Info("Saving game before stop.");
|
||||||
|
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
|
||||||
|
.SendMessageAsSelf($"Saving game before stop.");
|
||||||
|
DoSave()?.ContinueWith((a, mod) =>
|
||||||
|
{
|
||||||
|
ITorchBase torch = (mod as CommandModule)?.Context?.Torch;
|
||||||
|
Debug.Assert(torch != null);
|
||||||
|
torch.Stop();
|
||||||
|
}, this, TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Info("Stopping server.");
|
||||||
|
Context.Torch.Invoke(() => Context.Torch.Stop());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable RestartCountdown(int countdown, bool save)
|
private IEnumerable RestartCountdown(int countdown, bool save)
|
||||||
{
|
{
|
||||||
@@ -226,15 +315,18 @@ namespace Torch.Commands
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (save)
|
if (save)
|
||||||
Context.Torch.Save().ContinueWith(x => Restart());
|
{
|
||||||
else
|
Log.Info("Savin game before restart.");
|
||||||
Restart();
|
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>()
|
||||||
|
.SendMessageAsSelf($"Saving game before restart.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info("Restarting server.");
|
||||||
|
Context.Torch.Invoke(() => Context.Torch.Restart(save));
|
||||||
|
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Restart() => Context.Torch.Invoke(() => Context.Torch.Restart());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string Pluralize(int num)
|
private string Pluralize(int num)
|
||||||
@@ -255,7 +347,7 @@ namespace Torch.Commands
|
|||||||
|
|
||||||
private Task DoSave()
|
private Task DoSave()
|
||||||
{
|
{
|
||||||
Task<GameSaveResult> task = Context.Torch.Save(60 * 1000, true);
|
Task<GameSaveResult> task = Context.Torch.Save(300 * 1000, true);
|
||||||
if (task == null)
|
if (task == null)
|
||||||
{
|
{
|
||||||
Context.Respond("Save failed, a save is already in progress");
|
Context.Respond("Save failed, a save is already in progress");
|
||||||
@@ -290,5 +382,11 @@ namespace Torch.Commands
|
|||||||
}
|
}
|
||||||
}, this, TaskContinuationOptions.RunContinuationsAsynchronously);
|
}, this, TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command("uptime", "Check how long the server has been online.")]
|
||||||
|
public void Uptime()
|
||||||
|
{
|
||||||
|
Context.Respond(((ITorchServer)Context.Torch).ElapsedPlayTime.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
Torch/Extensions/LinqExtensions.cs
Normal file
41
Torch/Extensions/LinqExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Torch
|
||||||
|
{
|
||||||
|
public static class LinqExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<T> TSort<T>( this IEnumerable<T> source, Func<T, IEnumerable<T>> dependencies, bool throwOnCycle = false )
|
||||||
|
{
|
||||||
|
var sorted = new List<T>();
|
||||||
|
var visited = new HashSet<T>();
|
||||||
|
|
||||||
|
foreach( var item in source )
|
||||||
|
Visit( item, visited, sorted, dependencies, throwOnCycle );
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Visit<T>( T item, HashSet<T> visited, List<T> sorted, Func<T, IEnumerable<T>> dependencies, bool throwOnCycle )
|
||||||
|
{
|
||||||
|
if( !visited.Contains( item ) )
|
||||||
|
{
|
||||||
|
visited.Add( item );
|
||||||
|
|
||||||
|
var resolvedDependencies = dependencies(item);
|
||||||
|
if (resolvedDependencies != null)
|
||||||
|
{
|
||||||
|
foreach (var dep in resolvedDependencies)
|
||||||
|
Visit(dep, visited, sorted, dependencies, throwOnCycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
sorted.Add( item );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( throwOnCycle && !sorted.Contains( item ) )
|
||||||
|
throw new Exception( "Cyclic dependency found" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -15,6 +15,7 @@ using Torch.API;
|
|||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
|
using VRageMath;
|
||||||
|
|
||||||
namespace Torch.Managers.ChatManager
|
namespace Torch.Managers.ChatManager
|
||||||
{
|
{
|
||||||
@@ -38,17 +39,26 @@ namespace Torch.Managers.ChatManager
|
|||||||
{
|
{
|
||||||
if (Sandbox.Engine.Platform.Game.IsDedicated)
|
if (Sandbox.Engine.Platform.Game.IsDedicated)
|
||||||
{
|
{
|
||||||
|
// Sending invalid color to clients will crash them. KEEEN
|
||||||
|
var color = Torch.Config.ChatColor;
|
||||||
|
if (!StringUtils.IsFontEnum(Torch.Config.ChatColor))
|
||||||
|
{
|
||||||
|
_log.Warn("Invalid chat font color! Defaulting to 'Red'");
|
||||||
|
color = MyFontEnum.Red;
|
||||||
|
}
|
||||||
|
|
||||||
var scripted = new ScriptedChatMsg()
|
var scripted = new ScriptedChatMsg()
|
||||||
{
|
{
|
||||||
Author = "Server",
|
Author = Torch.Config.ChatName,
|
||||||
Font = MyFontEnum.Red,
|
Font = color,
|
||||||
Text = message,
|
Text = message,
|
||||||
Target = 0
|
Target = 0
|
||||||
};
|
};
|
||||||
MyMultiplayerBase.SendScriptedChatMessage(ref scripted);
|
MyMultiplayerBase.SendScriptedChatMessage(ref scripted);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
MyMultiplayer.Static.SendChatMessage(message);
|
throw new NotImplementedException("Chat system changes broke this");
|
||||||
|
//MyMultiplayer.Static.SendChatMessage(message);
|
||||||
}
|
}
|
||||||
else if (HasHud)
|
else if (HasHud)
|
||||||
MyHud.Chat.ShowMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", message);
|
MyHud.Chat.ShowMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", message);
|
||||||
@@ -59,12 +69,6 @@ namespace Torch.Managers.ChatManager
|
|||||||
{
|
{
|
||||||
if (HasHud)
|
if (HasHud)
|
||||||
MyHud.Chat?.ShowMessage(author, message, font);
|
MyHud.Chat?.ShowMessage(author, message, font);
|
||||||
MySession.Static.GlobalChatHistory.GlobalChatHistory.Chat.Enqueue(new MyGlobalChatItem()
|
|
||||||
{
|
|
||||||
Author = author,
|
|
||||||
AuthorFont = font,
|
|
||||||
Text = message
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -76,10 +80,10 @@ namespace Torch.Managers.ChatManager
|
|||||||
{
|
{
|
||||||
_chatMessageRecievedReplacer = _chatMessageReceivedFactory.Invoke();
|
_chatMessageRecievedReplacer = _chatMessageReceivedFactory.Invoke();
|
||||||
_scriptedChatMessageRecievedReplacer = _scriptedChatMessageReceivedFactory.Invoke();
|
_scriptedChatMessageRecievedReplacer = _scriptedChatMessageReceivedFactory.Invoke();
|
||||||
_chatMessageRecievedReplacer.Replace(new Action<ulong, string>(Multiplayer_ChatMessageReceived),
|
_chatMessageRecievedReplacer.Replace(new Action<ulong, string, ChatChannel, long, string>(Multiplayer_ChatMessageReceived),
|
||||||
MyMultiplayer.Static);
|
MyMultiplayer.Static);
|
||||||
_scriptedChatMessageRecievedReplacer.Replace(
|
_scriptedChatMessageRecievedReplacer.Replace(
|
||||||
new Action<string, string, string>(Multiplayer_ScriptedChatMessageReceived), MyMultiplayer.Static);
|
new Action<string, string, string, Color>(Multiplayer_ScriptedChatMessageReceived), MyMultiplayer.Static);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -113,7 +117,7 @@ namespace Torch.Managers.ChatManager
|
|||||||
{
|
{
|
||||||
if (!sendToOthers)
|
if (!sendToOthers)
|
||||||
return;
|
return;
|
||||||
var torchMsg = new TorchChatMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", Sync.MyId, messageText);
|
var torchMsg = new TorchChatMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", Sync.MyId, messageText, ChatChannel.Global, 0);
|
||||||
bool consumed = RaiseMessageRecieved(torchMsg);
|
bool consumed = RaiseMessageRecieved(torchMsg);
|
||||||
if (!consumed)
|
if (!consumed)
|
||||||
consumed = OfflineMessageProcessor(torchMsg);
|
consumed = OfflineMessageProcessor(torchMsg);
|
||||||
@@ -130,19 +134,19 @@ namespace Torch.Managers.ChatManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void Multiplayer_ChatMessageReceived(ulong steamUserId, string message)
|
private void Multiplayer_ChatMessageReceived(ulong steamUserId, string messageText, ChatChannel channel, long targetId, string customAuthorName)
|
||||||
{
|
{
|
||||||
var torchMsg = new TorchChatMessage(steamUserId, message,
|
var torchMsg = new TorchChatMessage(steamUserId, messageText, channel, targetId,
|
||||||
(steamUserId == MyGameService.UserId) ? MyFontEnum.DarkBlue : MyFontEnum.Blue);
|
(steamUserId == MyGameService.UserId) ? MyFontEnum.DarkBlue : MyFontEnum.Blue);
|
||||||
if (!RaiseMessageRecieved(torchMsg) && HasHud)
|
if (!RaiseMessageRecieved(torchMsg) && HasHud)
|
||||||
_hudChatMessageReceived.Invoke(MyHud.Chat, steamUserId, message);
|
_hudChatMessageReceived.Invoke(MyHud.Chat, steamUserId, messageText, channel, targetId, customAuthorName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Multiplayer_ScriptedChatMessageReceived(string message, string author, string font)
|
private void Multiplayer_ScriptedChatMessageReceived(string message, string author, string font, Color color)
|
||||||
{
|
{
|
||||||
var torchMsg = new TorchChatMessage(author, message, font);
|
var torchMsg = new TorchChatMessage(author, message, font);
|
||||||
if (!RaiseMessageRecieved(torchMsg) && HasHud)
|
if (!RaiseMessageRecieved(torchMsg) && HasHud)
|
||||||
_hudChatScriptedMessageReceived.Invoke(MyHud.Chat, author, message, font);
|
_hudChatScriptedMessageReceived.Invoke(MyHud.Chat, author, message, font, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool RaiseMessageRecieved(TorchChatMessage msg)
|
protected bool RaiseMessageRecieved(TorchChatMessage msg)
|
||||||
@@ -158,9 +162,9 @@ namespace Torch.Managers.ChatManager
|
|||||||
protected static bool HasHud => !Sandbox.Engine.Platform.Game.IsDedicated;
|
protected static bool HasHud => !Sandbox.Engine.Platform.Game.IsDedicated;
|
||||||
|
|
||||||
[ReflectedMethod(Name = _hudChatMessageReceivedName)]
|
[ReflectedMethod(Name = _hudChatMessageReceivedName)]
|
||||||
private static Action<MyHudChat, ulong, string> _hudChatMessageReceived;
|
private static Action<MyHudChat, ulong, string, ChatChannel, long, string> _hudChatMessageReceived;
|
||||||
[ReflectedMethod(Name = _hudChatScriptedMessageReceivedName)]
|
[ReflectedMethod(Name = _hudChatScriptedMessageReceivedName)]
|
||||||
private static Action<MyHudChat, string, string, string> _hudChatScriptedMessageReceived;
|
private static Action<MyHudChat, string, string, string, Color> _hudChatScriptedMessageReceived;
|
||||||
|
|
||||||
[ReflectedEventReplace(typeof(MyMultiplayerBase), nameof(MyMultiplayerBase.ChatMessageReceived), typeof(MyHudChat), _hudChatMessageReceivedName)]
|
[ReflectedEventReplace(typeof(MyMultiplayerBase), nameof(MyMultiplayerBase.ChatMessageReceived), typeof(MyHudChat), _hudChatMessageReceivedName)]
|
||||||
private static Func<ReflectedEventReplacer> _chatMessageReceivedFactory;
|
private static Func<ReflectedEventReplacer> _chatMessageReceivedFactory;
|
||||||
|
@@ -13,38 +13,72 @@ using Sandbox.Game.Multiplayer;
|
|||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers.PatchManager;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
using VRage;
|
using VRage;
|
||||||
|
using VRage.Collections;
|
||||||
using VRage.Library.Collections;
|
using VRage.Library.Collections;
|
||||||
using VRage.Network;
|
using VRage.Network;
|
||||||
|
|
||||||
namespace Torch.Managers.ChatManager
|
namespace Torch.Managers.ChatManager
|
||||||
{
|
{
|
||||||
|
[PatchShim]
|
||||||
|
internal static class ChatInterceptPatch
|
||||||
|
{
|
||||||
|
private static ChatManagerServer _chatManager;
|
||||||
|
private static ChatManagerServer ChatManager => _chatManager ?? (_chatManager = TorchBase.Instance.CurrentSession.Managers.GetManager<ChatManagerServer>());
|
||||||
|
|
||||||
|
internal static void Patch(PatchContext context)
|
||||||
|
{
|
||||||
|
var target = typeof(MyMultiplayerBase).GetMethod("OnChatMessageReceived_Server", BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
var patchMethod = typeof(ChatInterceptPatch).GetMethod(nameof(PrefixMessageProcessing), BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
context.GetPattern(target).Prefixes.Add(patchMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool PrefixMessageProcessing(ref ChatMsg msg)
|
||||||
|
{
|
||||||
|
var consumed = false;
|
||||||
|
ChatManager.RaiseMessageRecieved(msg, ref consumed);
|
||||||
|
return !consumed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class ChatManagerServer : ChatManagerClient, IChatManagerServer
|
public class ChatManagerServer : ChatManagerClient, IChatManagerServer
|
||||||
{
|
{
|
||||||
[Dependency(Optional = true)]
|
|
||||||
private INetworkManager _networkManager;
|
|
||||||
|
|
||||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
private static readonly Logger _chatLog = LogManager.GetLogger("Chat");
|
private static readonly Logger _chatLog = LogManager.GetLogger("Chat");
|
||||||
|
|
||||||
private readonly ChatIntercept _chatIntercept;
|
private readonly HashSet<ulong> _muted = new HashSet<ulong>();
|
||||||
|
/// <inheritdoc />
|
||||||
|
public HashSetReader<ulong> MutedUsers => _muted;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ChatManagerServer(ITorchBase torchInstance) : base(torchInstance)
|
public ChatManagerServer(ITorchBase torchInstance) : base(torchInstance)
|
||||||
{
|
{
|
||||||
_chatIntercept = new ChatIntercept(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event MessageProcessingDel MessageProcessing;
|
public event MessageProcessingDel MessageProcessing;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool MuteUser(ulong steamId)
|
||||||
|
{
|
||||||
|
return _muted.Add(steamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool UnmuteUser(ulong steamId)
|
||||||
|
{
|
||||||
|
return _muted.Remove(steamId);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0)
|
public void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0)
|
||||||
{
|
{
|
||||||
if (targetSteamId == Sync.MyId)
|
if (targetSteamId == Sync.MyId)
|
||||||
{
|
{
|
||||||
RaiseMessageRecieved(new TorchChatMessage(authorId, message));
|
RaiseMessageRecieved(new TorchChatMessage(authorId, message, ChatChannel.Global, 0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (MyMultiplayer.Static == null)
|
if (MyMultiplayer.Static == null)
|
||||||
@@ -89,44 +123,15 @@ namespace Torch.Managers.ChatManager
|
|||||||
}
|
}
|
||||||
var scripted = new ScriptedChatMsg()
|
var scripted = new ScriptedChatMsg()
|
||||||
{
|
{
|
||||||
Author = author,
|
Author = author ?? Torch.Config.ChatName,
|
||||||
Text = message,
|
Text = message,
|
||||||
Font = font,
|
Font = font ?? Torch.Config.ChatColor,
|
||||||
Target = Sync.Players.TryGetIdentityId(targetSteamId)
|
Target = Sync.Players.TryGetIdentityId(targetSteamId)
|
||||||
};
|
};
|
||||||
_chatLog.Info($"{author} (to {GetMemberName(targetSteamId)}): {message}");
|
_chatLog.Info($"{author} (to {GetMemberName(targetSteamId)}): {message}");
|
||||||
MyMultiplayerBase.SendScriptedChatMessage(ref scripted);
|
MyMultiplayerBase.SendScriptedChatMessage(ref scripted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Attach()
|
|
||||||
{
|
|
||||||
base.Attach();
|
|
||||||
if (_networkManager != null)
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_networkManager.RegisterNetworkHandler(_chatIntercept);
|
|
||||||
_log.Debug("Initialized network intercept for chat messages");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Discard exception and use second method
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MyMultiplayer.Static != null)
|
|
||||||
{
|
|
||||||
MyMultiplayer.Static.ChatMessageReceived += MpStaticChatMessageReceived;
|
|
||||||
_log.Warn(
|
|
||||||
"Failed to initialize network intercept, we can't discard chat messages! Falling back to another method.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_log.Debug("Using offline message processor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override bool OfflineMessageProcessor(TorchChatMessage msg)
|
protected override bool OfflineMessageProcessor(TorchChatMessage msg)
|
||||||
{
|
{
|
||||||
@@ -137,84 +142,25 @@ namespace Torch.Managers.ChatManager
|
|||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MpStaticChatMessageReceived(ulong a, string b)
|
|
||||||
{
|
|
||||||
var tmp = false;
|
|
||||||
RaiseMessageRecieved(new ChatMsg()
|
|
||||||
{
|
|
||||||
Author = a,
|
|
||||||
Text = b
|
|
||||||
}, ref tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Detach()
|
|
||||||
{
|
|
||||||
if (MyMultiplayer.Static != null)
|
|
||||||
MyMultiplayer.Static.ChatMessageReceived -= MpStaticChatMessageReceived;
|
|
||||||
_networkManager?.UnregisterNetworkHandler(_chatIntercept);
|
|
||||||
base.Detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void RaiseMessageRecieved(ChatMsg message, ref bool consumed)
|
internal void RaiseMessageRecieved(ChatMsg message, ref bool consumed)
|
||||||
{
|
{
|
||||||
var torchMsg = new TorchChatMessage(GetMemberName(message.Author), message.Author, message.Text);
|
var torchMsg = new TorchChatMessage(GetMemberName(message.Author), message.Author, message.Text, (ChatChannel)message.Channel, message.TargetId);
|
||||||
|
if (_muted.Contains(message.Author))
|
||||||
|
{
|
||||||
|
consumed = true;
|
||||||
|
_chatLog.Warn($"MUTED USER: [{torchMsg.Channel}:{torchMsg.Target}] {torchMsg.Author}: {torchMsg.Message}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MessageProcessing?.Invoke(torchMsg, ref consumed);
|
MessageProcessing?.Invoke(torchMsg, ref consumed);
|
||||||
|
|
||||||
if (!consumed)
|
if (!consumed)
|
||||||
_chatLog.Info($"{torchMsg.Author}: {torchMsg.Message}");
|
_chatLog.Info($"[{torchMsg.Channel}:{torchMsg.Target}] {torchMsg.Author}: {torchMsg.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetMemberName(ulong steamId)
|
public static string GetMemberName(ulong steamId)
|
||||||
{
|
{
|
||||||
return MyMultiplayer.Static?.GetMemberName(steamId) ?? $"user_{steamId}";
|
return MyMultiplayer.Static?.GetMemberName(steamId) ?? $"user_{steamId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ChatIntercept : NetworkHandlerBase, INetworkHandler
|
|
||||||
{
|
|
||||||
private readonly ChatManagerServer _chatManager;
|
|
||||||
private bool? _unitTestResult;
|
|
||||||
|
|
||||||
public ChatIntercept(ChatManagerServer chatManager)
|
|
||||||
{
|
|
||||||
_chatManager = chatManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool CanHandle(CallSite site)
|
|
||||||
{
|
|
||||||
if (site.MethodInfo.Name != "OnChatMessageRecieved")
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (_unitTestResult.HasValue)
|
|
||||||
return _unitTestResult.Value;
|
|
||||||
|
|
||||||
ParameterInfo[] parameters = site.MethodInfo.GetParameters();
|
|
||||||
if (parameters.Length != 1)
|
|
||||||
{
|
|
||||||
_unitTestResult = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameters[0].ParameterType != typeof(ChatMsg))
|
|
||||||
_unitTestResult = false;
|
|
||||||
|
|
||||||
_unitTestResult = true;
|
|
||||||
|
|
||||||
return _unitTestResult.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet)
|
|
||||||
{
|
|
||||||
var msg = new ChatMsg();
|
|
||||||
Serialize(site.MethodInfo, stream, ref msg);
|
|
||||||
|
|
||||||
var consumed = false;
|
|
||||||
_chatManager.RaiseMessageRecieved(msg, ref consumed);
|
|
||||||
|
|
||||||
return consumed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,12 +4,14 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
|
||||||
namespace Torch.Managers
|
namespace Torch.Managers
|
||||||
{
|
{
|
||||||
public class FilesystemManager : Manager
|
public class FilesystemManager : Manager
|
||||||
{
|
{
|
||||||
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Temporary directory for Torch that is cleared every time the program is started.
|
/// Temporary directory for Torch that is cleared every time the program is started.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -22,30 +24,46 @@ namespace Torch.Managers
|
|||||||
|
|
||||||
public FilesystemManager(ITorchBase torchInstance) : base(torchInstance)
|
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;
|
var torch = new FileInfo(typeof(FilesystemManager).Assembly.Location).Directory.FullName;
|
||||||
|
TempDirectory = Directory.CreateDirectory(Path.Combine(torch, "tmp")).FullName;
|
||||||
TorchDirectory = torch;
|
TorchDirectory = torch;
|
||||||
|
|
||||||
|
_log.Debug($"Clearing tmp directory at {TempDirectory}");
|
||||||
ClearTemp();
|
ClearTemp();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearTemp()
|
private void ClearTemp()
|
||||||
{
|
{
|
||||||
foreach (var file in Directory.GetFiles(TempDirectory, "*", SearchOption.AllDirectories))
|
foreach (var file in Directory.GetFiles(TempDirectory, "*", SearchOption.AllDirectories))
|
||||||
File.Delete(file);
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
_log.Debug($"Failed to delete file {file}, it's probably in use by another process'");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Warn($"Unhandled exception when clearing temp files. You may ignore this. {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move the given file (if it exists) to a temporary directory that will be cleared the next time the application starts.
|
/// Move the given file (if it exists) to a temporary directory that will be cleared the next time the application starts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SoftDelete(string file)
|
public void SoftDelete(string path, string file)
|
||||||
{
|
{
|
||||||
if (!File.Exists(file))
|
string source = Path.Combine(path, file);
|
||||||
|
if (!File.Exists(source))
|
||||||
return;
|
return;
|
||||||
var rand = Path.GetRandomFileName();
|
var rand = Path.GetRandomFileName();
|
||||||
var dest = Path.Combine(TempDirectory, rand);
|
var dest = Path.Combine(TempDirectory, rand);
|
||||||
File.Move(file, dest);
|
File.Move(source, rand);
|
||||||
|
string rsource = Path.Combine(path, rand);
|
||||||
|
File.Move(rsource, dest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -76,7 +76,21 @@ namespace Torch.Managers
|
|||||||
|
|
||||||
private static StringBuilder PrepareLog(MyLog log)
|
private static StringBuilder PrepareLog(MyLog log)
|
||||||
{
|
{
|
||||||
return _tmpStringBuilder.Value.Clear().Append(' ', _getIndentByThread(log, _getThreadId(log)) * 3);
|
try
|
||||||
|
{
|
||||||
|
var v = _tmpStringBuilder.Value;
|
||||||
|
v.Clear();
|
||||||
|
var i = _getThreadId(log);
|
||||||
|
var t = _getIndentByThread(log, i);
|
||||||
|
v.Append(' ', t * 3);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Error(e);
|
||||||
|
return _tmpStringBuilder.Value.Clear();
|
||||||
|
}
|
||||||
|
//return _tmpStringBuilder.Value.Clear().Append(' ', _getIndentByThread(log, _getThreadId(log)) * 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PrefixWriteLine(MyLog __instance, string msg)
|
private static bool PrefixWriteLine(MyLog __instance, string msg)
|
||||||
@@ -117,7 +131,15 @@ namespace Torch.Managers
|
|||||||
|
|
||||||
private static bool PrefixLogFormatted(MyLog __instance, MyLogSeverity severity, string format, object[] args)
|
private static bool PrefixLogFormatted(MyLog __instance, MyLogSeverity severity, string format, object[] args)
|
||||||
{
|
{
|
||||||
_log.Log(LogLevelFor(severity), PrepareLog(__instance).AppendFormat(format, args));
|
// Sometimes this is called with a pre-formatted string and no args
|
||||||
|
// and causes a crash when the format string contains braces
|
||||||
|
var sb = PrepareLog(__instance);
|
||||||
|
if (args != null && args.Length > 0)
|
||||||
|
sb.AppendFormat(format, args);
|
||||||
|
else
|
||||||
|
sb.Append(format);
|
||||||
|
|
||||||
|
_log.Log(LogLevelFor(severity), sb);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -116,7 +116,7 @@ namespace Torch.Managers
|
|||||||
protected void RaiseClientJoined(ulong steamId)
|
protected void RaiseClientJoined(ulong steamId)
|
||||||
{
|
{
|
||||||
var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected };
|
var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected };
|
||||||
_log.Info($"Player {vm.Name} joined ({vm.SteamId}");
|
_log.Info($"Player {vm.Name} joined ({vm.SteamId})");
|
||||||
Players.Add(steamId, vm);
|
Players.Add(steamId, vm);
|
||||||
PlayerJoined?.Invoke(vm);
|
PlayerJoined?.Invoke(vm);
|
||||||
}
|
}
|
||||||
|
@@ -1,244 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VRage;
|
|
||||||
using VRage.Library.Collections;
|
|
||||||
using VRage.Network;
|
|
||||||
using VRage.Serialization;
|
|
||||||
|
|
||||||
namespace Torch.Managers
|
|
||||||
{
|
|
||||||
public abstract class NetworkHandlerBase
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Check the method name and do unit tests on parameters in here.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="site"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public abstract bool CanHandle(CallSite site);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Performs action on network packet. Return value of true means the packet has been handled, and will not be passed on to the game server.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="remoteUserId"></param>
|
|
||||||
/// <param name="site"></param>
|
|
||||||
/// <param name="stream"></param>
|
|
||||||
/// <param name="obj"></param>
|
|
||||||
/// <param name="packet"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public abstract bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extracts method arguments from the bitstream or packs them back in, depending on stream read mode.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T1"></typeparam>
|
|
||||||
/// <param name="info"></param>
|
|
||||||
/// <param name="stream"></param>
|
|
||||||
/// <param name="arg1"></param>
|
|
||||||
public void Serialize<T1>(MethodInfo info, BitStream stream, ref T1 arg1)
|
|
||||||
{
|
|
||||||
var s1 = MyFactory.GetSerializer<T1>();
|
|
||||||
|
|
||||||
var args = info.GetParameters();
|
|
||||||
var info1 = MySerializeInfo.CreateForParameter(args, 0);
|
|
||||||
|
|
||||||
if (stream.Reading)
|
|
||||||
{
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg1, s1, info1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MySerializationHelpers.Write(stream, ref arg1, s1, info1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Serialize<T1, T2>(MethodInfo info, BitStream stream, ref T1 arg1, ref T2 arg2)
|
|
||||||
{
|
|
||||||
var s1 = MyFactory.GetSerializer<T1>();
|
|
||||||
var s2 = MyFactory.GetSerializer<T2>();
|
|
||||||
|
|
||||||
var args = info.GetParameters();
|
|
||||||
var info1 = MySerializeInfo.CreateForParameter(args, 0);
|
|
||||||
var info2 = MySerializeInfo.CreateForParameter(args, 1);
|
|
||||||
|
|
||||||
if (stream.Reading)
|
|
||||||
{
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg1, s1, info1);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg2, s2, info2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MySerializationHelpers.Write(stream, ref arg1, s1, info1);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg2, s2, info2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Serialize<T1, T2, T3>(MethodInfo info, BitStream stream, ref T1 arg1, ref T2 arg2, ref T3 arg3)
|
|
||||||
{
|
|
||||||
var s1 = MyFactory.GetSerializer<T1>();
|
|
||||||
var s2 = MyFactory.GetSerializer<T2>();
|
|
||||||
var s3 = MyFactory.GetSerializer<T3>();
|
|
||||||
|
|
||||||
var args = info.GetParameters();
|
|
||||||
var info1 = MySerializeInfo.CreateForParameter(args, 0);
|
|
||||||
var info2 = MySerializeInfo.CreateForParameter(args, 1);
|
|
||||||
var info3 = MySerializeInfo.CreateForParameter(args, 2);
|
|
||||||
|
|
||||||
if (stream.Reading)
|
|
||||||
{
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg1, s1, info1);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg2, s2, info2);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg3, s3, info3);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MySerializationHelpers.Write(stream, ref arg1, s1, info1);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg2, s2, info2);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg3, s3, info3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Serialize<T1, T2, T3, T4>(MethodInfo info, BitStream stream, ref T1 arg1, ref T2 arg2, ref T3 arg3, ref T4 arg4)
|
|
||||||
{
|
|
||||||
var s1 = MyFactory.GetSerializer<T1>();
|
|
||||||
var s2 = MyFactory.GetSerializer<T2>();
|
|
||||||
var s3 = MyFactory.GetSerializer<T3>();
|
|
||||||
var s4 = MyFactory.GetSerializer<T4>();
|
|
||||||
|
|
||||||
var args = info.GetParameters();
|
|
||||||
var info1 = MySerializeInfo.CreateForParameter(args, 0);
|
|
||||||
var info2 = MySerializeInfo.CreateForParameter(args, 1);
|
|
||||||
var info3 = MySerializeInfo.CreateForParameter(args, 2);
|
|
||||||
var info4 = MySerializeInfo.CreateForParameter(args, 3);
|
|
||||||
|
|
||||||
if (stream.Reading)
|
|
||||||
{
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg1, s1, info1);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg2, s2, info2);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg3, s3, info3);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg4, s4, info4);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MySerializationHelpers.Write(stream, ref arg1, s1, info1);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg2, s2, info2);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg3, s3, info3);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg4, s4, info4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Serialize<T1, T2, T3, T4, T5>(MethodInfo info, BitStream stream, ref T1 arg1, ref T2 arg2, ref T3 arg3, ref T4 arg4, ref T5 arg5)
|
|
||||||
{
|
|
||||||
var s1 = MyFactory.GetSerializer<T1>();
|
|
||||||
var s2 = MyFactory.GetSerializer<T2>();
|
|
||||||
var s3 = MyFactory.GetSerializer<T3>();
|
|
||||||
var s4 = MyFactory.GetSerializer<T4>();
|
|
||||||
var s5 = MyFactory.GetSerializer<T5>();
|
|
||||||
|
|
||||||
var args = info.GetParameters();
|
|
||||||
var info1 = MySerializeInfo.CreateForParameter(args, 0);
|
|
||||||
var info2 = MySerializeInfo.CreateForParameter(args, 1);
|
|
||||||
var info3 = MySerializeInfo.CreateForParameter(args, 2);
|
|
||||||
var info4 = MySerializeInfo.CreateForParameter(args, 3);
|
|
||||||
var info5 = MySerializeInfo.CreateForParameter(args, 4);
|
|
||||||
|
|
||||||
if (stream.Reading)
|
|
||||||
{
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg1, s1, info1);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg2, s2, info2);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg3, s3, info3);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg4, s4, info4);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg5, s5, info5);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MySerializationHelpers.Write(stream, ref arg1, s1, info1);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg2, s2, info2);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg3, s3, info3);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg4, s4, info4);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg5, s5, info5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Serialize<T1, T2, T3, T4, T5, T6>(MethodInfo info, BitStream stream, ref T1 arg1, ref T2 arg2, ref T3 arg3, ref T4 arg4, ref T5 arg5, ref T6 arg6)
|
|
||||||
{
|
|
||||||
var s1 = MyFactory.GetSerializer<T1>();
|
|
||||||
var s2 = MyFactory.GetSerializer<T2>();
|
|
||||||
var s3 = MyFactory.GetSerializer<T3>();
|
|
||||||
var s4 = MyFactory.GetSerializer<T4>();
|
|
||||||
var s5 = MyFactory.GetSerializer<T5>();
|
|
||||||
var s6 = MyFactory.GetSerializer<T6>();
|
|
||||||
|
|
||||||
var args = info.GetParameters();
|
|
||||||
var info1 = MySerializeInfo.CreateForParameter(args, 0);
|
|
||||||
var info2 = MySerializeInfo.CreateForParameter(args, 1);
|
|
||||||
var info3 = MySerializeInfo.CreateForParameter(args, 2);
|
|
||||||
var info4 = MySerializeInfo.CreateForParameter(args, 3);
|
|
||||||
var info5 = MySerializeInfo.CreateForParameter(args, 4);
|
|
||||||
var info6 = MySerializeInfo.CreateForParameter(args, 5);
|
|
||||||
|
|
||||||
if (stream.Reading)
|
|
||||||
{
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg1, s1, info1);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg2, s2, info2);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg3, s3, info3);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg4, s4, info4);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg5, s5, info5);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg6, s6, info6);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MySerializationHelpers.Write(stream, ref arg1, s1, info1);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg2, s2, info2);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg3, s3, info3);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg4, s4, info4);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg5, s5, info5);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg6, s6, info6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Serialize<T1, T2, T3, T4, T5, T6, T7>(MethodInfo info, BitStream stream, ref T1 arg1, ref T2 arg2, ref T3 arg3, ref T4 arg4, ref T5 arg5, ref T6 arg6, ref T7 arg7)
|
|
||||||
{
|
|
||||||
var s1 = MyFactory.GetSerializer<T1>();
|
|
||||||
var s2 = MyFactory.GetSerializer<T2>();
|
|
||||||
var s3 = MyFactory.GetSerializer<T3>();
|
|
||||||
var s4 = MyFactory.GetSerializer<T4>();
|
|
||||||
var s5 = MyFactory.GetSerializer<T5>();
|
|
||||||
var s6 = MyFactory.GetSerializer<T6>();
|
|
||||||
var s7 = MyFactory.GetSerializer<T7>();
|
|
||||||
|
|
||||||
var args = info.GetParameters();
|
|
||||||
var info1 = MySerializeInfo.CreateForParameter(args, 0);
|
|
||||||
var info2 = MySerializeInfo.CreateForParameter(args, 1);
|
|
||||||
var info3 = MySerializeInfo.CreateForParameter(args, 2);
|
|
||||||
var info4 = MySerializeInfo.CreateForParameter(args, 3);
|
|
||||||
var info5 = MySerializeInfo.CreateForParameter(args, 4);
|
|
||||||
var info6 = MySerializeInfo.CreateForParameter(args, 5);
|
|
||||||
var info7 = MySerializeInfo.CreateForParameter(args, 6);
|
|
||||||
|
|
||||||
if ( stream.Reading )
|
|
||||||
{
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg1, s1, info1);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg2, s2, info2);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg3, s3, info3);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg4, s4, info4);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg5, s5, info5);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg6, s6, info6);
|
|
||||||
MySerializationHelpers.CreateAndRead(stream, out arg7, s7, info7);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MySerializationHelpers.Write(stream, ref arg1, s1, info1);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg2, s2, info2);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg3, s3, info3);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg4, s4, info4);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg5, s5, info5);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg6, s6, info6);
|
|
||||||
MySerializationHelpers.Write(stream, ref arg7, s7, info7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,396 +1,133 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using Sandbox.Engine.Multiplayer;
|
using Sandbox.Engine.Multiplayer;
|
||||||
using Sandbox.Game.Multiplayer;
|
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
using Torch.Utils;
|
|
||||||
using VRage;
|
|
||||||
using VRage.Library.Collections;
|
|
||||||
using VRage.Network;
|
using VRage.Network;
|
||||||
using VRageMath;
|
using VRageMath;
|
||||||
|
|
||||||
namespace Torch.Managers
|
namespace Torch.Managers
|
||||||
{
|
{
|
||||||
public class NetworkManager : Manager, INetworkManager
|
public static class NetworkManager
|
||||||
{
|
{
|
||||||
private static Logger _log = LogManager.GetCurrentClassLogger();
|
private static Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
private const string _myTransportLayerField = "TransportLayer";
|
|
||||||
private const string _transportHandlersField = "m_handlers";
|
|
||||||
private readonly HashSet<INetworkHandler> _networkHandlers = new HashSet<INetworkHandler>();
|
|
||||||
private bool _init;
|
|
||||||
|
|
||||||
private const int MAX_ARGUMENT = 6;
|
|
||||||
private const int GENERIC_PARAMETERS = 8;
|
|
||||||
private const int DISPATCH_PARAMETERS = 10;
|
|
||||||
private static readonly DBNull DbNull = DBNull.Value;
|
|
||||||
private static MethodInfo _dispatchInfo;
|
|
||||||
|
|
||||||
private static MethodInfo DispatchEventInfo => _dispatchInfo ?? (_dispatchInfo = typeof(MyReplicationLayerBase).GetMethod("DispatchEvent", BindingFlags.NonPublic | BindingFlags.Instance));
|
|
||||||
|
|
||||||
[ReflectedGetter(Name = "m_typeTable")]
|
|
||||||
private static Func<MyReplicationLayerBase, MyTypeTable> _typeTableGetter;
|
|
||||||
[ReflectedGetter(Name = "m_methodInfoLookup")]
|
|
||||||
private static Func<MyEventTable, Dictionary<MethodInfo, CallSite>> _methodInfoLookupGetter;
|
|
||||||
[ReflectedMethod(Type = typeof(MyReplicationLayer), Name = "GetObjectByNetworkId")]
|
|
||||||
private static Func<MyReplicationLayer, NetworkId, IMyNetObject> _getObjectByNetworkId;
|
|
||||||
|
|
||||||
public NetworkManager(ITorchBase torchInstance) : base(torchInstance)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ReflectionUnitTest(bool suppress = false)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var syncLayerType = typeof(MySyncLayer);
|
|
||||||
var transportLayerField = syncLayerType.GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
|
|
||||||
if (transportLayerField == null)
|
|
||||||
throw new TypeLoadException("Could not find internal type for TransportLayer");
|
|
||||||
|
|
||||||
var transportLayerType = transportLayerField.FieldType;
|
|
||||||
|
|
||||||
if (!Reflection.HasField(transportLayerType, _transportHandlersField))
|
|
||||||
throw new TypeLoadException("Could not find Handlers field");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (TypeLoadException ex)
|
|
||||||
{
|
|
||||||
_log.Error(ex);
|
|
||||||
if (suppress)
|
|
||||||
return false;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Attach()
|
|
||||||
{
|
|
||||||
if (_init)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_init = true;
|
|
||||||
|
|
||||||
if (!ReflectionUnitTest())
|
|
||||||
throw new InvalidOperationException("Reflection unit test failed.");
|
|
||||||
|
|
||||||
//don't bother with nullchecks here, it was all handled in ReflectionUnitTest
|
|
||||||
var transportType = typeof(MySyncLayer).GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance).FieldType;
|
|
||||||
var transportInstance = typeof(MySyncLayer).GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(MyMultiplayer.Static.SyncLayer);
|
|
||||||
var handlers = (IDictionary)transportType.GetField(_transportHandlersField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(transportInstance);
|
|
||||||
var handlerTypeField = handlers.GetType().GenericTypeArguments[0].GetField("messageId"); //Should be MyTransportLayer.HandlerId
|
|
||||||
object id = null;
|
|
||||||
foreach (var key in handlers.Keys)
|
|
||||||
{
|
|
||||||
if ((MyMessageId)handlerTypeField.GetValue(key) != MyMessageId.RPC)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
id = key;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (id == null)
|
|
||||||
throw new InvalidOperationException("RPC handler not found.");
|
|
||||||
|
|
||||||
//remove Keen's network listener
|
|
||||||
handlers.Remove(id);
|
|
||||||
//replace it with our own
|
|
||||||
handlers.Add(id, new Action<MyPacket>(OnEvent));
|
|
||||||
|
|
||||||
//PrintDebug();
|
|
||||||
|
|
||||||
_log.Debug("Initialized network intercept");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Detach()
|
|
||||||
{
|
|
||||||
// TODO reverse what was done in Attach
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Network Intercept
|
|
||||||
|
|
||||||
//TODO: Change this to a method patch so I don't have to try to keep up with Keen.
|
|
||||||
/// <summary>
|
|
||||||
/// This is the main body of the network intercept system. When messages come in from clients, they are processed here
|
|
||||||
/// before being passed on to the game server.
|
|
||||||
///
|
|
||||||
/// DO NOT modify this method unless you're absolutely sure of what you're doing. This can very easily destabilize the game!
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="packet"></param>
|
|
||||||
private void OnEvent(MyPacket packet)
|
|
||||||
{
|
|
||||||
if (_networkHandlers.Count == 0)
|
|
||||||
{
|
|
||||||
//pass the message back to the game server
|
|
||||||
try
|
|
||||||
{
|
|
||||||
((MyReplicationLayer)MyMultiplayer.ReplicationLayer).OnEvent(packet);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_log.Error(ex);
|
|
||||||
//crash after logging, bad things could happen if we continue on with bad data
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stream = new BitStream();
|
|
||||||
stream.ResetRead(packet.BitStream);
|
|
||||||
|
|
||||||
var networkId = stream.ReadNetworkId();
|
|
||||||
//this value is unused, but removing this line corrupts the rest of the stream
|
|
||||||
var blockedNetworkId = stream.ReadNetworkId();
|
|
||||||
var eventId = (uint)stream.ReadUInt16();
|
|
||||||
bool flag = stream.ReadBool();
|
|
||||||
Vector3D? position = new Vector3D?();
|
|
||||||
if (flag)
|
|
||||||
position = new Vector3D?(stream.ReadVector3D());
|
|
||||||
|
|
||||||
CallSite site;
|
|
||||||
object obj;
|
|
||||||
if (networkId.IsInvalid) // Static event
|
|
||||||
{
|
|
||||||
site = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).StaticEventTable.Get(eventId);
|
|
||||||
obj = null;
|
|
||||||
}
|
|
||||||
else // Instance event
|
|
||||||
{
|
|
||||||
//var sendAs = ((MyReplicationLayer)MyMultiplayer.ReplicationLayer).GetObjectByNetworkId(networkId);
|
|
||||||
var sendAs = _getObjectByNetworkId((MyReplicationLayer)MyMultiplayer.ReplicationLayer, networkId);
|
|
||||||
if (sendAs == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(sendAs.GetType());
|
|
||||||
var eventCount = typeInfo.EventTable.Count;
|
|
||||||
if (eventId < eventCount) // Directly
|
|
||||||
{
|
|
||||||
obj = sendAs;
|
|
||||||
site = typeInfo.EventTable.Get(eventId);
|
|
||||||
}
|
|
||||||
else // Through proxy
|
|
||||||
{
|
|
||||||
obj = ((IMyProxyTarget)sendAs).Target;
|
|
||||||
typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(obj.GetType());
|
|
||||||
site = typeInfo.EventTable.Get(eventId - (uint)eventCount); // Subtract max id of Proxy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//we're handling the network live in the game thread, this needs to go as fast as possible
|
|
||||||
var discard = false;
|
|
||||||
foreach (var handler in _networkHandlers)
|
|
||||||
//Parallel.ForEach(_networkHandlers, handler =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (handler.CanHandle(site))
|
|
||||||
discard |= handler.Handle(packet.Sender.Id.Value, site, stream, obj, packet);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
//ApplicationLog.Error(ex.ToString());
|
|
||||||
_log.Error(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//one of the handlers wants us to discard this packet
|
|
||||||
if (discard)
|
|
||||||
return;
|
|
||||||
|
|
||||||
//pass the message back to the game server
|
|
||||||
try
|
|
||||||
{
|
|
||||||
((MyReplicationLayer)MyMultiplayer.ReplicationLayer).OnEvent(packet);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_log.Error(ex, "Error processing network event!");
|
|
||||||
_log.Error(ex);
|
|
||||||
//crash after logging, bad things could happen if we continue on with bad data
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void RegisterNetworkHandler(INetworkHandler handler)
|
|
||||||
{
|
|
||||||
var handlerType = handler.GetType().FullName;
|
|
||||||
var toRemove = new List<INetworkHandler>();
|
|
||||||
foreach (var item in _networkHandlers)
|
|
||||||
{
|
|
||||||
if (item.GetType().FullName == handlerType)
|
|
||||||
{
|
|
||||||
//if (ExtenderOptions.IsDebugging)
|
|
||||||
_log.Error("Network handler already registered! " + handlerType);
|
|
||||||
toRemove.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var oldHandler in toRemove)
|
|
||||||
_networkHandlers.Remove(oldHandler);
|
|
||||||
|
|
||||||
_networkHandlers.Add(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool UnregisterNetworkHandler(INetworkHandler handler)
|
|
||||||
{
|
|
||||||
return _networkHandlers.Remove(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterNetworkHandlers(params INetworkHandler[] handlers)
|
|
||||||
{
|
|
||||||
foreach (var handler in handlers)
|
|
||||||
RegisterNetworkHandler(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Network Injection
|
#region Network Injection
|
||||||
|
|
||||||
|
private static Dictionary<MethodInfo, Delegate> _delegateCache = new Dictionary<MethodInfo, Delegate>();
|
||||||
|
|
||||||
/// <summary>
|
private static Func<T, TA> GetDelegate<T, TA>(MethodInfo method) where TA : class
|
||||||
/// Broadcasts an event to all connected clients
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="obj"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseEvent(MethodInfo method, object obj, params object[] args)
|
|
||||||
{
|
{
|
||||||
//default(EndpointId) tells the network to broadcast the message
|
if (!_delegateCache.TryGetValue(method, out var del))
|
||||||
RaiseEvent(method, obj, default(EndpointId), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends an event to one client by SteamId
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="obj"></param>
|
|
||||||
/// <param name="steamId"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseEvent(MethodInfo method, object obj, ulong steamId, params object[] args)
|
|
||||||
{
|
|
||||||
RaiseEvent(method, obj, new EndpointId(steamId), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends an event to one client
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="obj"></param>
|
|
||||||
/// <param name="endpoint"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseEvent(MethodInfo method, object obj, EndpointId endpoint, params object[] args)
|
|
||||||
{
|
|
||||||
if (method == null)
|
|
||||||
throw new ArgumentNullException(nameof(method), "MethodInfo cannot be null!");
|
|
||||||
|
|
||||||
if (args.Length > MAX_ARGUMENT)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(args), $"Cannot pass more than {MAX_ARGUMENT} arguments!");
|
|
||||||
|
|
||||||
var owner = obj as IMyEventOwner;
|
|
||||||
if (obj != null && owner == null)
|
|
||||||
throw new InvalidCastException("Provided event target is not of type IMyEventOwner!");
|
|
||||||
|
|
||||||
if (!method.HasAttribute<EventAttribute>())
|
|
||||||
throw new CustomAttributeFormatException("Provided event target does not have the Event attribute! Replication will not succeed!");
|
|
||||||
|
|
||||||
//array to hold arguments to pass into DispatchEvent
|
|
||||||
object[] arguments = new object[DISPATCH_PARAMETERS];
|
|
||||||
|
|
||||||
|
|
||||||
arguments[0] = obj == null ? TryGetStaticCallSite(method) : TryGetCallSite(method, obj);
|
|
||||||
arguments[1] = endpoint;
|
|
||||||
arguments[2] = owner;
|
|
||||||
|
|
||||||
//copy supplied arguments into the reflection arguments
|
|
||||||
for (var i = 0; i < args.Length; i++)
|
|
||||||
arguments[i + 3] = args[i];
|
|
||||||
|
|
||||||
//pad the array out with DBNull, skip last element
|
|
||||||
//last element should stay null (this is for blocking events -- not used?)
|
|
||||||
for (var j = args.Length + 3; j < arguments.Length - 1; j++)
|
|
||||||
arguments[j] = DbNull;
|
|
||||||
|
|
||||||
//create an array of Types so we can create a generic method
|
|
||||||
var argTypes = new Type[GENERIC_PARAMETERS];
|
|
||||||
|
|
||||||
//any null arguments (not DBNull) must be of type IMyEventOwner
|
|
||||||
for (var k = 2; k < arguments.Length; k++)
|
|
||||||
argTypes[k - 2] = arguments[k]?.GetType() ?? typeof(IMyEventOwner);
|
|
||||||
|
|
||||||
var parameters = method.GetParameters();
|
|
||||||
for (var i = 0; i < parameters.Length; i++)
|
|
||||||
{
|
{
|
||||||
if (argTypes[i + 1] != parameters[i].ParameterType)
|
del = (Func<T, TA>)(x => Delegate.CreateDelegate(typeof(TA), x, method) as TA);
|
||||||
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()))}");
|
_delegateCache[method] = del;
|
||||||
}
|
}
|
||||||
|
|
||||||
//create a generic method of DispatchEvent and invoke to inject our data into the network
|
return (Func<T, TA>)del;
|
||||||
var dispatch = DispatchEventInfo.MakeGenericMethod(argTypes);
|
|
||||||
dispatch.Invoke(MyMultiplayer.ReplicationLayer, arguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public static void RaiseEvent<T1>(T1 instance, MethodInfo method, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
/// Broadcasts a static event to all connected clients
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseStaticEvent(MethodInfo method, params object[] args)
|
|
||||||
{
|
{
|
||||||
//default(EndpointId) tells the network to broadcast the message
|
var del = GetDelegate<T1, Action>(method);
|
||||||
RaiseStaticEvent(method, default(EndpointId), args);
|
|
||||||
|
MyMultiplayer.RaiseEvent(instance, del, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public static void RaiseEvent<T1, T2>(T1 instance, MethodInfo method, T2 arg1, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
/// Sends a static event to one client by SteamId
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="steamId"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseStaticEvent(MethodInfo method, ulong steamId, params object[] args)
|
|
||||||
{
|
{
|
||||||
RaiseEvent(method, null, new EndpointId(steamId), args);
|
var del = GetDelegate<T1, Action<T2>> (method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseEvent(instance, del, arg1, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public static void RaiseEvent<T1, T2, T3>(T1 instance, MethodInfo method, T2 arg1, T3 arg2, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
/// Sends a static event to one client
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="endpoint"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseStaticEvent(MethodInfo method, EndpointId endpoint, params object[] args)
|
|
||||||
{
|
{
|
||||||
RaiseEvent(method, null, endpoint, args);
|
var del = GetDelegate<T1, Action<T2, T3>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseEvent(instance, del, arg1, arg2, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CallSite TryGetStaticCallSite(MethodInfo method)
|
public static void RaiseEvent<T1, T2, T3, T4>(T1 instance, MethodInfo method, T2 arg1, T3 arg2, T4 arg3, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
{
|
{
|
||||||
MyTypeTable typeTable = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer);
|
var del = GetDelegate<T1, Action<T2, T3, T4>>(method);
|
||||||
if (!_methodInfoLookupGetter.Invoke(typeTable.StaticEventTable).TryGetValue(method, out CallSite result))
|
|
||||||
throw new MissingMemberException("Provided event target not found!");
|
MyMultiplayer.RaiseEvent(instance, del, arg1, arg2, arg3, target);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CallSite TryGetCallSite(MethodInfo method, object arg)
|
public static void RaiseEvent<T1, T2, T3, T4, T5>(T1 instance, MethodInfo method, T2 arg1, T3 arg2, T4 arg3, T5 arg4, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
{
|
{
|
||||||
MySynchronizedTypeInfo typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(arg.GetType());
|
var del = GetDelegate<T1, Action<T2, T3, T4, T5>>(method);
|
||||||
if (!_methodInfoLookupGetter.Invoke(typeInfo.EventTable).TryGetValue(method, out CallSite result))
|
|
||||||
throw new MissingMemberException("Provided event target not found!");
|
MyMultiplayer.RaiseEvent(instance, del, arg1, arg2, arg3, arg4, target);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void RaiseEvent<T1, T2, T3, T4, T5, T6>(T1 instance, MethodInfo method, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
|
{
|
||||||
|
var del = GetDelegate<T1, Action<T2, T3, T4, T5, T6>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseEvent(instance, del, arg1, arg2, arg3, arg4, arg5, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseEvent<T1, T2, T3, T4, T5, T6, T7>(T1 instance, MethodInfo method, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
|
{
|
||||||
|
var del = GetDelegate<T1, Action<T2, T3, T4, T5, T6, T7>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseEvent(instance, del, arg1, arg2, arg3, arg4, arg5, arg6, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent(MethodInfo method, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1>(MethodInfo method, T1 arg1, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1, T2>(MethodInfo method, T1 arg1, T2 arg2, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1, T2>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, arg2, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1, T2, T3>(MethodInfo method, T1 arg1, T2 arg2, T3 arg3, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1, T2, T3>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, arg2, arg3, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1, T2, T3, T4>(MethodInfo method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1, T2, T3, T4>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, arg2, arg3, arg4, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1, T2, T3, T4, T5>(MethodInfo method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1, T2, T3, T4, T5>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, arg2, arg3, arg4, arg5, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1, T2, T3, T4, T5, T6>(MethodInfo method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1, T2, T3, T4, T5, T6>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, arg2, arg3, arg4, arg5, arg6, target, position);
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -83,7 +83,7 @@ namespace Torch.Managers.PatchManager
|
|||||||
private DynamicMethod AllocatePatchMethod()
|
private DynamicMethod AllocatePatchMethod()
|
||||||
{
|
{
|
||||||
Debug.Assert(_method.DeclaringType != null);
|
Debug.Assert(_method.DeclaringType != null);
|
||||||
var methodName = _method.Name + $"_{_patchSalt++}";
|
var methodName = "Patched_" + _method.DeclaringType.FullName + _method.Name + $"_{_patchSalt++}";
|
||||||
var returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
|
var returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
|
||||||
var parameters = _method.GetParameters();
|
var parameters = _method.GetParameters();
|
||||||
var parameterTypes = (_method.IsStatic ? Enumerable.Empty<Type>() : new[] {typeof(object)})
|
var parameterTypes = (_method.IsStatic ? Enumerable.Empty<Type>() : new[] {typeof(object)})
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
namespace Torch.Managers.PatchManager
|
namespace Torch.Managers.PatchManager
|
||||||
{
|
{
|
||||||
@@ -10,6 +11,7 @@ namespace Torch.Managers.PatchManager
|
|||||||
public sealed class PatchContext
|
public sealed class PatchContext
|
||||||
{
|
{
|
||||||
private readonly Dictionary<MethodBase, MethodRewritePattern> _rewritePatterns = new Dictionary<MethodBase, MethodRewritePattern>();
|
private readonly Dictionary<MethodBase, MethodRewritePattern> _rewritePatterns = new Dictionary<MethodBase, MethodRewritePattern>();
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
internal PatchContext()
|
internal PatchContext()
|
||||||
{
|
{
|
||||||
|
@@ -1,54 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Sandbox.ModAPI;
|
|
||||||
using Torch.API;
|
|
||||||
using Torch.API.Managers;
|
|
||||||
using Torch.API.ModAPI;
|
|
||||||
using Torch.API.ModAPI.Ingame;
|
|
||||||
using VRage.Scripting;
|
|
||||||
|
|
||||||
namespace Torch.Managers
|
|
||||||
{
|
|
||||||
public class ScriptingManager : IManager
|
|
||||||
{
|
|
||||||
private MyScriptWhitelist _whitelist;
|
|
||||||
|
|
||||||
public void Attach()
|
|
||||||
{
|
|
||||||
_whitelist = MyScriptCompiler.Static.Whitelist;
|
|
||||||
MyScriptCompiler.Static.AddConditionalCompilationSymbols("TORCH");
|
|
||||||
MyScriptCompiler.Static.AddReferencedAssemblies(typeof(ITorchBase).Assembly.Location);
|
|
||||||
MyScriptCompiler.Static.AddImplicitIngameNamespacesFromTypes(typeof(GridExtensions));
|
|
||||||
|
|
||||||
using (var whitelist = _whitelist.OpenBatch())
|
|
||||||
{
|
|
||||||
whitelist.AllowNamespaceOfTypes(MyWhitelistTarget.ModApi, typeof(TorchAPI));
|
|
||||||
whitelist.AllowNamespaceOfTypes(MyWhitelistTarget.Both, typeof(GridExtensions));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
//dump whitelist
|
|
||||||
var whitelist = new StringBuilder();
|
|
||||||
foreach (var pair in MyScriptCompiler.Static.Whitelist.GetWhitelist())
|
|
||||||
{
|
|
||||||
var split = pair.Key.Split(',');
|
|
||||||
whitelist.AppendLine("|-");
|
|
||||||
whitelist.AppendLine($"|{pair.Value} || {split[0]} || {split[1]}");
|
|
||||||
}
|
|
||||||
Log.Info(whitelist);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Detach()
|
|
||||||
{
|
|
||||||
// TODO unregister whitelist patches
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnwhitelistType(Type t)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -10,8 +10,8 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Octokit;
|
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using Torch.API.WebAPI;
|
||||||
|
|
||||||
namespace Torch.Managers
|
namespace Torch.Managers
|
||||||
{
|
{
|
||||||
@@ -21,12 +21,11 @@ namespace Torch.Managers
|
|||||||
public class UpdateManager : Manager
|
public class UpdateManager : Manager
|
||||||
{
|
{
|
||||||
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 string _torchDir = new FileInfo(typeof(UpdateManager).Assembly.Location).DirectoryName;
|
||||||
private Logger _log = LogManager.GetCurrentClassLogger();
|
private Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
[Dependency]
|
[Dependency]
|
||||||
private FilesystemManager _fsManager;
|
private FilesystemManager _fsManager;
|
||||||
|
|
||||||
public UpdateManager(ITorchBase torchInstance) : base(torchInstance)
|
public UpdateManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
{
|
{
|
||||||
//_updatePollTimer = new Timer(TimerElapsed, this, TimeSpan.Zero, TimeSpan.FromMinutes(5));
|
//_updatePollTimer = new Timer(TimerElapsed, this, TimeSpan.Zero, TimeSpan.FromMinutes(5));
|
||||||
@@ -42,49 +41,34 @@ namespace Torch.Managers
|
|||||||
{
|
{
|
||||||
CheckAndUpdateTorch();
|
CheckAndUpdateTorch();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Tuple<Version, string>> TryGetLatestArchiveUrl(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"));
|
|
||||||
if (zip == null)
|
|
||||||
_log.Error($"Latest release of {owner}/{name} does not contain a zip archive.");
|
|
||||||
if (!latest.TagName.TryExtractVersion(out Version version))
|
|
||||||
_log.Error($"Unable to parse version tag for {owner}/{name}");
|
|
||||||
return new Tuple<Version, string>(version, zip?.BrowserDownloadUrl);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_log.Error($"An error occurred getting release information for '{owner}/{name}'");
|
|
||||||
_log.Error(e);
|
|
||||||
return default(Tuple<Version, string>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void CheckAndUpdateTorch()
|
private async void CheckAndUpdateTorch()
|
||||||
{
|
{
|
||||||
// Doesn't work properly or reliably, TODO update when Jenkins is fully configured
|
if (Torch.Config.NoUpdate || !Torch.Config.GetTorchUpdates)
|
||||||
return;
|
|
||||||
|
|
||||||
if (!Torch.Config.GetTorchUpdates)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var releaseInfo = await TryGetLatestArchiveUrl("TorchAPI", "Torch").ConfigureAwait(false);
|
var job = await JenkinsQuery.Instance.GetLatestVersion(Torch.TorchVersion.Branch);
|
||||||
if (releaseInfo.Item1 > Torch.TorchVersion)
|
if (job == null)
|
||||||
{
|
{
|
||||||
_log.Warn($"Updating Torch from version {Torch.TorchVersion} to version {releaseInfo.Item1}");
|
_log.Info("Failed to fetch latest version.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.Version > Torch.TorchVersion)
|
||||||
|
{
|
||||||
|
_log.Warn($"Updating Torch from version {Torch.TorchVersion} to version {job.Version}");
|
||||||
var updateName = Path.Combine(_fsManager.TempDirectory, "torchupdate.zip");
|
var updateName = Path.Combine(_fsManager.TempDirectory, "torchupdate.zip");
|
||||||
new WebClient().DownloadFile(new Uri(releaseInfo.Item2), updateName);
|
//new WebClient().DownloadFile(new Uri(releaseInfo.Item2), updateName);
|
||||||
|
if (!await JenkinsQuery.Instance.DownloadRelease(job, updateName))
|
||||||
|
{
|
||||||
|
_log.Warn("Failed to download new release!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
UpdateFromZip(updateName, _torchDir);
|
UpdateFromZip(updateName, _torchDir);
|
||||||
File.Delete(updateName);
|
File.Delete(updateName);
|
||||||
_log.Warn($"Torch version {releaseInfo.Item1} has been installed, please restart Torch to finish the process.");
|
_log.Warn($"Torch version {job.Version} has been installed, please restart Torch to finish the process.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -104,12 +88,16 @@ namespace Torch.Managers
|
|||||||
{
|
{
|
||||||
foreach (var file in zip.Entries)
|
foreach (var file in zip.Entries)
|
||||||
{
|
{
|
||||||
|
if(file.Name == "NLog-user.config" && File.Exists(Path.Combine(extractPath, file.FullName)))
|
||||||
|
continue;
|
||||||
|
|
||||||
_log.Debug($"Unzipping {file.FullName}");
|
_log.Debug($"Unzipping {file.FullName}");
|
||||||
var targetFile = Path.Combine(extractPath, file.FullName);
|
var targetFile = Path.Combine(extractPath, file.FullName);
|
||||||
_fsManager.SoftDelete(targetFile);
|
_fsManager.SoftDelete(extractPath, file.FullName);
|
||||||
|
file.ExtractToFile(targetFile, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
zip.ExtractToDirectory(extractPath);
|
//zip.ExtractToDirectory(extractPath); //throws exceptions sometimes?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
Torch/MySteamServiceWrapper.cs
Normal file
29
Torch/MySteamServiceWrapper.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using VRage.GameServices;
|
||||||
|
|
||||||
|
namespace Torch
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides static accessor for MySteamService because Keen made it internal
|
||||||
|
/// </summary>
|
||||||
|
public static class MySteamServiceWrapper
|
||||||
|
{
|
||||||
|
private static readonly MethodInfo _getGameService;
|
||||||
|
|
||||||
|
public static IMyGameService Static => (IMyGameService)_getGameService.Invoke(null, null);
|
||||||
|
|
||||||
|
static MySteamServiceWrapper()
|
||||||
|
{
|
||||||
|
var type = Type.GetType("VRage.Steam.MySteamService, VRage.Steam");
|
||||||
|
var prop = type.GetProperty("Static", BindingFlags.Static | BindingFlags.Public);
|
||||||
|
_getGameService = prop.GetGetMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IMyGameService Init(bool dedicated, uint appId)
|
||||||
|
{
|
||||||
|
return (IMyGameService)Activator.CreateInstance(Type.GetType("VRage.Steam.MySteamService, VRage.Steam"), dedicated, appId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -12,7 +12,7 @@ namespace Torch.Patches
|
|||||||
public static class GameAnalyticsPatch
|
public static class GameAnalyticsPatch
|
||||||
{
|
{
|
||||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
private static Action<ILogger> _setLogger;
|
private static Action<ILogger, ILogger> _setLogger;
|
||||||
|
|
||||||
public static void Patch(PatchContext ctx)
|
public static void Patch(PatchContext ctx)
|
||||||
{
|
{
|
||||||
@@ -27,7 +27,7 @@ namespace Torch.Patches
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RuntimeHelpers.RunClassConstructor(type.TypeHandle);
|
RuntimeHelpers.RunClassConstructor(type.TypeHandle);
|
||||||
_setLogger = loggerField?.CreateSetter<ILogger>();
|
_setLogger = loggerField?.CreateSetter<ILogger, ILogger>();
|
||||||
FixLogging();
|
FixLogging();
|
||||||
|
|
||||||
ConstructorInfo ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], new ParameterModifier[0]);
|
ConstructorInfo ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], new ParameterModifier[0]);
|
||||||
@@ -42,7 +42,7 @@ namespace Torch.Patches
|
|||||||
|
|
||||||
private static void FixLogging()
|
private static void FixLogging()
|
||||||
{
|
{
|
||||||
_setLogger(LogManager.GetLogger("GameAnalytics"));
|
_setLogger(null, LogManager.GetLogger("GameAnalytics"));
|
||||||
if (!(LogManager.Configuration is XmlLoggingConfiguration))
|
if (!(LogManager.Configuration is XmlLoggingConfiguration))
|
||||||
LogManager.Configuration = new XmlLoggingConfiguration(Path.Combine(
|
LogManager.Configuration = new XmlLoggingConfiguration(Path.Combine(
|
||||||
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) ?? Environment.CurrentDirectory, "NLog.config"));
|
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) ?? Environment.CurrentDirectory, "NLog.config"));
|
||||||
|
@@ -19,7 +19,7 @@ namespace Torch.Patches
|
|||||||
|
|
||||||
internal static void Patch(PatchContext target)
|
internal static void Patch(PatchContext target)
|
||||||
{
|
{
|
||||||
ConstructorInfo ctor = typeof(MySandboxGame).GetConstructor(new[] { typeof(string[]) });
|
ConstructorInfo ctor = typeof(MySandboxGame).GetConstructor(new[] { typeof(string[]), typeof(IntPtr) });
|
||||||
if (ctor == null)
|
if (ctor == null)
|
||||||
throw new ArgumentException("Can't find constructor MySandboxGame(string[])");
|
throw new ArgumentException("Can't find constructor MySandboxGame(string[])");
|
||||||
target.GetPattern(ctor).Prefixes.Add(MethodRef(PrefixConstructor));
|
target.GetPattern(ctor).Prefixes.Add(MethodRef(PrefixConstructor));
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user