Compare commits
99 Commits
1.0.182.32
...
v1.2.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9471d83a45 | ||
![]() |
52b225d944 | ||
![]() |
c8f0a61209 | ||
![]() |
59c3e9eb54 | ||
![]() |
d20d68b831 | ||
![]() |
cfda1f8eef | ||
![]() |
2fb222125a | ||
![]() |
c7c3c00783 | ||
![]() |
dcc130e2cf | ||
![]() |
491f3d3af4 | ||
![]() |
178fcb8164 | ||
![]() |
3d6806b63a | ||
![]() |
22e3019610 | ||
![]() |
7937cbd122 | ||
![]() |
2bef34ee5b | ||
![]() |
efe7236d31 | ||
![]() |
67dba9c820 | ||
![]() |
52c509aba0 | ||
![]() |
437c7d293e | ||
![]() |
2b6ce4f25b | ||
![]() |
64f123abe9 | ||
![]() |
599a98bceb | ||
![]() |
2cd1b8bd4e | ||
![]() |
c0be9c25da | ||
![]() |
5cea66374f | ||
![]() |
ee1c270c68 | ||
![]() |
4ab08e2faf | ||
![]() |
dd094edb88 | ||
![]() |
56e45236d8 | ||
![]() |
be9a8c5839 | ||
![]() |
8c11baf3b9 | ||
![]() |
6ce679bd83 | ||
![]() |
a4b1b9bb96 | ||
![]() |
91ad78e6a2 | ||
![]() |
4a68d66ab0 | ||
![]() |
4cb50b556f | ||
![]() |
b5f73a99cc | ||
![]() |
e9476a59e8 | ||
![]() |
f48f23c2eb | ||
![]() |
ddf465d8c9 | ||
![]() |
7149287b8e | ||
![]() |
cc709c6bb3 | ||
![]() |
8d101c4c11 | ||
![]() |
64eef6cd8e | ||
![]() |
f8ae3c0dd1 | ||
![]() |
589205edc3 | ||
![]() |
48b212faaf | ||
![]() |
0554dbc608 | ||
![]() |
3564eb805c | ||
![]() |
62a8064edd | ||
![]() |
55ed45190b | ||
![]() |
a6ae96093f | ||
![]() |
5b1afe6d50 | ||
![]() |
60df71a74c | ||
![]() |
a6d5da861f | ||
![]() |
afc10911f7 | ||
![]() |
0686e95c72 | ||
![]() |
234754fd49 | ||
![]() |
efb8d0f226 | ||
![]() |
3f881f7d67 | ||
![]() |
64d38abc99 | ||
![]() |
4a39362702 | ||
![]() |
db2d3794ae | ||
![]() |
526ff6fff0 | ||
![]() |
eebc0e428e | ||
![]() |
80aca514b2 | ||
![]() |
3e8068e82d | ||
![]() |
601fbcd176 | ||
![]() |
40eab15d69 | ||
![]() |
ceb272c0b4 | ||
![]() |
80d4f62694 | ||
![]() |
42f58a8649 | ||
![]() |
6b9af71967 | ||
![]() |
dbd98a09c5 | ||
![]() |
c6a6363163 | ||
![]() |
82815f66e5 | ||
![]() |
0a38eb770d | ||
![]() |
97da740e7e | ||
![]() |
42bb24ca6a | ||
![]() |
2f3b6cdda7 | ||
![]() |
525b496774 | ||
![]() |
562bb77dda | ||
![]() |
76a13dc53a | ||
![]() |
df0f8072a9 | ||
![]() |
87d9825c91 | ||
![]() |
3dba4f744f | ||
![]() |
1fcfe6fb5f | ||
![]() |
3ece4baba6 | ||
![]() |
f49dae2cbf | ||
![]() |
ddf15d756a | ||
![]() |
96d1faddbe | ||
![]() |
17ee96038c | ||
![]() |
e9b432288e | ||
![]() |
b814d1210b | ||
![]() |
c137fb4953 | ||
![]() |
4acce1c9c9 | ||
![]() |
8ab16c3d30 | ||
![]() |
7373dd37a6 | ||
![]() |
1251b945bc |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -252,3 +252,6 @@ ModelManifest.xml
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
GameBinaries
|
||||
|
||||
# Generated Files
|
||||
**Gen.cs
|
||||
|
33
CHANGELOG.md
Normal file
33
CHANGELOG.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Torch 1.1.229.265
|
||||
* Features
|
||||
- Added more lenient version parsing for plugins (v#.# should work)
|
||||
- Added countdown option to restart command (!restart [seconds])
|
||||
* Fixes
|
||||
- General fixes to work with the latest SE version
|
||||
- Fixed config changes not saving
|
||||
- (hopefully) Fixed issue causing crashes on servers using the Windows Classic theme
|
||||
|
||||
# Torch 1.1.207.7
|
||||
* Notes
|
||||
- This release makes significant changes to TorchConfig.xml. It has been renamed to Torch.cfg and has different options.
|
||||
* Features
|
||||
- Plugins, Torch, and the DS can now all update automatically
|
||||
- Changed command prefix to !
|
||||
- Added manual save command (thanks to Maldark)
|
||||
- Added restart command
|
||||
- Improved instance creation: now creates an entire skeleton instance with blank config
|
||||
- Added instance name to console title
|
||||
* Fixes
|
||||
- Optimized UI so it's snappier and freezes less often
|
||||
- Fixed NetworkManager.RaiseEvent overload that had an off-by-one bug
|
||||
- Fixed chat window so it automatically scrolls down
|
||||
|
||||
# Torch 1.0.182.329
|
||||
* Improved logging, logs now to go the Logs folder and aren't deleted on start
|
||||
* Fixed chat tab not enabling with -autostart
|
||||
* Fixed player list
|
||||
* Watchdog time-out is now configurable in TorchConfig.xml
|
||||
* Fixed infinario log spam
|
||||
* Fixed crash when sending empty message from chat tab
|
||||
* Fixed permissions on Torch commands
|
||||
* Changed plugin StoragePath to the current instance path (per-instance configs)
|
26
CONTRIBUTING.md
Normal file
26
CONTRIBUTING.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Making a Pull Request
|
||||
* Fork this repository and make sure your local **staging** branch is up to date with the main repository.
|
||||
* Create a new branch from the **staging** branch for your addition with an appropriate name, e.g. **add-restart-command**
|
||||
* PRs work by submitting the *entire* branch, so this allows you to continue work without locking up your whole repository.
|
||||
* Commit your changes to that branch, making sure that you **follow the code guidelines below**.
|
||||
* Submit your branch as a PR to be reviewed, with Torch's **staging** branch as the base.
|
||||
|
||||
## Naming Conventions
|
||||
* Types: **PascalCase**
|
||||
* Prefix interfaces with "**I**"
|
||||
* Suffix delegates with "**Del**"
|
||||
* Methods: **PascalCase**
|
||||
* Method names should generally use verbs in the infinitive tense, for example `GetValue()` or `OpenFile()`. Callbacks and events should use present continuous (-ing) or past tense depending on the context.
|
||||
* Non-Private Members: **PascalCase**
|
||||
* Private Members: **_camelCase**
|
||||
|
||||
## Code Design
|
||||
* **One type per file** with the exception of nested types and delegate declarations.
|
||||
* **No public fields** except for consts, use properties instead
|
||||
* **No stateful static types.** These are a pain to clean up, static types should not store any information.
|
||||
* Use **[dependency injection](https://stackoverflow.com/a/130862)** when possible. Most Torch code uses constructor injection.
|
||||
* **Events and actions** should be null checked before calling or invoked with the `action?.Invoke()` syntax.
|
||||
|
||||
## Documentation
|
||||
* All types and members not marked **private** or **internal** should have XML documentation using the `/// <summary>` tag.
|
||||
* Interface implementations and overridden methods should use the `/// <inheritdoc />` tag unless the summary needs to be changed from the base/interface summary.
|
22
Jenkins/jenkins-grab-se.ps1
Normal file
22
Jenkins/jenkins-grab-se.ps1
Normal file
@@ -0,0 +1,22 @@
|
||||
pushd
|
||||
|
||||
$steamData = "C:/Steam/Data/"
|
||||
$steamCMDPath = "C:/Steam/steamcmd/"
|
||||
$steamCMDZip = "C:/Steam/steamcmd.zip"
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
|
||||
if (!(Test-Path $steamData)) {
|
||||
mkdir "$steamData"
|
||||
}
|
||||
if (!(Test-Path $steamCMDPath)) {
|
||||
if (!(Test-Path $steamCMDZip)) {
|
||||
(New-Object System.Net.WebClient).DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", "$steamCMDZip");
|
||||
}
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($steamCMDZip, $steamCMDPath)
|
||||
}
|
||||
|
||||
cd "$steamData"
|
||||
& "$steamCMDPath/steamcmd.exe" "+login anonymous" "+force_install_dir $steamData" "+app_update 298740" "+quit"
|
||||
|
||||
popd
|
52
Jenkins/release.ps1
Normal file
52
Jenkins/release.ps1
Normal file
@@ -0,0 +1,52 @@
|
||||
param([string] $ApiBase, [string]$tagName, [string]$authinfo, [string[]] $assetPaths)
|
||||
Add-Type -AssemblyName "System.Web"
|
||||
|
||||
$headers = @{
|
||||
Authorization = "Basic " + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($authinfo))
|
||||
Accept = "application/vnd.github.v3+json"
|
||||
}
|
||||
try
|
||||
{
|
||||
Write-Output("Checking if release with tag " + $tagName + " already exists...")
|
||||
$release = Invoke-RestMethod -Uri ($ApiBase+"releases/tags/$tagName") -Method "GET" -Headers $headers
|
||||
Write-Output(" Using existing release " + $release.id + " at " + $release.html_url)
|
||||
} catch {
|
||||
Write-Output(" Doesn't exist")
|
||||
$rel_arg = @{
|
||||
tag_name=$tagName
|
||||
name="Generated $tagName"
|
||||
body=""
|
||||
draft=$TRUE
|
||||
prerelease=$tagName.Contains("alpha") -or $tagName.Contains("beta")
|
||||
}
|
||||
Write-Output("Creating new release " + $tagName + "...")
|
||||
$release = Invoke-RestMethod -Uri ($ApiBase+"releases") -Method "POST" -Headers $headers -Body (ConvertTo-Json($rel_arg))
|
||||
Write-Output(" Created new release " + $tagName + " at " + $release.html_url)
|
||||
}
|
||||
|
||||
$assetsApiBase = $release.assets_url
|
||||
Write-Output("Checking for existing assets...")
|
||||
$existingAssets = Invoke-RestMethod -Uri ($assetsApiBase) -Method "GET" -Headers $headers
|
||||
$assetLabels = ($assetPaths | ForEach-Object {[System.IO.Path]::GetFileName($_)})
|
||||
foreach ($asset in $existingAssets) {
|
||||
if ($assetLabels -contains $asset.name) {
|
||||
$uri = $asset.url
|
||||
Write-Output(" Deleting old asset " + $asset.name + " (id " + $asset.id + "); URI=" + $uri)
|
||||
$result = Invoke-RestMethod -Uri $uri -Method "DELETE" -Headers $headers
|
||||
}
|
||||
}
|
||||
Write-Output("Uploading assets...")
|
||||
$uploadUrl = $release.upload_url.Substring(0, $release.upload_url.LastIndexOf('{'))
|
||||
foreach ($asset in $assetPaths) {
|
||||
$assetName = [System.IO.Path]::GetFileName($asset)
|
||||
$assetType = [System.Web.MimeMapping]::GetMimeMapping($asset)
|
||||
$assetData = [System.IO.File]::ReadAllBytes($asset)
|
||||
$headerExtra = $headers + @{
|
||||
"Content-Type" = $assetType
|
||||
Name = $assetName
|
||||
}
|
||||
$uri = $uploadUrl + "?name=" + $assetName
|
||||
Write-Output(" Uploading " + $asset + " as " + $assetType + "; URI=" + $uri)
|
||||
$result = Invoke-RestMethod -Uri $uri -Method "POST" -Headers $headerExtra -Body $assetData
|
||||
Write-Output(" ID=" + $result.id + ", found at=" + $result.browser_download_url)
|
||||
}
|
67
Jenkinsfile
vendored
Normal file
67
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
node {
|
||||
stage('Checkout') {
|
||||
checkout scm
|
||||
bat 'git pull --tags'
|
||||
}
|
||||
|
||||
stage('Acquire SE') {
|
||||
bat 'powershell -File Jenkins/jenkins-grab-se.ps1'
|
||||
bat 'IF EXIST GameBinaries RMDIR GameBinaries'
|
||||
bat 'mklink /J GameBinaries "C:/Steam/Data/DedicatedServer64/"'
|
||||
}
|
||||
|
||||
stage('Acquire NuGet Packages') {
|
||||
bat 'nuget restore Torch.sln'
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
currentBuild.description = bat(returnStdout: true, script: '@powershell -File Versioning/version.ps1').trim()
|
||||
bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=Release /p:Platform=x64"
|
||||
}
|
||||
|
||||
stage('Test') {
|
||||
bat 'IF NOT EXIST reports MKDIR reports'
|
||||
bat "\"packages/xunit.runner.console.2.2.0/tools/xunit.console.exe\" \"bin-test/x64/Release/Torch.Tests.dll\" \"bin-test/x64/Release/Torch.Server.Tests.dll\" \"bin-test/x64/Release/Torch.Client.Tests.dll\" -parallel none -xml \"reports/Torch.Tests.xml\""
|
||||
step([
|
||||
$class: 'XUnitBuilder',
|
||||
thresholdMode: 1,
|
||||
thresholds: [[$class: 'FailedThreshold', failureThreshold: '1']],
|
||||
tools: [[
|
||||
$class: 'XUnitDotNetTestType',
|
||||
deleteOutputFiles: true,
|
||||
failIfNotNew: true,
|
||||
pattern: 'reports/*.xml',
|
||||
skipNoTestFiles: false,
|
||||
stopProcessingIfError: true
|
||||
]]
|
||||
])
|
||||
}
|
||||
|
||||
stage('Archive') {
|
||||
bat '''IF EXIST bin\\torch-server.zip DEL bin\\torch-server.zip
|
||||
IF EXIST bin\\package-server RMDIR /S /Q bin\\package-server
|
||||
xcopy bin\\x64\\Release bin\\package-server\\
|
||||
del bin\\package-server\\Torch.Client*'''
|
||||
bat "powershell -Command \"Add-Type -Assembly System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::CreateFromDirectory(\\\"\$PWD\\bin\\package-server\\\", \\\"\$PWD\\bin\\torch-server.zip\\\")\""
|
||||
archiveArtifacts artifacts: 'bin/torch-server.zip', caseSensitive: false, onlyIfSuccessful: true
|
||||
|
||||
bat '''IF EXIST bin\\torch-client.zip DEL bin\\torch-client.zip
|
||||
IF EXIST bin\\package-client RMDIR /S /Q bin\\package-client
|
||||
xcopy bin\\x64\\Release bin\\package-client\\
|
||||
del bin\\package-client\\Torch.Server*'''
|
||||
bat "powershell -Command \"Add-Type -Assembly System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::CreateFromDirectory(\\\"\$PWD\\bin\\package-client\\\", \\\"\$PWD\\bin\\torch-client.zip\\\")\""
|
||||
archiveArtifacts artifacts: 'bin/torch-client.zip', caseSensitive: false, onlyIfSuccessful: true
|
||||
|
||||
archiveArtifacts artifacts: 'bin/x64/Release/Torch*', caseSensitive: false, fingerprint: true, onlyIfSuccessful: true
|
||||
}
|
||||
|
||||
gitVersion = bat(returnStdout: true, script: "@git describe --tags").trim()
|
||||
gitSimpleVersion = bat(returnStdout: true, script: "@git describe --tags --abbrev=0").trim()
|
||||
if (gitVersion == gitSimpleVersion) {
|
||||
stage('Release') {
|
||||
withCredentials([usernamePassword(credentialsId: 'torch-github', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
|
||||
powershell "& ./Jenkins/release.ps1 \"https://api.github.com/repos/TorchAPI/Torch/\" \"$gitSimpleVersion\" \"$USERNAME:$PASSWORD\" @(\"bin/torch-server.zip\", \"bin/torch-client.zip\")"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
README.md
20
README.md
@@ -1,4 +1,4 @@
|
||||
Discord: [](https://discord.gg/8uHZykr)
|
||||
[](https://discord.gg/8uHZykr) [](http://server.torchapi.net:8080/job/Torch/job/Torch/job/master/)
|
||||
|
||||
# What is Torch?
|
||||
Torch is the successor to SE Server Extender and gives server admins the tools they need to keep their Space Engineers servers running smoothly. It features a user interface with live management tools and a plugin system so you can run your server exactly how you'd like. Torch is still in early development so there may be bugs and incomplete features.
|
||||
@@ -10,18 +10,15 @@ Torch is the successor to SE Server Extender and gives server admins the tools t
|
||||
* Organized, easy to use configuration editor
|
||||
* Extensible using the Torch plugin system
|
||||
|
||||
# Installation
|
||||
|
||||
* Get the latest Torch release here: https://github.com/TorchAPI/Torch/releases
|
||||
* 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.
|
||||
|
||||
# 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.
|
||||
|
||||
# Installation Guide
|
||||
Note: Until Torch is in a stable, nearly feature complete state there will not be any binaries available. You'll have to compile the solution yourself.
|
||||
|
||||
### Automatic (recommended)
|
||||
* Unzip Torch to its own folder, run Torch.Server.exe and enter 'y' in the prompt for automatic updates. Torch will automatically download the Space Engineers files and generate all of the configs/folders necessary.
|
||||
|
||||
### Manual (for hosting companies or the paranoid)
|
||||
* Install the Space Engineers DS and then unzip the Torch files into the server's DedicatedServer64 directory. It will automatically detect the manual install and disable automatic updates.
|
||||
|
||||
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
|
||||
@@ -29,4 +26,5 @@ Install plugins by unzipping them into the 'Plugins' folder which should be in t
|
||||
* [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](https://www.patreon.com/bePatron?u=847269)!
|
||||
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon.
|
||||
[](https://www.patreon.com/bePatron?u=847269)!
|
||||
|
@@ -3,7 +3,7 @@
|
||||
@echo off
|
||||
set /p path="Please enter the folder location of your SpaceEngineersDedicated.exe: "
|
||||
cd %~dp0
|
||||
mklink /D GameBinaries %path%
|
||||
mklink /J GameBinaries "%path%"
|
||||
if errorlevel 1 goto Error
|
||||
echo Done! You can now open the Torch solution without issue.
|
||||
goto End
|
||||
|
@@ -1,39 +1,122 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.Session;
|
||||
using VRage.Game.ModAPI;
|
||||
|
||||
namespace Torch.API
|
||||
{
|
||||
/// <summary>
|
||||
/// API for Torch functions shared between client and server.
|
||||
/// </summary>
|
||||
public interface ITorchBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired when the session begins loading.
|
||||
/// </summary>
|
||||
event Action SessionLoading;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the session finishes loading.
|
||||
/// </summary>
|
||||
event Action SessionLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when the session begins unloading.
|
||||
/// </summary>
|
||||
event Action SessionUnloading;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the session finishes unloading.
|
||||
/// </summary>
|
||||
event Action SessionUnloaded;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently running session instance, or null if none exists.
|
||||
/// </summary>
|
||||
ITorchSession CurrentSession { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for the current instance.
|
||||
/// </summary>
|
||||
ITorchConfig Config { get; }
|
||||
|
||||
/// <inheritdoc cref="IMultiplayerManager"/>
|
||||
[Obsolete]
|
||||
IMultiplayerManager Multiplayer { get; }
|
||||
|
||||
/// <inheritdoc cref="IPluginManager"/>
|
||||
[Obsolete]
|
||||
IPluginManager Plugins { get; }
|
||||
|
||||
/// <inheritdoc cref="IDependencyManager"/>
|
||||
IDependencyManager Managers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The binary version of the current instance.
|
||||
/// </summary>
|
||||
Version TorchVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoke an action on the game thread.
|
||||
/// </summary>
|
||||
void Invoke(Action action);
|
||||
|
||||
/// <summary>
|
||||
/// Invoke an action on the game thread and block until it has completed.
|
||||
/// If this is called on the game thread the action will execute immediately.
|
||||
/// </summary>
|
||||
void InvokeBlocking(Action action);
|
||||
|
||||
/// <summary>
|
||||
/// Invoke an action on the game thread asynchronously.
|
||||
/// </summary>
|
||||
Task InvokeAsync(Action action);
|
||||
string[] RunArgs { get; set; }
|
||||
bool IsOnGameThread();
|
||||
|
||||
/// <summary>
|
||||
/// Start the Torch instance.
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Stop the Torch instance.
|
||||
/// </summary>
|
||||
void Stop();
|
||||
|
||||
/// <summary>
|
||||
/// Restart the Torch instance.
|
||||
/// </summary>
|
||||
void Restart();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a save of the game.
|
||||
/// </summary>
|
||||
/// <param name="callerId">Id of the player who initiated the save.</param>
|
||||
Task Save(long callerId);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the Torch instance.
|
||||
/// </summary>
|
||||
void Init();
|
||||
T GetManager<T>() where T : class, IManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API for the Torch server.
|
||||
/// </summary>
|
||||
public interface ITorchServer : ITorchBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Path of the dedicated instance folder.
|
||||
/// </summary>
|
||||
string InstancePath { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API for the Torch client.
|
||||
/// </summary>
|
||||
public interface ITorchClient : ITorchBase
|
||||
{
|
||||
|
||||
|
@@ -1,13 +1,23 @@
|
||||
namespace Torch
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
public interface ITorchConfig
|
||||
{
|
||||
bool Autostart { get; set; }
|
||||
bool ForceUpdate { get; set; }
|
||||
bool GetPluginUpdates { get; set; }
|
||||
bool GetTorchUpdates { get; set; }
|
||||
string InstanceName { get; set; }
|
||||
string InstancePath { get; set; }
|
||||
bool RedownloadPlugins { get; set; }
|
||||
bool AutomaticUpdates { get; set; }
|
||||
bool NoGui { get; set; }
|
||||
bool NoUpdate { get; set; }
|
||||
List<string> Plugins { get; set; }
|
||||
bool RestartOnCrash { get; set; }
|
||||
bool ShouldUpdatePlugins { get; }
|
||||
bool ShouldUpdateTorch { get; }
|
||||
int TickTimeout { get; set; }
|
||||
string WaitForPID { get; set; }
|
||||
|
||||
bool Save(string path = null);
|
||||
}
|
||||
|
30
Torch.API/Managers/DependencyManagerExtensions.cs
Normal file
30
Torch.API/Managers/DependencyManagerExtensions.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
public static class DependencyManagerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Removes a single manager from this dependency manager.
|
||||
/// </summary>
|
||||
/// <param name="managerType">The dependency type to remove</param>
|
||||
/// <returns>The manager that was removed, or null if one wasn't removed</returns>
|
||||
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||
public static IManager RemoveManager(this IDependencyManager depManager, Type managerType)
|
||||
{
|
||||
IManager mgr = depManager.GetManager(managerType);
|
||||
return depManager.RemoveManager(mgr) ? mgr : null;
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes a single manager from this dependency manager.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The dependency type to remove</typeparam>
|
||||
/// <returns>The manager that was removed, or null if one wasn't removed</returns>
|
||||
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||
public static IManager RemoveManager<T>(this IDependencyManager depManager)
|
||||
{
|
||||
return depManager.RemoveManager(typeof(T));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
15
Torch.API/Managers/DependencyProviderExtensions.cs
Normal file
15
Torch.API/Managers/DependencyProviderExtensions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
public static class DependencyProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the manager that provides the given type. If there is no such manager, returns null.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of manager</typeparam>
|
||||
/// <returns>manager, or null if none exists</returns>
|
||||
public static T GetManager<T>(this IDependencyProvider depProvider) where T : class, IManager
|
||||
{
|
||||
return (T)depProvider.GetManager(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
62
Torch.API/Managers/IDependencyManager.cs
Normal file
62
Torch.API/Managers/IDependencyManager.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages a set of <see cref="IManager"/> and the dependencies between them.
|
||||
/// </summary>
|
||||
public interface IDependencyManager : IDependencyProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the given manager into the dependency system.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method only returns false when there is already a manager registered with a type derived from this given manager,
|
||||
/// or when the given manager is derived from an already existing manager.
|
||||
/// </remarks>
|
||||
/// <param name="manager">Manager to register</param>
|
||||
/// <exception cref="InvalidOperationException">When adding a new manager to an initialized dependency manager</exception>
|
||||
/// <returns>true if added, false if not</returns>
|
||||
bool AddManager(IManager manager);
|
||||
|
||||
/// <summary>
|
||||
/// Clears all managers registered with this dependency manager
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||
void ClearManagers();
|
||||
|
||||
/// <summary>
|
||||
/// Removes a single manager from this dependency manager.
|
||||
/// </summary>
|
||||
/// <param name="manager">The manager to remove</param>
|
||||
/// <returns>true if successful, false if the manager wasn't found</returns>
|
||||
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||
bool RemoveManager(IManager manager);
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the dependency manager, then attaches all its registered managers in <see cref="AttachOrder" />
|
||||
/// </summary>
|
||||
void Attach();
|
||||
|
||||
/// <summary>
|
||||
/// Detaches all registered managers in <see cref="DetachOrder"/>
|
||||
/// </summary>
|
||||
void Detach();
|
||||
|
||||
/// <summary>
|
||||
/// The order that managers should be attached in. (Dependencies, then dependents)
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">When trying to determine load order before this dependency manager is initialized</exception>
|
||||
IEnumerable<IManager> AttachOrder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The order that managers should be detached in. (Dependents, then dependencies)
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">When trying to determine unload order before this dependency manager is initialized</exception>
|
||||
IEnumerable<IManager> DetachOrder { get; }
|
||||
}
|
||||
}
|
18
Torch.API/Managers/IDependencyProvider.cs
Normal file
18
Torch.API/Managers/IDependencyProvider.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
public interface IDependencyProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the manager that provides the given type. If there is no such manager, returns null.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of manager</param>
|
||||
/// <returns>manager, or null if none exists</returns>
|
||||
IManager GetManager(Type type);
|
||||
}
|
||||
}
|
@@ -6,8 +6,19 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Base interface for Torch managers.
|
||||
/// </summary>
|
||||
public interface IManager
|
||||
{
|
||||
void Init();
|
||||
/// <summary>
|
||||
/// Attaches the manager to the session. Called once this manager's dependencies have been attached.
|
||||
/// </summary>
|
||||
void Attach();
|
||||
|
||||
/// <summary>
|
||||
/// Detaches the manager from the session. Called before this manager's dependencies are detached.
|
||||
/// </summary>
|
||||
void Detach();
|
||||
}
|
||||
}
|
||||
|
@@ -6,17 +6,56 @@ using VRage.Game.ModAPI;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for received messages.
|
||||
/// </summary>
|
||||
/// <param name="message">Message data.</param>
|
||||
/// <param name="sendToOthers">Flag to broadcast message to other players.</param>
|
||||
public delegate void MessageReceivedDel(IChatMessage message, ref bool sendToOthers);
|
||||
|
||||
/// <summary>
|
||||
/// API for multiplayer related functions.
|
||||
/// </summary>
|
||||
public interface IMultiplayerManager : IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired when a player joins.
|
||||
/// </summary>
|
||||
event Action<IPlayer> PlayerJoined;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a player disconnects.
|
||||
/// </summary>
|
||||
event Action<IPlayer> PlayerLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a chat message is received.
|
||||
/// </summary>
|
||||
event MessageReceivedDel MessageReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat message to all or one specific player.
|
||||
/// </summary>
|
||||
void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Blue);
|
||||
|
||||
/// <summary>
|
||||
/// Kicks the player from the game.
|
||||
/// </summary>
|
||||
void KickPlayer(ulong steamId);
|
||||
|
||||
/// <summary>
|
||||
/// Bans or unbans a player from the game.
|
||||
/// </summary>
|
||||
void BanPlayer(ulong steamId, bool banned = true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a player by their Steam64 ID or returns null if the player isn't found.
|
||||
/// </summary>
|
||||
IMyPlayer GetPlayerBySteamId(ulong id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a player by their display name or returns null if the player isn't found.
|
||||
/// </summary>
|
||||
IMyPlayer GetPlayerByName(string name);
|
||||
}
|
||||
}
|
@@ -9,14 +9,30 @@ using VRage.Network;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// API for the network intercept.
|
||||
/// </summary>
|
||||
public interface INetworkManager : IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Register a network handler.
|
||||
/// </summary>
|
||||
void RegisterNetworkHandler(INetworkHandler handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for multiplayer network messages.
|
||||
/// </summary>
|
||||
public interface INetworkHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if the handler can process the call site.
|
||||
/// </summary>
|
||||
bool CanHandle(CallSite callSite);
|
||||
|
||||
/// <summary>
|
||||
/// Processes a network message.
|
||||
/// </summary>
|
||||
bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
|
||||
}
|
||||
}
|
||||
|
@@ -6,11 +6,29 @@ using VRage.Plugins;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// API for the Torch plugin manager.
|
||||
/// </summary>
|
||||
public interface IPluginManager : IManager, IEnumerable<ITorchPlugin>
|
||||
{
|
||||
event Action<List<ITorchPlugin>> PluginsLoaded;
|
||||
List<ITorchPlugin> Plugins { get; }
|
||||
/// <summary>
|
||||
/// Fired when plugins are loaded.
|
||||
/// </summary>
|
||||
event Action<IList<ITorchPlugin>> PluginsLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of loaded plugins.
|
||||
/// </summary>
|
||||
IList<ITorchPlugin> Plugins { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates all loaded plugins.
|
||||
/// </summary>
|
||||
void UpdatePlugins();
|
||||
void DisposePlugins();
|
||||
|
||||
/// <summary>
|
||||
/// Load plugins.
|
||||
/// </summary>
|
||||
void LoadPlugins();
|
||||
}
|
||||
}
|
@@ -1,22 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the given type should be loaded by the plugin manager as a plugin.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class PluginAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The display name of the plugin
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
/// <summary>
|
||||
/// The version of the plugin
|
||||
/// </summary>
|
||||
public Version Version { get; }
|
||||
/// <summary>
|
||||
/// The GUID of the plugin
|
||||
/// </summary>
|
||||
public Guid Guid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new plugin attribute with the given attributes
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="version"></param>
|
||||
/// <param name="guid"></param>
|
||||
public PluginAttribute(string name, string version, string guid)
|
||||
{
|
||||
Name = name;
|
||||
Version = Version.Parse(version);
|
||||
Guid = Guid.Parse(guid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new plugin attribute with the given attributes. Version is computed as the version of the assembly containing the given type.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="versionSupplier">Version is this type's assembly's version</param>
|
||||
/// <param name="guid"></param>
|
||||
public PluginAttribute(string name, Type versionSupplier, string guid)
|
||||
{
|
||||
Name = name;
|
||||
Version = versionSupplier.Assembly.GetName().Version;
|
||||
Guid = Guid.Parse(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,36 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("TorchAPI")]
|
||||
[assembly: AssemblyTitle("Torch API")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("TorchAPI")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyProduct("Torch")]
|
||||
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("fba5d932-6254-4a1e-baf4-e229fa94e3c2")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
#if DEBUG
|
||||
[assembly: AssemblyConfiguration("Debug")]
|
||||
#else
|
||||
[assembly: AssemblyConfiguration("Release")]
|
||||
#endif
|
@@ -6,11 +6,29 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to indicate the state of the dedicated server.
|
||||
/// </summary>
|
||||
public enum ServerState
|
||||
{
|
||||
/// <summary>
|
||||
/// The server is not running.
|
||||
/// </summary>
|
||||
Stopped,
|
||||
|
||||
/// <summary>
|
||||
/// The server is starting/loading the session.
|
||||
/// </summary>
|
||||
Starting,
|
||||
|
||||
/// <summary>
|
||||
/// The server is running.
|
||||
/// </summary>
|
||||
Running,
|
||||
|
||||
/// <summary>
|
||||
/// The server encountered an error.
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
}
|
||||
|
29
Torch.API/Session/ITorchSession.cs
Normal file
29
Torch.API/Session/ITorchSession.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Sandbox.Game.World;
|
||||
using Torch.API.Managers;
|
||||
|
||||
namespace Torch.API.Session
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Torch code working with a single game session
|
||||
/// </summary>
|
||||
public interface ITorchSession
|
||||
{
|
||||
/// <summary>
|
||||
/// The Torch instance this session is bound to
|
||||
/// </summary>
|
||||
ITorchBase Torch { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Space Engineers game session this session is bound to.
|
||||
/// </summary>
|
||||
MySession KeenSession { get; }
|
||||
|
||||
/// <inheritdoc cref="IDependencyManager"/>
|
||||
IDependencyManager Managers { get; }
|
||||
}
|
||||
}
|
46
Torch.API/Session/ITorchSessionManager.cs
Normal file
46
Torch.API/Session/ITorchSessionManager.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Torch.API.Managers;
|
||||
|
||||
namespace Torch.API.Session
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a manager for the given session if applicable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is for creating managers that will live inside the session, not the manager that controls sesssions.
|
||||
/// </remarks>
|
||||
/// <param name="session">The session to construct a bound manager for</param>
|
||||
/// <returns>The manager that will live in the session, or null if none.</returns>
|
||||
public delegate IManager SessionManagerFactoryDel(ITorchSession session);
|
||||
|
||||
/// <summary>
|
||||
/// Manages the creation and destruction of <see cref="ITorchSession"/> instances for each <see cref="Sandbox.Game.World.MySession"/> created by Space Engineers.
|
||||
/// </summary>
|
||||
public interface ITorchSessionManager : IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently running session
|
||||
/// </summary>
|
||||
ITorchSession CurrentSession { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given factory as a supplier for session based managers
|
||||
/// </summary>
|
||||
/// <param name="factory">Session based manager supplier</param>
|
||||
/// <returns>true if added, false if already present</returns>
|
||||
/// <exception cref="ArgumentNullException">If the factory is null</exception>
|
||||
bool AddFactory(SessionManagerFactoryDel factory);
|
||||
|
||||
/// <summary>
|
||||
/// Remove the given factory from the suppliers for session based managers
|
||||
/// </summary>
|
||||
/// <param name="factory">Session based manager supplier</param>
|
||||
/// <returns>true if removed, false if not present</returns>
|
||||
/// <exception cref="ArgumentNullException">If the factory is null</exception>
|
||||
bool RemoveFactory(SessionManagerFactoryDel factory);
|
||||
}
|
||||
}
|
@@ -2,8 +2,6 @@
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
@@ -12,10 +10,12 @@
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
@@ -23,14 +23,14 @@
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<DocumentationFile>bin\x64\Release\Torch.API.xml</DocumentationFile>
|
||||
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.API.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
|
||||
@@ -38,8 +38,8 @@
|
||||
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="NLog">
|
||||
<HintPath>..\packages\NLog.4.4.1\lib\net45\NLog.dll</HintPath>
|
||||
<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>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="PresentationCore" />
|
||||
@@ -156,14 +156,21 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||
<Link>Properties\AssemblyVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="ConnectionState.cs" />
|
||||
<Compile Include="IChatMessage.cs" />
|
||||
<Compile Include="ITorchConfig.cs" />
|
||||
<Compile Include="Managers\DependencyManagerExtensions.cs" />
|
||||
<Compile Include="Managers\DependencyProviderExtensions.cs" />
|
||||
<Compile Include="Managers\IDependencyManager.cs" />
|
||||
<Compile Include="Managers\IDependencyProvider.cs" />
|
||||
<Compile Include="Managers\IManager.cs" />
|
||||
<Compile Include="Managers\IMultiplayerManager.cs" />
|
||||
<Compile Include="IPlayer.cs" />
|
||||
<Compile Include="Managers\INetworkManager.cs" />
|
||||
<Compile Include="Managers\IPluginManager.cs" />
|
||||
<Compile Include="ITorchConfig.cs" />
|
||||
<Compile Include="Plugins\ITorchPlugin.cs" />
|
||||
<Compile Include="IServerControls.cs" />
|
||||
<Compile Include="ITorchBase.cs" />
|
||||
@@ -173,16 +180,15 @@
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ServerState.cs" />
|
||||
<Compile Include="ModAPI\TorchAPI.cs" />
|
||||
<Compile Include="Session\ITorchSession.cs" />
|
||||
<Compile Include="Session\ITorchSessionManager.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
</Project>
|
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NLog" version="4.4.1" targetFramework="net461" />
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
</packages>
|
@@ -1,12 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("Torch Server")]
|
||||
[assembly: AssemblyTitle("Torch Client Tests")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Torch")]
|
||||
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
#if DEBUG
|
||||
[assembly: AssemblyConfiguration("Debug")]
|
||||
#else
|
||||
[assembly: AssemblyConfiguration("Release")]
|
||||
#endif
|
96
Torch.Client.Tests/Torch.Client.Tests.csproj
Normal file
96
Torch.Client.Tests/Torch.Client.Tests.csproj
Normal file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{632E78C0-0DAC-4B71-B411-2F1B333CC310}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Torch.Client.Tests</RootNamespace>
|
||||
<AssemblyName>Torch.Client.Tests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<NoWarn>1591,0649</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>$(SolutionDir)\bin-test\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>$(SolutionDir)\bin-test\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<DocumentationFile>$(SolutionDir)\bin-test\x64\Release\Torch.Client.Tests.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<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>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.assert, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.core, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.execution.desktop, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||
<Link>Properties\AssemblyVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TorchClientReflectionTest.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
|
||||
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
|
||||
<Name>Torch.API</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Torch.Client\Torch.Client.csproj">
|
||||
<Project>{e36df745-260b-4956-a2e8-09f08b2e7161}</Project>
|
||||
<Name>Torch.Client</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Torch.Tests\Torch.Tests.csproj">
|
||||
<Project>{c3c8b671-6ad1-44aa-a8da-e0c0dc0fedf5}</Project>
|
||||
<Name>Torch.Tests</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Torch\Torch.csproj">
|
||||
<Project>{7e01635c-3b67-472e-bcd6-c5539564f214}</Project>
|
||||
<Name>Torch</Name>
|
||||
<Private>True</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
</Project>
|
67
Torch.Client.Tests/TorchClientReflectionTest.cs
Normal file
67
Torch.Client.Tests/TorchClientReflectionTest.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
using Torch.Client;
|
||||
using Torch.Tests;
|
||||
using Torch.Utils;
|
||||
using Xunit;
|
||||
|
||||
namespace Torch.Client.Tests
|
||||
{
|
||||
public class TorchClientReflectionTest
|
||||
{
|
||||
static TorchClientReflectionTest()
|
||||
{
|
||||
TestUtils.Init();
|
||||
}
|
||||
|
||||
private static ReflectionTestManager _manager;
|
||||
|
||||
private static ReflectionTestManager Manager()
|
||||
{
|
||||
if (_manager != null)
|
||||
return _manager;
|
||||
|
||||
return _manager = new ReflectionTestManager().Init(typeof(TorchClient).Assembly);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> Getters => Manager().Getters;
|
||||
|
||||
public static IEnumerable<object[]> Setters => Manager().Setters;
|
||||
|
||||
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
||||
|
||||
#region Binding
|
||||
[Theory]
|
||||
[MemberData(nameof(Getters))]
|
||||
public void TestBindingGetter(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Setters))]
|
||||
public void TestBindingSetter(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Invokers))]
|
||||
public void TestBindingInvoker(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
12
Torch.Client.Tests/packages.config
Normal file
12
Torch.Client.Tests/packages.config
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
<package id="xunit" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.abstractions" version="2.0.1" targetFramework="net461" />
|
||||
<package id="xunit.assert" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.core" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.extensibility.core" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.extensibility.execution" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.runner.console" version="2.2.0" targetFramework="net461" developmentDependency="true" />
|
||||
</packages>
|
@@ -1,17 +1,167 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using NLog;
|
||||
using Torch.Utils;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
|
||||
namespace Torch.Client
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public const string SpaceEngineersBinaries = "Bin64";
|
||||
private static string _spaceEngInstallAlias = null;
|
||||
public static string SpaceEngineersInstallAlias
|
||||
{
|
||||
get
|
||||
{
|
||||
// ReSharper disable once ConvertIfStatementToNullCoalescingExpression
|
||||
if (_spaceEngInstallAlias == null)
|
||||
{
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
_spaceEngInstallAlias = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "SpaceEngineersAlias");
|
||||
}
|
||||
return _spaceEngInstallAlias;
|
||||
}
|
||||
}
|
||||
|
||||
private const string _steamSpaceEngineersDirectory = @"steamapps\common\SpaceEngineers\";
|
||||
private const string _spaceEngineersVerifyFile = SpaceEngineersBinaries + @"\SpaceEngineers.exe";
|
||||
|
||||
public const string ConfigName = "Torch.cfg";
|
||||
|
||||
private static Logger _log = LogManager.GetLogger("Torch");
|
||||
|
||||
#if DEBUG
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern void AllocConsole();
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern void FreeConsole();
|
||||
#endif
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
#if DEBUG
|
||||
try
|
||||
{
|
||||
AllocConsole();
|
||||
#endif
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
|
||||
// Early config: Resolve SE install directory.
|
||||
if (!File.Exists(Path.Combine(SpaceEngineersInstallAlias, _spaceEngineersVerifyFile)))
|
||||
SetupSpaceEngInstallAlias();
|
||||
|
||||
using (new TorchAssemblyResolver(Path.Combine(SpaceEngineersInstallAlias, SpaceEngineersBinaries)))
|
||||
{
|
||||
RunClient();
|
||||
}
|
||||
#if DEBUG
|
||||
}
|
||||
finally
|
||||
{
|
||||
FreeConsole();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void SetupSpaceEngInstallAlias()
|
||||
{
|
||||
string spaceEngineersDirectory = null;
|
||||
|
||||
// TODO look at Steam/config/Config.VDF? Has alternate directories.
|
||||
var steamDir =
|
||||
Microsoft.Win32.Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\Valve\\Steam", "SteamPath", null) as string;
|
||||
if (steamDir != null)
|
||||
{
|
||||
spaceEngineersDirectory = Path.Combine(steamDir, _steamSpaceEngineersDirectory);
|
||||
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||
if (File.Exists(Path.Combine(spaceEngineersDirectory, _spaceEngineersVerifyFile)))
|
||||
_log.Debug("Found Space Engineers in {0}", spaceEngineersDirectory);
|
||||
else
|
||||
_log.Debug("Couldn't find Space Engineers in {0}", spaceEngineersDirectory);
|
||||
}
|
||||
if (spaceEngineersDirectory == null)
|
||||
{
|
||||
var dialog = new System.Windows.Forms.FolderBrowserDialog
|
||||
{
|
||||
Description = "Please select the SpaceEngineers installation folder"
|
||||
};
|
||||
do
|
||||
{
|
||||
if (dialog.ShowDialog() != DialogResult.OK)
|
||||
{
|
||||
var ex = new FileNotFoundException("Unable to find the Space Engineers install directory, aborting");
|
||||
_log.Fatal(ex);
|
||||
LogManager.Flush();
|
||||
throw ex;
|
||||
}
|
||||
spaceEngineersDirectory = dialog.SelectedPath;
|
||||
if (File.Exists(Path.Combine(spaceEngineersDirectory, _spaceEngineersVerifyFile)))
|
||||
break;
|
||||
if (MessageBox.Show(
|
||||
$"Unable to find {0} in {1}. Are you sure it's the Space Engineers install directory?",
|
||||
"Invalid Space Engineers Directory", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
break;
|
||||
} while (true); // Repeat until they confirm.
|
||||
}
|
||||
if (!JunctionLink(SpaceEngineersInstallAlias, spaceEngineersDirectory))
|
||||
{
|
||||
var ex = new IOException($"Failed to create junction link {SpaceEngineersInstallAlias} => {spaceEngineersDirectory}. Aborting.");
|
||||
_log.Fatal(ex);
|
||||
LogManager.Flush();
|
||||
throw ex;
|
||||
}
|
||||
string junctionVerify = Path.Combine(SpaceEngineersInstallAlias, _spaceEngineersVerifyFile);
|
||||
if (!File.Exists(junctionVerify))
|
||||
{
|
||||
var ex = new FileNotFoundException($"Junction link is not working. File {junctionVerify} does not exist");
|
||||
_log.Fatal(ex);
|
||||
LogManager.Flush();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool JunctionLink(string linkName, string targetDir)
|
||||
{
|
||||
var junctionLinkProc = new ProcessStartInfo("cmd.exe", $"/c mklink /J \"{linkName}\" \"{targetDir}\"")
|
||||
{
|
||||
WorkingDirectory = Directory.GetCurrentDirectory(),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
StandardOutputEncoding = Encoding.ASCII
|
||||
};
|
||||
Process cmd = Process.Start(junctionLinkProc);
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
while (!cmd.HasExited)
|
||||
{
|
||||
string line = cmd.StandardOutput.ReadLine();
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
_log.Info(line);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
if (cmd.ExitCode != 0)
|
||||
_log.Error("Unable to create junction link {0} => {1}", linkName, targetDir);
|
||||
return cmd.ExitCode == 0;
|
||||
}
|
||||
|
||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var ex = (Exception)e.ExceptionObject;
|
||||
_log.Error(ex);
|
||||
LogManager.Flush();
|
||||
MessageBox.Show(ex.StackTrace, ex.Message);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void RunClient()
|
||||
{
|
||||
var client = new TorchClient();
|
||||
|
||||
try
|
||||
@@ -27,11 +177,5 @@ namespace Torch.Client
|
||||
|
||||
client.Start();
|
||||
}
|
||||
|
||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var ex = (Exception)e.ExceptionObject;
|
||||
MessageBox.Show(ex.StackTrace, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.182.329")]
|
||||
[assembly: AssemblyFileVersion("1.0.182.329")]
|
||||
[assembly: AssemblyTitle("Torch Client")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Torch")]
|
||||
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
#if DEBUG
|
||||
[assembly: AssemblyConfiguration("Debug")]
|
||||
#else
|
||||
[assembly: AssemblyConfiguration("Release")]
|
||||
#endif
|
@@ -1,16 +0,0 @@
|
||||
<#@ template debug="false" hostspecific="false" language="C#" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#@ output extension=".cs" #>
|
||||
using System.Reflection;
|
||||
|
||||
<# var dt = DateTime.Now;
|
||||
int major = 1;
|
||||
int minor = 0;
|
||||
int build = dt.DayOfYear;
|
||||
int rev = (int)dt.TimeOfDay.TotalMinutes / 2;
|
||||
#>
|
||||
[assembly: AssemblyVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
||||
[assembly: AssemblyFileVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
@@ -2,8 +2,6 @@
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{E36DF745-260B-4956-A2E8-09F08B2E7161}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
@@ -15,10 +13,12 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
@@ -27,7 +27,7 @@
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
@@ -35,16 +35,21 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<DocumentationFile>bin\x64\Release\Torch.Client.xml</DocumentationFile>
|
||||
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.Client.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>torchicon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.4.1\lib\net45\NLog.dll</HintPath>
|
||||
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Sandbox.Game">
|
||||
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
@@ -60,6 +65,7 @@
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Core" />
|
||||
@@ -77,6 +83,10 @@
|
||||
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="VRage.Game.XmlSerializers">
|
||||
<HintPath>..\GameBinaries\VRage.Game.XmlSerializers.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="VRage.Input, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\GameBinaries\VRage.Input.dll</HintPath>
|
||||
@@ -99,18 +109,21 @@
|
||||
<HintPath>..\GameBinaries\VRage.Render11.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="VRage.Steam">
|
||||
<HintPath>..\GameBinaries\VRage.Steam.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<DependentUpon>AssemblyInfo.tt</DependentUpon>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||
<Link>Properties\AssemblyVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo1.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TorchClient.cs" />
|
||||
<Compile Include="TorchClientConfig.cs" />
|
||||
<Compile Include="TorchConsoleScreen.cs" />
|
||||
<Compile Include="TorchMainMenuScreen.cs" />
|
||||
<Compile Include="TorchSettingsScreen.cs" />
|
||||
@@ -140,35 +153,24 @@
|
||||
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
|
||||
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
|
||||
<Name>Torch.API</Name>
|
||||
<Private>True</Private>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Torch\Torch.csproj">
|
||||
<Project>{7E01635C-3B67-472E-BCD6-C5539564F214}</Project>
|
||||
<Name>Torch</Name>
|
||||
<Private>True</Private>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="torchicon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Properties\AssemblyInfo.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"</PostBuildEvent>
|
||||
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
@@ -1,18 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Engine.Platform;
|
||||
using Sandbox.Engine.Utils;
|
||||
using Sandbox.Game;
|
||||
using SpaceEngineers.Game;
|
||||
using VRage.Steam;
|
||||
using Torch.API;
|
||||
using VRage;
|
||||
using VRage.FileSystem;
|
||||
using VRage.GameServices;
|
||||
using VRageRender;
|
||||
using VRageRender.ExternalApp;
|
||||
|
||||
namespace Torch.Client
|
||||
{
|
||||
@@ -21,50 +23,50 @@ namespace Torch.Client
|
||||
private MyCommonProgramStartup _startup;
|
||||
private IMyRender _renderer;
|
||||
private const uint APP_ID = 244850;
|
||||
private VRageGameServices _services;
|
||||
|
||||
public TorchClient()
|
||||
{
|
||||
Config = new TorchClientConfig();
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Directory.SetCurrentDirectory(Program.SpaceEngineersInstallAlias);
|
||||
MyFileSystem.ExePath = Path.Combine(Program.SpaceEngineersInstallAlias, Program.SpaceEngineersBinaries);
|
||||
Log.Info("Initializing Torch Client");
|
||||
base.Init();
|
||||
|
||||
if (!File.Exists("steam_appid.txt"))
|
||||
{
|
||||
Directory.SetCurrentDirectory(Path.GetDirectoryName(typeof(VRage.FastResourceLock).Assembly.Location) + "\\..");
|
||||
}
|
||||
|
||||
SpaceEngineersGame.SetupBasicGameInfo();
|
||||
_startup = new MyCommonProgramStartup(RunArgs);
|
||||
if (_startup.PerformReporting())
|
||||
return;
|
||||
throw new InvalidOperationException("Torch client won't launch when started in error reporting mode");
|
||||
|
||||
_startup.PerformAutoconnect();
|
||||
if (!_startup.CheckSingleInstance())
|
||||
return;
|
||||
throw new InvalidOperationException("Only one instance of Space Engineers can be running at a time.");
|
||||
|
||||
var appDataPath = _startup.GetAppDataPath();
|
||||
MyInitializer.InvokeBeforeRun(APP_ID, MyPerGameSettings.BasicGameInfo.ApplicationName, appDataPath);
|
||||
MyInitializer.InitCheckSum();
|
||||
_startup.InitSplashScreen();
|
||||
if (!_startup.Check64Bit())
|
||||
return;
|
||||
throw new InvalidOperationException("Torch requires a 64bit operating system");
|
||||
|
||||
_startup.DetectSharpDxLeaksBeforeRun();
|
||||
using (var mySteamService = new SteamService(Game.IsDedicated, APP_ID))
|
||||
{
|
||||
_renderer = null;
|
||||
SpaceEngineersGame.SetupPerGameSettings();
|
||||
var steamService = new SteamService(Game.IsDedicated, APP_ID);
|
||||
MyServiceManager.Instance.AddService<IMyGameService>(steamService);
|
||||
_renderer = null;
|
||||
SpaceEngineersGame.SetupPerGameSettings();
|
||||
// I'm sorry, but it's what Keen does in SpaceEngineers.MyProgram
|
||||
#pragma warning disable 612
|
||||
SpaceEngineersGame.SetupRender();
|
||||
#pragma warning restore 612
|
||||
InitializeRender();
|
||||
if (!_startup.CheckSteamRunning())
|
||||
throw new InvalidOperationException("Space Engineers requires steam to be running");
|
||||
|
||||
OverrideMenus();
|
||||
|
||||
InitializeRender();
|
||||
|
||||
_services = new VRageGameServices(mySteamService);
|
||||
if (!Game.IsDedicated)
|
||||
MyFileSystem.InitUserSpecific(mySteamService.UserId.ToString());
|
||||
}
|
||||
|
||||
_startup.DetectSharpDxLeaksAfterRun();
|
||||
MyInitializer.InvokeAfterRun();
|
||||
if (!Game.IsDedicated)
|
||||
MyFileSystem.InitUserSpecific(MyGameService.UserId.ToString());
|
||||
}
|
||||
|
||||
private void OverrideMenus()
|
||||
@@ -84,16 +86,43 @@ namespace Torch.Client
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
using (var spaceEngineersGame = new SpaceEngineersGame(_services, RunArgs))
|
||||
using (var spaceEngineersGame = new SpaceEngineersGame(RunArgs))
|
||||
{
|
||||
Log.Info("Starting client");
|
||||
OverrideMenus();
|
||||
spaceEngineersGame.OnGameLoaded += SpaceEngineersGame_OnGameLoaded;
|
||||
spaceEngineersGame.Run();
|
||||
spaceEngineersGame.OnGameExit += Dispose;
|
||||
spaceEngineersGame.Run(false, _startup.DisposeSplashScreen);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetRenderWindowTitle(string title)
|
||||
{
|
||||
MyRenderThread renderThread = MySandboxGame.Static?.GameRenderComponent?.RenderThread;
|
||||
if (renderThread == null)
|
||||
return;
|
||||
FieldInfo renderWindowField = typeof(MyRenderThread).GetField("m_renderWindow",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (renderWindowField == null)
|
||||
return;
|
||||
var window = renderWindowField.GetValue(MySandboxGame.Static.GameRenderComponent.RenderThread) as System.Windows.Forms.Form;
|
||||
if (window != null)
|
||||
renderThread.Invoke(() =>
|
||||
{
|
||||
window.Text = title;
|
||||
});
|
||||
}
|
||||
|
||||
private void SpaceEngineersGame_OnGameLoaded(object sender, EventArgs e)
|
||||
{
|
||||
SetRenderWindowTitle($"Space Engineers v{GameVersion} with Torch v{TorchVersion}");
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
MyGameService.ShutDown();
|
||||
_startup.DetectSharpDxLeaksAfterRun();
|
||||
MyInitializer.InvokeAfterRun();
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
|
32
Torch.Client/TorchClientConfig.cs
Normal file
32
Torch.Client/TorchClientConfig.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.Client
|
||||
{
|
||||
public class TorchClientConfig : ITorchConfig
|
||||
{
|
||||
// How do we want to handle client side config? It's radically different than the server.
|
||||
public bool GetPluginUpdates { get; set; } = false;
|
||||
public bool GetTorchUpdates { get; set; } = false;
|
||||
public string InstanceName { get; set; } = "TorchClient";
|
||||
public string InstancePath { get; set; }
|
||||
public bool NoUpdate { get; set; } = true;
|
||||
public List<string> Plugins { get; set; }
|
||||
public bool ShouldUpdatePlugins { get; } = false;
|
||||
public bool ShouldUpdateTorch { get; } = false;
|
||||
public int TickTimeout { get; set; }
|
||||
public bool Autostart { get; set; } = false;
|
||||
public bool ForceUpdate { get; set; } = false;
|
||||
public bool NoGui { get; set; } = false;
|
||||
public bool RestartOnCrash { get; set; } = false;
|
||||
public string WaitForPID { get; set; } = null;
|
||||
|
||||
public bool Save(string path = null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NLog" version="4.4.1" targetFramework="net461" />
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
</packages>
|
17
Torch.Server.Tests/Properties/AssemblyInfo.cs
Normal file
17
Torch.Server.Tests/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("Torch Server Tests")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Torch")]
|
||||
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
#if DEBUG
|
||||
[assembly: AssemblyConfiguration("Debug")]
|
||||
#else
|
||||
[assembly: AssemblyConfiguration("Release")]
|
||||
#endif
|
93
Torch.Server.Tests/Torch.Server.Tests.csproj
Normal file
93
Torch.Server.Tests/Torch.Server.Tests.csproj
Normal file
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Torch.Server.Tests</RootNamespace>
|
||||
<AssemblyName>Torch.Server.Tests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<NoWarn>1591,0649</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>$(SolutionDir)\bin-test\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>$(SolutionDir)\bin-test\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<DocumentationFile>$(SolutionDir)\bin-test\x64\Release\Torch.Server.Tests.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<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>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.assert, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.core, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.execution.desktop, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||
<Link>Properties\AssemblyVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TorchServerReflectionTest.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
|
||||
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
|
||||
<Name>Torch.API</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Torch.Server\Torch.Server.csproj">
|
||||
<Project>{ca50886b-7b22-4cd8-93a0-c06f38d4f77d}</Project>
|
||||
<Name>Torch.Server</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Torch.Tests\Torch.Tests.csproj">
|
||||
<Project>{c3c8b671-6ad1-44aa-a8da-e0c0dc0fedf5}</Project>
|
||||
<Name>Torch.Tests</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Torch\Torch.csproj">
|
||||
<Project>{7e01635c-3b67-472e-bcd6-c5539564f214}</Project>
|
||||
<Name>Torch</Name>
|
||||
<Private>True</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
</Project>
|
66
Torch.Server.Tests/TorchServerReflectionTest.cs
Normal file
66
Torch.Server.Tests/TorchServerReflectionTest.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using Torch.Tests;
|
||||
using Torch.Utils;
|
||||
using Xunit;
|
||||
|
||||
namespace Torch.Server.Tests
|
||||
{
|
||||
public class TorchServerReflectionTest
|
||||
{
|
||||
static TorchServerReflectionTest()
|
||||
{
|
||||
TestUtils.Init();
|
||||
}
|
||||
|
||||
private static ReflectionTestManager _manager;
|
||||
|
||||
private static ReflectionTestManager Manager()
|
||||
{
|
||||
if (_manager != null)
|
||||
return _manager;
|
||||
|
||||
return _manager = new ReflectionTestManager().Init(typeof(TorchServer).Assembly);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> Getters => Manager().Getters;
|
||||
|
||||
public static IEnumerable<object[]> Setters => Manager().Setters;
|
||||
|
||||
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
||||
|
||||
#region Binding
|
||||
[Theory]
|
||||
[MemberData(nameof(Getters))]
|
||||
public void TestBindingGetter(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Setters))]
|
||||
public void TestBindingSetter(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Invokers))]
|
||||
public void TestBindingInvoker(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
12
Torch.Server.Tests/packages.config
Normal file
12
Torch.Server.Tests/packages.config
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
<package id="xunit" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.abstractions" version="2.0.1" targetFramework="net461" />
|
||||
<package id="xunit.assert" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.core" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.extensibility.core" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.extensibility.execution" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.runner.console" version="2.2.0" targetFramework="net461" developmentDependency="true" />
|
||||
</packages>
|
185
Torch.Server/Initializer.cs
Normal file
185
Torch.Server/Initializer.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using NLog;
|
||||
using Torch.Utils;
|
||||
|
||||
namespace Torch.Server
|
||||
{
|
||||
public class Initializer
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetLogger(nameof(Initializer));
|
||||
private bool _init;
|
||||
private const string STEAMCMD_DIR = "steamcmd";
|
||||
private const string STEAMCMD_ZIP = "temp.zip";
|
||||
private static readonly string STEAMCMD_PATH = $"{STEAMCMD_DIR}\\steamcmd.exe";
|
||||
private static readonly string RUNSCRIPT_PATH = $"{STEAMCMD_DIR}\\runscript.txt";
|
||||
private const string RUNSCRIPT = @"force_install_dir ../
|
||||
login anonymous
|
||||
app_update 298740
|
||||
quit";
|
||||
|
||||
private TorchAssemblyResolver _resolver;
|
||||
private TorchConfig _config;
|
||||
private TorchServer _server;
|
||||
private string _basePath;
|
||||
|
||||
public TorchConfig Config => _config;
|
||||
public TorchServer Server => _server;
|
||||
|
||||
public Initializer(string basePath)
|
||||
{
|
||||
_basePath = basePath;
|
||||
}
|
||||
|
||||
public bool Initialize(string[] args)
|
||||
{
|
||||
if (_init)
|
||||
return false;
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += HandleException;
|
||||
|
||||
if (!args.Contains("-noupdate"))
|
||||
RunSteamCmd();
|
||||
|
||||
_resolver = new TorchAssemblyResolver(Path.Combine(_basePath, "DedicatedServer64"));
|
||||
_config = InitConfig();
|
||||
if (!_config.Parse(args))
|
||||
return false;
|
||||
|
||||
if (!string.IsNullOrEmpty(_config.WaitForPID))
|
||||
{
|
||||
try
|
||||
{
|
||||
var pid = int.Parse(_config.WaitForPID);
|
||||
var waitProc = Process.GetProcessById(pid);
|
||||
Log.Info("Continuing in 5 seconds.");
|
||||
Thread.Sleep(5000);
|
||||
if (!waitProc.HasExited)
|
||||
{
|
||||
Log.Warn($"Killing old process {pid}.");
|
||||
waitProc.Kill();
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
_init = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
_server = new TorchServer(_config);
|
||||
_server.Init();
|
||||
|
||||
if (!_config.NoGui)
|
||||
{
|
||||
var ui = new TorchUI(_server);
|
||||
if (_config.Autostart)
|
||||
new Thread(_server.Start).Start();
|
||||
ui.ShowDialog();
|
||||
}
|
||||
else
|
||||
_server.Start();
|
||||
|
||||
_resolver?.Dispose();
|
||||
}
|
||||
|
||||
private TorchConfig InitConfig()
|
||||
{
|
||||
var configName = "Torch.cfg";
|
||||
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
|
||||
if (File.Exists(configName))
|
||||
{
|
||||
Log.Info($"Loading config {configPath}");
|
||||
return TorchConfig.LoadFrom(configPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info($"Generating default config at {configPath}");
|
||||
var config = new TorchConfig { InstancePath = Path.GetFullPath("Instance") };
|
||||
config.Save(configPath);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunSteamCmd()
|
||||
{
|
||||
var log = LogManager.GetLogger("SteamCMD");
|
||||
|
||||
if (!Directory.Exists(STEAMCMD_DIR))
|
||||
{
|
||||
Directory.CreateDirectory(STEAMCMD_DIR);
|
||||
}
|
||||
|
||||
if (!File.Exists(RUNSCRIPT_PATH))
|
||||
File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT);
|
||||
|
||||
if (!File.Exists(STEAMCMD_PATH))
|
||||
{
|
||||
try
|
||||
{
|
||||
log.Info("Downloading SteamCMD.");
|
||||
using (var client = new WebClient())
|
||||
client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP);
|
||||
|
||||
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR);
|
||||
File.Delete(STEAMCMD_ZIP);
|
||||
log.Info("SteamCMD downloaded successfully!");
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Error("Failed to download SteamCMD, unable to update the DS.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Checking for DS updates.");
|
||||
var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt")
|
||||
{
|
||||
WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), STEAMCMD_DIR),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
StandardOutputEncoding = Encoding.ASCII
|
||||
};
|
||||
var cmd = Process.Start(steamCmdProc);
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
while (!cmd.HasExited)
|
||||
{
|
||||
log.Info(cmd.StandardOutput.ReadLine());
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void HandleException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var ex = (Exception)e.ExceptionObject;
|
||||
Log.Fatal(ex);
|
||||
Console.WriteLine("Exiting in 5 seconds.");
|
||||
Thread.Sleep(5000);
|
||||
if (_config.RestartOnCrash)
|
||||
{
|
||||
var exe = typeof(Program).Assembly.Location;
|
||||
_config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
||||
Process.Start(exe, _config.ToString());
|
||||
}
|
||||
//1627 = Function failed during execution.
|
||||
Environment.Exit(1627);
|
||||
}
|
||||
}
|
||||
}
|
59
Torch.Server/ListBoxExtensions.cs
Normal file
59
Torch.Server/ListBoxExtensions.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Torch.Server
|
||||
{
|
||||
public static class ListBoxExtensions
|
||||
{
|
||||
//https://stackoverflow.com/questions/28689125/how-to-autoscroll-listbox-to-bottom-wpf-c
|
||||
public static void ScrollToItem(this ListBox listBox, int index)
|
||||
{
|
||||
// Find a container
|
||||
UIElement container = null;
|
||||
for (int i = index; i > 0; i--)
|
||||
{
|
||||
container = listBox.ItemContainerGenerator.ContainerFromIndex(i) as UIElement;
|
||||
if (container != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (container == null)
|
||||
return;
|
||||
|
||||
// Find the ScrollContentPresenter
|
||||
ScrollContentPresenter presenter = null;
|
||||
for (Visual vis = container; vis != null && vis != listBox; vis = VisualTreeHelper.GetParent(vis) as Visual)
|
||||
if ((presenter = vis as ScrollContentPresenter) != null)
|
||||
break;
|
||||
if (presenter == null)
|
||||
return;
|
||||
|
||||
// Find the IScrollInfo
|
||||
var scrollInfo =
|
||||
!presenter.CanContentScroll ? presenter :
|
||||
presenter.Content as IScrollInfo ??
|
||||
FirstVisualChild(presenter.Content as ItemsPresenter) as IScrollInfo ??
|
||||
presenter;
|
||||
|
||||
// Find the amount of items that is "Visible" in the ListBox
|
||||
var height = (container as ListBoxItem).ActualHeight;
|
||||
var lbHeight = listBox.ActualHeight;
|
||||
var showCount = (int)Math.Floor(lbHeight / height) - 1;
|
||||
|
||||
//Set the scrollbar
|
||||
if (scrollInfo.CanVerticallyScroll)
|
||||
scrollInfo.SetVerticalOffset(index - showCount);
|
||||
}
|
||||
|
||||
private static DependencyObject FirstVisualChild(Visual visual)
|
||||
{
|
||||
if (visual == null) return null;
|
||||
if (VisualTreeHelper.GetChildrenCount(visual) == 0) return null;
|
||||
return VisualTreeHelper.GetChild(visual, 0);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Sandbox.Engine.Utils;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Managers;
|
||||
using Torch.Server.ViewModels;
|
||||
using VRage.Game;
|
||||
|
||||
namespace Torch.Server.Managers
|
||||
{
|
||||
//TODO
|
||||
public class ConfigManager : Manager
|
||||
{
|
||||
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
|
||||
public ConfigDedicatedViewModel DedicatedConfig { get; set; }
|
||||
public TorchConfig TorchConfig { get; set; }
|
||||
|
||||
public ConfigManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
{
|
||||
LoadInstance(Torch.Config.InstancePath);
|
||||
}
|
||||
|
||||
public void LoadInstance(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
throw new FileNotFoundException($"Instance directory not found at '{path}'");
|
||||
|
||||
var configPath = Path.Combine(path, CONFIG_NAME);
|
||||
var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
|
||||
config.Load();
|
||||
DedicatedConfig = new ConfigDedicatedViewModel(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skeleton of a DS instance folder at the given directory.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
public void CreateInstance(string path)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
return;
|
||||
|
||||
Directory.CreateDirectory(path);
|
||||
var savesPath = Path.Combine(path, "Saves");
|
||||
Directory.CreateDirectory(savesPath);
|
||||
var modsPath = Path.Combine(path, "Mods");
|
||||
Directory.CreateDirectory(modsPath);
|
||||
LoadInstance(path);
|
||||
}
|
||||
}
|
||||
}
|
175
Torch.Server/Managers/InstanceManager.cs
Normal file
175
Torch.Server/Managers/InstanceManager.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Havok;
|
||||
using NLog;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Engine.Utils;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Managers;
|
||||
using Torch.Server.ViewModels;
|
||||
using VRage.FileSystem;
|
||||
using VRage.Game;
|
||||
using VRage.ObjectBuilders;
|
||||
|
||||
namespace Torch.Server.Managers
|
||||
{
|
||||
public class InstanceManager : Manager
|
||||
{
|
||||
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
|
||||
public ConfigDedicatedViewModel DedicatedConfig { get; set; }
|
||||
private static readonly Logger Log = LogManager.GetLogger(nameof(InstanceManager));
|
||||
[Dependency]
|
||||
private FilesystemManager _filesystemManager;
|
||||
|
||||
public InstanceManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Attach()
|
||||
{
|
||||
MyFileSystem.ExePath = Path.Combine(_filesystemManager.TorchDirectory, "DedicatedServer64");
|
||||
MyFileSystem.Init("Content", Torch.Config.InstancePath);
|
||||
//Initializes saves path. Why this isn't in Init() we may never know.
|
||||
MyFileSystem.InitUserSpecific(null);
|
||||
}
|
||||
|
||||
public void LoadInstance(string path, bool validate = true)
|
||||
{
|
||||
if (validate)
|
||||
ValidateInstance(path);
|
||||
|
||||
MyFileSystem.Reset();
|
||||
MyFileSystem.ExePath = Path.Combine(_filesystemManager.TorchDirectory, "DedicatedServer64");
|
||||
MyFileSystem.Init("Content", path);
|
||||
//Initializes saves path. Why this isn't in Init() we may never know.
|
||||
MyFileSystem.InitUserSpecific(null);
|
||||
|
||||
var configPath = Path.Combine(path, CONFIG_NAME);
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
Log.Error($"Failed to load dedicated config at {path}");
|
||||
return;
|
||||
}
|
||||
|
||||
var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
|
||||
config.Load(configPath);
|
||||
|
||||
DedicatedConfig = new ConfigDedicatedViewModel(config);
|
||||
var worldFolders = Directory.EnumerateDirectories(Path.Combine(Torch.Config.InstancePath, "Saves"));
|
||||
|
||||
foreach (var f in worldFolders)
|
||||
DedicatedConfig.WorldPaths.Add(f);
|
||||
|
||||
if (DedicatedConfig.WorldPaths.Count == 0)
|
||||
{
|
||||
Log.Warn($"No worlds found in the current instance {path}.");
|
||||
return;
|
||||
}
|
||||
|
||||
ImportWorldConfig();
|
||||
|
||||
/*
|
||||
if (string.IsNullOrEmpty(DedicatedConfig.LoadWorld))
|
||||
{
|
||||
Log.Warn("No world specified, importing first available world.");
|
||||
SelectWorld(DedicatedConfig.WorldPaths[0], false);
|
||||
}*/
|
||||
}
|
||||
|
||||
public void SelectWorld(string worldPath, bool modsOnly = true)
|
||||
{
|
||||
DedicatedConfig.LoadWorld = worldPath;
|
||||
ImportWorldConfig(modsOnly);
|
||||
}
|
||||
|
||||
|
||||
private void ImportWorldConfig(bool modsOnly = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(DedicatedConfig.LoadWorld))
|
||||
return;
|
||||
|
||||
var sandboxPath = Path.Combine(DedicatedConfig.LoadWorld, "Sandbox.sbc");
|
||||
|
||||
if (!File.Exists(sandboxPath))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
||||
if (checkpoint == null)
|
||||
{
|
||||
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
|
||||
return;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var mod in checkpoint.Mods)
|
||||
sb.AppendLine(mod.PublishedFileId.ToString());
|
||||
|
||||
DedicatedConfig.Mods = sb.ToString();
|
||||
|
||||
Log.Debug("Loaded mod list from world");
|
||||
|
||||
if (!modsOnly)
|
||||
DedicatedConfig.SessionSettings = new SessionSettingsViewModel(checkpoint.Settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Error loading mod list from world, verify that your mod list is accurate. '{DedicatedConfig.LoadWorld}'.");
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveConfig()
|
||||
{
|
||||
DedicatedConfig.Save(Path.Combine(Torch.Config.InstancePath, CONFIG_NAME));
|
||||
Log.Info("Saved dedicated config.");
|
||||
|
||||
try
|
||||
{
|
||||
MyObjectBuilderSerializer.DeserializeXML(Path.Combine(DedicatedConfig.LoadWorld, "Sandbox.sbc"), out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
||||
if (checkpoint == null)
|
||||
{
|
||||
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
|
||||
return;
|
||||
}
|
||||
checkpoint.Settings = DedicatedConfig.SessionSettings;
|
||||
checkpoint.Mods.Clear();
|
||||
foreach (var modId in DedicatedConfig.Model.Mods)
|
||||
checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId));
|
||||
|
||||
MyLocalCache.SaveCheckpoint(checkpoint, DedicatedConfig.LoadWorld);
|
||||
Log.Info("Saved world config.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Failed to write sandbox config, changes will not appear on server");
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the given path is a valid server instance.
|
||||
/// </summary>
|
||||
private void ValidateInstance(string path)
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(path, "Saves"));
|
||||
Directory.CreateDirectory(Path.Combine(path, "Mods"));
|
||||
var configPath = Path.Combine(path, CONFIG_NAME);
|
||||
if (File.Exists(configPath))
|
||||
return;
|
||||
|
||||
var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
|
||||
config.Save(configPath);
|
||||
}
|
||||
}
|
||||
}
|
@@ -23,7 +23,9 @@ using Torch.Server.Views;
|
||||
using VRage.Game.ModAPI;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Security.Policy;
|
||||
using Torch.Server.Managers;
|
||||
using Torch.Utils;
|
||||
using VRage.FileSystem;
|
||||
using VRageRender;
|
||||
|
||||
@@ -31,284 +33,32 @@ namespace Torch.Server
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
private static ITorchServer _server;
|
||||
private static Logger _log = LogManager.GetLogger("Torch");
|
||||
private static bool _restartOnCrash;
|
||||
public static bool IsManualInstall;
|
||||
private static TorchCli _cli;
|
||||
|
||||
/// <summary>
|
||||
/// <remarks>
|
||||
/// This method must *NOT* load any types/assemblies from the vanilla game, otherwise automatic updates will fail.
|
||||
/// </summary>
|
||||
/// </remarks>
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
//Ensures that all the files are downloaded in the Torch directory.
|
||||
Directory.SetCurrentDirectory(new FileInfo(typeof(Program).Assembly.Location).Directory.ToString());
|
||||
|
||||
IsManualInstall = File.Exists("SpaceEngineersDedicated.exe");
|
||||
if (!IsManualInstall)
|
||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
var workingDir = new FileInfo(typeof(Program).Assembly.Location).Directory.ToString();
|
||||
var binDir = Path.Combine(workingDir, "DedicatedServer64");
|
||||
Directory.SetCurrentDirectory(workingDir);
|
||||
|
||||
if (!Environment.UserInteractive)
|
||||
{
|
||||
using (var service = new TorchService())
|
||||
using (new TorchAssemblyResolver(binDir))
|
||||
{
|
||||
ServiceBase.Run(service);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var configName = "TorchConfig.xml";
|
||||
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
|
||||
TorchConfig options;
|
||||
if (File.Exists(configName))
|
||||
{
|
||||
_log.Info($"Loading config {configPath}");
|
||||
options = TorchConfig.LoadFrom(configPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info($"Generating default config at {configPath}");
|
||||
options = new TorchConfig();
|
||||
|
||||
if (!IsManualInstall)
|
||||
{
|
||||
//new ConfigManager().CreateInstance("Instance");
|
||||
options.InstancePath = Path.GetFullPath("Instance");
|
||||
|
||||
_log.Warn("Would you like to enable automatic updates? (Y/n):");
|
||||
|
||||
var input = Console.ReadLine() ?? "";
|
||||
var autoUpdate = !input.Equals("n", StringComparison.InvariantCultureIgnoreCase);
|
||||
options.AutomaticUpdates = autoUpdate;
|
||||
if (autoUpdate)
|
||||
{
|
||||
_log.Info("Automatic updates enabled, updating server.");
|
||||
RunSteamCmd();
|
||||
}
|
||||
}
|
||||
|
||||
//var setupDialog = new FirstTimeSetup { DataContext = options };
|
||||
//setupDialog.ShowDialog();
|
||||
options.Save(configPath);
|
||||
}
|
||||
|
||||
_cli = new TorchCli { Config = options };
|
||||
if (!_cli.Parse(args))
|
||||
var initializer = new Initializer(workingDir);
|
||||
if (!initializer.Initialize(args))
|
||||
return;
|
||||
|
||||
_log.Debug(_cli.ToString());
|
||||
|
||||
if (!string.IsNullOrEmpty(_cli.WaitForPID))
|
||||
{
|
||||
try
|
||||
{
|
||||
var pid = int.Parse(_cli.WaitForPID);
|
||||
var waitProc = Process.GetProcessById(pid);
|
||||
_log.Warn($"Waiting for process {pid} to exit.");
|
||||
waitProc.WaitForExit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
_restartOnCrash = _cli.RestartOnCrash;
|
||||
|
||||
if (options.AutomaticUpdates || _cli.Update)
|
||||
{
|
||||
if (IsManualInstall)
|
||||
_log.Warn("Detected manual install, won't attempt to update DS");
|
||||
else
|
||||
{
|
||||
RunSteamCmd();
|
||||
}
|
||||
}
|
||||
RunServer(options, _cli);
|
||||
}
|
||||
|
||||
private const string STEAMCMD_DIR = "steamcmd";
|
||||
private const string STEAMCMD_ZIP = "temp.zip";
|
||||
private static readonly string STEAMCMD_PATH = $"{STEAMCMD_DIR}\\steamcmd.exe";
|
||||
private static readonly string RUNSCRIPT_PATH = $"{STEAMCMD_DIR}\\runscript.txt";
|
||||
private const string RUNSCRIPT = @"force_install_dir ../
|
||||
login anonymous
|
||||
app_update 298740
|
||||
quit";
|
||||
|
||||
public static void RunSteamCmd()
|
||||
{
|
||||
var log = LogManager.GetLogger("SteamCMD");
|
||||
|
||||
if (!Directory.Exists(STEAMCMD_DIR))
|
||||
{
|
||||
Directory.CreateDirectory(STEAMCMD_DIR);
|
||||
}
|
||||
|
||||
if (!File.Exists(RUNSCRIPT_PATH))
|
||||
File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT);
|
||||
|
||||
if (!File.Exists(STEAMCMD_PATH))
|
||||
{
|
||||
try
|
||||
{
|
||||
log.Info("Downloading SteamCMD.");
|
||||
using (var client = new WebClient())
|
||||
client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP);
|
||||
|
||||
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR);
|
||||
File.Delete(STEAMCMD_ZIP);
|
||||
log.Info("SteamCMD downloaded successfully!");
|
||||
}
|
||||
catch
|
||||
{
|
||||
log.Error("Failed to download SteamCMD, unable to update the DS.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Checking for DS updates.");
|
||||
var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt")
|
||||
{
|
||||
WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), STEAMCMD_DIR),
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
StandardOutputEncoding = Encoding.ASCII
|
||||
};
|
||||
var cmd = Process.Start(steamCmdProc);
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
while (!cmd.HasExited)
|
||||
{
|
||||
log.Info(cmd.StandardOutput.ReadLine());
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RunServer(TorchConfig options, TorchCli cli)
|
||||
{
|
||||
|
||||
|
||||
/*
|
||||
if (!parser.ParseArguments(args, options))
|
||||
{
|
||||
_log.Error($"Parsing arguments failed: {string.Join(" ", args)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(options.Config) && File.Exists(options.Config))
|
||||
{
|
||||
options = ServerConfig.LoadFrom(options.Config);
|
||||
parser.ParseArguments(args, options);
|
||||
}*/
|
||||
|
||||
//RestartOnCrash autostart autosave=15
|
||||
//gamepath ="C:\Program Files\Space Engineers DS" instance="Hydro Survival" instancepath="C:\ProgramData\SpaceEngineersDedicated\Hydro Survival"
|
||||
|
||||
/*
|
||||
if (options.InstallService)
|
||||
{
|
||||
var serviceName = $"\"Torch - {options.InstanceName}\"";
|
||||
// Working on installing the service properly instead of with sc.exe
|
||||
_log.Info($"Installing service '{serviceName}");
|
||||
var exePath = $"\"{Assembly.GetExecutingAssembly().Location}\"";
|
||||
var createInfo = new ServiceCreateInfo
|
||||
{
|
||||
Name = options.InstanceName,
|
||||
BinaryPath = exePath,
|
||||
};
|
||||
_log.Info("Service Installed");
|
||||
|
||||
var runArgs = string.Join(" ", args.Skip(1));
|
||||
_log.Info($"Installing Torch as a service with arguments '{runArgs}'");
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "sc.exe",
|
||||
Arguments = $"create Torch binPath=\"{Assembly.GetExecutingAssembly().Location} {runArgs}\"",
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = true,
|
||||
Verb = "runas"
|
||||
};
|
||||
Process.Start(startInfo).WaitForExit();
|
||||
_log.Info("Torch service installed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.UninstallService)
|
||||
{
|
||||
_log.Info("Uninstalling Torch service");
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "sc.exe",
|
||||
Arguments = "delete Torch",
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = true,
|
||||
Verb = "runas"
|
||||
};
|
||||
Process.Start(startInfo).WaitForExit();
|
||||
_log.Info("Torch service uninstalled");
|
||||
return;
|
||||
}*/
|
||||
|
||||
_server = new TorchServer(options);
|
||||
_server.Init();
|
||||
|
||||
if (cli.NoGui || cli.Autostart)
|
||||
{
|
||||
new Thread(() => _server.Start()).Start();
|
||||
}
|
||||
|
||||
if (!cli.NoGui)
|
||||
{
|
||||
var ui = new TorchUI((TorchServer)_server);
|
||||
ui.LoadConfig(options);
|
||||
ui.ShowDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var basePath = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "DedicatedServer64");
|
||||
string asmPath = Path.Combine(basePath, new AssemblyName(args.Name).Name + ".dll");
|
||||
if (File.Exists(asmPath))
|
||||
return Assembly.LoadFrom(asmPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var ex = (Exception)e.ExceptionObject;
|
||||
_log.Fatal(ex);
|
||||
Console.WriteLine("Exiting in 5 seconds.");
|
||||
Thread.Sleep(5000);
|
||||
if (_restartOnCrash)
|
||||
{
|
||||
/* Throws an exception somehow and I'm too lazy to debug it.
|
||||
try
|
||||
{
|
||||
if (MySession.Static != null && MySession.Static.AutoSaveInMinutes > 0)
|
||||
MySession.Static.Save();
|
||||
}
|
||||
catch { }*/
|
||||
|
||||
var exe = typeof(Program).Assembly.Location;
|
||||
_cli.WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
||||
Process.Start(exe, _cli.ToString());
|
||||
}
|
||||
//1627 = Function failed during execution.
|
||||
Environment.Exit(1627);
|
||||
initializer.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.182.329")]
|
||||
[assembly: AssemblyFileVersion("1.0.182.329")]
|
||||
[assembly: AssemblyTitle("Torch Server")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Torch")]
|
||||
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
#if DEBUG
|
||||
[assembly: AssemblyConfiguration("Debug")]
|
||||
#else
|
||||
[assembly: AssemblyConfiguration("Release")]
|
||||
#endif
|
@@ -1,16 +0,0 @@
|
||||
<#@ template debug="false" hostspecific="false" language="C#" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#@ output extension=".cs" #>
|
||||
using System.Reflection;
|
||||
|
||||
<# var dt = DateTime.Now;
|
||||
int major = 1;
|
||||
int minor = 0;
|
||||
int build = dt.DayOfYear;
|
||||
int rev = (int)dt.TimeOfDay.TotalMinutes / 2;
|
||||
#>
|
||||
[assembly: AssemblyVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
||||
[assembly: AssemblyFileVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
@@ -2,8 +2,6 @@
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
@@ -15,10 +13,12 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
@@ -27,7 +27,7 @@
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
@@ -35,7 +35,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<DocumentationFile>bin\x64\Release\Torch.Server.xml</DocumentationFile>
|
||||
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.Server.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>Torch.Server.Program</StartupObject>
|
||||
@@ -59,8 +59,11 @@
|
||||
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.4.1\lib\net45\NLog.dll</HintPath>
|
||||
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
@@ -122,6 +125,7 @@
|
||||
<Reference Include="VRage.Audio, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\GameBinaries\VRage.Audio.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="VRage.Dedicated, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
@@ -183,15 +187,14 @@
|
||||
<Reference Include="PresentationFramework" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Managers\ConfigManager.cs" />
|
||||
<Compile Include="TorchCli.cs" />
|
||||
<Compile Include="NativeMethods.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>AssemblyInfo.tt</DependentUpon>
|
||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||
<Link>Properties\AssemblyVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo1.cs" />
|
||||
<Compile Include="ListBoxExtensions.cs" />
|
||||
<Compile Include="Managers\InstanceManager.cs" />
|
||||
<Compile Include="NativeMethods.cs" />
|
||||
<Compile Include="Initializer.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ServerStatistics.cs" />
|
||||
<Compile Include="TorchConfig.cs" />
|
||||
<Compile Include="TorchService.cs">
|
||||
@@ -288,12 +291,12 @@
|
||||
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
|
||||
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
|
||||
<Name>Torch.API</Name>
|
||||
<Private>True</Private>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Torch\Torch.csproj">
|
||||
<Project>{7e01635c-3b67-472e-bcd6-c5539564f214}</Project>
|
||||
<Name>Torch</Name>
|
||||
<Private>True</Private>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -356,22 +359,10 @@
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Properties\AssemblyInfo.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.Server
|
||||
{
|
||||
public class TorchCli : CommandLine
|
||||
{
|
||||
public TorchConfig Config { get; set; }
|
||||
|
||||
[Arg("instancepath", "Server data folder where saves and mods are stored.")]
|
||||
public string InstancePath { get => Config.InstancePath; set => Config.InstancePath = value; }
|
||||
|
||||
[Arg("noupdate", "Disable automatically downloading game and plugin updates.")]
|
||||
public bool NoUpdate { get => !Config.AutomaticUpdates; set => Config.AutomaticUpdates = !value; }
|
||||
|
||||
[Arg("update", "Manually check for and install updates.")]
|
||||
public bool Update { get; set; }
|
||||
|
||||
//TODO: backend code for this
|
||||
//[Arg("worldpath", "Path to the game world folder to load.")]
|
||||
public string WorldPath { get; set; }
|
||||
|
||||
[Arg("autostart", "Start the server immediately.")]
|
||||
public bool Autostart { get; set; }
|
||||
|
||||
[Arg("restartoncrash", "Automatically restart the server if it crashes.")]
|
||||
public bool RestartOnCrash { get => Config.RestartOnCrash; set => Config.RestartOnCrash = value; }
|
||||
|
||||
[Arg("nogui", "Do not show the Torch UI.")]
|
||||
public bool NoGui { get; set; }
|
||||
|
||||
[Arg("silent", "Do not show the Torch UI or the command line.")]
|
||||
public bool Silent { get; set; }
|
||||
|
||||
[Arg("waitforpid", "Makes Torch wait for another process to exit.")]
|
||||
public string WaitForPID { get; set; }
|
||||
}
|
||||
}
|
@@ -3,56 +3,87 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Xml.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
|
||||
namespace Torch.Server
|
||||
{
|
||||
public class TorchConfig : ITorchConfig
|
||||
// TODO: redesign this gerbage
|
||||
public class TorchConfig : CommandLine, ITorchConfig
|
||||
{
|
||||
private static Logger _log = LogManager.GetLogger("Config");
|
||||
|
||||
public string InstancePath { get; set; }
|
||||
public bool ShouldUpdatePlugins => (GetPluginUpdates && !NoUpdate) || ForceUpdate;
|
||||
public bool ShouldUpdateTorch => (GetTorchUpdates && !NoUpdate) || ForceUpdate;
|
||||
|
||||
/// <inheritdoc />
|
||||
[Arg("instancename", "The name of the Torch instance.")]
|
||||
public string InstanceName { get; set; }
|
||||
#warning World Path not implemented
|
||||
public string WorldPath { get; set; }
|
||||
public bool AutomaticUpdates { get; set; } = true;
|
||||
public bool RedownloadPlugins { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Arg("instancepath", "Server data folder where saves and mods are stored.")]
|
||||
public string InstancePath { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[XmlIgnore, Arg("noupdate", "Disable automatically downloading game and plugin updates.")]
|
||||
public bool NoUpdate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[XmlIgnore, Arg("forceupdate", "Manually check for and install updates.")]
|
||||
public bool ForceUpdate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Arg("autostart", "Start the server immediately.")]
|
||||
public bool Autostart { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Arg("restartoncrash", "Automatically restart the server if it crashes.")]
|
||||
public bool RestartOnCrash { get; set; }
|
||||
/// <summary>
|
||||
/// How long in seconds to wait before automatically resetting a frozen server.
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc />
|
||||
[Arg("nogui", "Do not show the Torch UI.")]
|
||||
public bool NoGui { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[XmlIgnore, Arg("waitforpid", "Makes Torch wait for another process to exit.")]
|
||||
public string WaitForPID { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetTorchUpdates { get; set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetPluginUpdates { get; set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int TickTimeout { get; set; } = 60;
|
||||
/// <summary>
|
||||
/// A list of plugins to install or update. TODO
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<string> Plugins { get; set; } = new List<string>();
|
||||
|
||||
internal Point WindowSize { get; set; } = new Point(800, 600);
|
||||
internal Point WindowPosition { get; set; } = new Point();
|
||||
[NonSerialized]
|
||||
[XmlIgnore]
|
||||
private string _path;
|
||||
|
||||
public TorchConfig() : this("Torch") { }
|
||||
|
||||
public TorchConfig(string instanceName = "Torch", string instancePath = null, int autosaveInterval = 5, bool autoRestart = false)
|
||||
public TorchConfig(string instanceName = "Torch", string instancePath = null)
|
||||
{
|
||||
InstanceName = instanceName;
|
||||
InstancePath = instancePath ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SpaceEngineersDedicated");
|
||||
//Autosave = autosaveInterval;
|
||||
//AutoRestart = autoRestart;
|
||||
}
|
||||
|
||||
public static TorchConfig LoadFrom(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var serializer = new XmlSerializer(typeof(TorchConfig));
|
||||
TorchConfig config;
|
||||
var ser = new XmlSerializer(typeof(TorchConfig));
|
||||
using (var f = File.OpenRead(path))
|
||||
{
|
||||
config = (TorchConfig)serializer.Deserialize(f);
|
||||
var config = (TorchConfig)ser.Deserialize(f);
|
||||
config._path = path;
|
||||
return config;
|
||||
}
|
||||
config._path = path;
|
||||
return config;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -70,11 +101,9 @@ namespace Torch.Server
|
||||
|
||||
try
|
||||
{
|
||||
var serializer = new XmlSerializer(typeof(TorchConfig));
|
||||
var ser = new XmlSerializer(typeof(TorchConfig));
|
||||
using (var f = File.Create(path))
|
||||
{
|
||||
serializer.Serialize(f, this);
|
||||
}
|
||||
ser.Serialize(f, this);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@@ -10,21 +10,27 @@ using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Xml.Serialization.GeneratedAssembly;
|
||||
using Sandbox.Engine.Analytics;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
using Sandbox.ModAPI;
|
||||
using SteamSDK;
|
||||
using Torch.API;
|
||||
using Torch.Managers;
|
||||
using Torch.Server.Managers;
|
||||
using Torch.Utils;
|
||||
using VRage.Dedicated;
|
||||
using VRage.FileSystem;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
using VRage.Game.ObjectBuilder;
|
||||
using VRage.Game.SessionComponents;
|
||||
using VRage.Library;
|
||||
using VRage.ObjectBuilders;
|
||||
using VRage.Plugins;
|
||||
using VRage.Utils;
|
||||
|
||||
#pragma warning disable 618
|
||||
|
||||
namespace Torch.Server
|
||||
@@ -37,7 +43,10 @@ namespace Torch.Server
|
||||
public Thread GameThread { get; private set; }
|
||||
public ServerState State { get => _state; private set { _state = value; OnPropertyChanged(); } }
|
||||
public bool IsRunning { get => _isRunning; set { _isRunning = value; OnPropertyChanged(); } }
|
||||
public InstanceManager DedicatedInstance { get; }
|
||||
/// <inheritdoc />
|
||||
public string InstanceName => Config?.InstanceName;
|
||||
/// <inheritdoc />
|
||||
public string InstancePath => Config?.InstancePath;
|
||||
|
||||
private bool _isRunning;
|
||||
@@ -46,18 +55,21 @@ namespace Torch.Server
|
||||
private float _simRatio;
|
||||
private readonly AutoResetEvent _stopHandle = new AutoResetEvent(false);
|
||||
private Timer _watchdog;
|
||||
private Stopwatch _uptime;
|
||||
|
||||
public TorchServer(TorchConfig config = null)
|
||||
{
|
||||
DedicatedInstance = new InstanceManager(this);
|
||||
AddManager(DedicatedInstance);
|
||||
Config = config ?? new TorchConfig();
|
||||
MyFakes.ENABLE_INFINARIO = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'");
|
||||
base.Init();
|
||||
|
||||
MyPerGameSettings.SendLogToKeen = false;
|
||||
MyPerServerSettings.GameName = MyPerGameSettings.GameName;
|
||||
@@ -67,43 +79,22 @@ namespace Torch.Server
|
||||
MySessionComponentExtDebug.ForceDisable = true;
|
||||
MyPerServerSettings.AppId = 244850;
|
||||
MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion;
|
||||
|
||||
MyObjectBuilderSerializer.RegisterFromAssembly(typeof(MyObjectBuilder_CheckpointSerializer).Assembly);
|
||||
|
||||
InvokeBeforeRun();
|
||||
|
||||
//MyObjectBuilderSerializer.RegisterFromAssembly(typeof(MyObjectBuilder_CheckpointSerializer).Assembly);
|
||||
MyPlugins.RegisterGameAssemblyFile(MyPerGameSettings.GameModAssembly);
|
||||
MyPlugins.RegisterGameObjectBuildersAssemblyFile(MyPerGameSettings.GameModObjBuildersAssembly);
|
||||
MyPlugins.RegisterSandboxAssemblyFile(MyPerGameSettings.SandboxAssembly);
|
||||
MyPlugins.RegisterSandboxGameAssemblyFile(MyPerGameSettings.SandboxGameAssembly);
|
||||
MyPlugins.Load();
|
||||
MyGlobalTypeMetadata.Static.Init();
|
||||
RuntimeHelpers.RunClassConstructor(typeof(MyObjectBuilder_Base).TypeHandle);
|
||||
|
||||
GetManager<InstanceManager>().LoadInstance(Config.InstancePath);
|
||||
Plugins.LoadPlugins();
|
||||
}
|
||||
|
||||
public void InvokeBeforeRun()
|
||||
private void InvokeBeforeRun()
|
||||
{
|
||||
|
||||
var contentPath = "Content";
|
||||
|
||||
var privateContentPath = typeof(MyFileSystem).GetField("m_contentPath", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as string;
|
||||
if (privateContentPath != null)
|
||||
Log.Debug("MyFileSystem already initialized");
|
||||
else
|
||||
{
|
||||
if (Program.IsManualInstall)
|
||||
{
|
||||
var rootPath = new FileInfo(MyFileSystem.ExePath).Directory.FullName;
|
||||
contentPath = Path.Combine(rootPath, "Content");
|
||||
}
|
||||
else
|
||||
{
|
||||
MyFileSystem.ExePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DedicatedServer64");
|
||||
}
|
||||
|
||||
MyFileSystem.Init(contentPath, InstancePath);
|
||||
}
|
||||
|
||||
MySandboxGame.Log.Init("SpaceEngineers-Dedicated.log", MyFinalBuildConstants.APP_VERSION_STRING);
|
||||
MySandboxGame.Log.WriteLine("Steam build: Always true");
|
||||
MySandboxGame.Log.WriteLine("Environment.ProcessorCount: " + MyEnvironment.ProcessorCount);
|
||||
@@ -132,28 +123,39 @@ namespace Torch.Server
|
||||
MySandboxGame.Config.Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start server on the current thread.
|
||||
/// </summary>
|
||||
[ReflectedStaticMethod(Type = typeof(DedicatedServer), Name = "RunInternal")]
|
||||
private static Action _dsRunInternal;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Start()
|
||||
{
|
||||
if (State != ServerState.Stopped)
|
||||
return;
|
||||
|
||||
DedicatedInstance.SaveConfig();
|
||||
_uptime = Stopwatch.StartNew();
|
||||
IsRunning = true;
|
||||
GameThread = Thread.CurrentThread;
|
||||
Config.Save();
|
||||
State = ServerState.Starting;
|
||||
Log.Info("Starting server.");
|
||||
|
||||
var runInternal = typeof(DedicatedServer).GetMethod("RunInternal", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
|
||||
MySandboxGame.IsDedicated = true;
|
||||
Environment.SetEnvironmentVariable("SteamAppId", MyPerServerSettings.AppId.ToString());
|
||||
|
||||
VRage.Service.ExitListenerSTA.OnExit += delegate { MySandboxGame.Static?.Exit(); };
|
||||
|
||||
base.Start();
|
||||
runInternal.Invoke(null, null);
|
||||
// Stops RunInternal from calling MyFileSystem.InitUserSpecific(null), we call it in InstanceManager.
|
||||
MySandboxGame.IsReloading = true;
|
||||
try
|
||||
{
|
||||
_dsRunInternal.Invoke();
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
// Makes log formatting a little nicer.
|
||||
throw e.InnerException ?? e;
|
||||
}
|
||||
|
||||
MySandboxGame.Log.Close();
|
||||
State = ServerState.Stopped;
|
||||
@@ -172,12 +174,13 @@ namespace Torch.Server
|
||||
{
|
||||
base.Update();
|
||||
SimulationRatio = Sync.ServerSimulationRatio;
|
||||
ElapsedPlayTime = MySession.Static?.ElapsedPlayTime ?? default(TimeSpan);
|
||||
var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds));
|
||||
ElapsedPlayTime = elapsed;
|
||||
|
||||
if (_watchdog == null && Instance.Config.TickTimeout > 0)
|
||||
if (_watchdog == null && Config.TickTimeout > 0)
|
||||
{
|
||||
Log.Info("Starting server watchdog.");
|
||||
_watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero, TimeSpan.FromSeconds(Instance.Config.TickTimeout));
|
||||
_watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero, TimeSpan.FromSeconds(Config.TickTimeout));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,17 +191,16 @@ namespace Torch.Server
|
||||
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
|
||||
{
|
||||
var mainThread = MySandboxGame.Static.UpdateThread;
|
||||
mainThread.Suspend();
|
||||
if (mainThread.IsAlive)
|
||||
mainThread.Suspend();
|
||||
var stackTrace = new StackTrace(mainThread, true);
|
||||
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {Instance.Config.TickTimeout} seconds.\n{stackTrace}");
|
||||
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.\n{stackTrace}");
|
||||
}
|
||||
|
||||
Log.Debug("Server watchdog responded");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the server.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override void Stop()
|
||||
{
|
||||
if (State == ServerState.Stopped)
|
||||
@@ -220,6 +222,54 @@ namespace Torch.Server
|
||||
Log.Info("Server stopped.");
|
||||
_stopHandle.Set();
|
||||
State = ServerState.Stopped;
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restart the program. DOES NOT SAVE!
|
||||
/// </summary>
|
||||
public override void Restart()
|
||||
{
|
||||
var exe = Assembly.GetExecutingAssembly().Location;
|
||||
((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
||||
Process.Start(exe, Config.ToString());
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task Save(long callerId)
|
||||
{
|
||||
return SaveGameAsync(statusCode => SaveCompleted(statusCode, callerId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback for when save has finished.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">Return code of the save operation</param>
|
||||
/// <param name="callerId">Caller of the save operation</param>
|
||||
private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0)
|
||||
{
|
||||
switch (statusCode)
|
||||
{
|
||||
case SaveGameStatus.Success:
|
||||
Log.Info("Save completed.");
|
||||
Multiplayer.SendMessage("Saved game.", playerId: callerId);
|
||||
break;
|
||||
case SaveGameStatus.SaveInProgress:
|
||||
Log.Error("Save failed, a save is already in progress.");
|
||||
Multiplayer.SendMessage("Save failed, a save is already in progress.", playerId: callerId, font: MyFontEnum.Red);
|
||||
break;
|
||||
case SaveGameStatus.GameNotReady:
|
||||
Log.Error("Save failed, game was not ready.");
|
||||
Multiplayer.SendMessage("Save failed, game was not ready.", playerId: callerId, font: MyFontEnum.Red);
|
||||
break;
|
||||
case SaveGameStatus.TimedOut:
|
||||
Log.Error("Save failed, save timed out.");
|
||||
Multiplayer.SendMessage("Save failed, save timed out.", playerId: callerId, font: MyFontEnum.Red);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,13 +14,15 @@ namespace Torch.Server
|
||||
{
|
||||
public const string Name = "Torch (SEDS)";
|
||||
private TorchServer _server;
|
||||
private static Logger _log = LogManager.GetLogger("Torch");
|
||||
private Initializer _initializer;
|
||||
|
||||
public TorchService()
|
||||
{
|
||||
ServiceName = Name;
|
||||
var workingDir = new FileInfo(typeof(TorchService).Assembly.Location).Directory.ToString();
|
||||
Directory.SetCurrentDirectory(workingDir);
|
||||
_initializer = new Initializer(workingDir);
|
||||
|
||||
CanHandlePowerEvent = true;
|
||||
ServiceName = Name;
|
||||
CanHandleSessionChangeEvent = false;
|
||||
CanPauseAndContinue = false;
|
||||
CanStop = true;
|
||||
@@ -31,17 +33,8 @@ namespace Torch.Server
|
||||
{
|
||||
base.OnStart(args);
|
||||
|
||||
string configName = args.Length > 0 ? args[0] : "TorchConfig.xml";
|
||||
var options = new TorchConfig("Torch");
|
||||
if (File.Exists(configName))
|
||||
options = TorchConfig.LoadFrom(configName);
|
||||
else
|
||||
options.Save(configName);
|
||||
|
||||
_server = new TorchServer(options);
|
||||
_server.Init();
|
||||
_server.RunArgs = args;
|
||||
Task.Run(() => _server.Start());
|
||||
_initializer.Initialize(args);
|
||||
_initializer.Run();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -50,17 +43,5 @@ namespace Torch.Server
|
||||
_server.Stop();
|
||||
base.OnStop();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnShutdown()
|
||||
{
|
||||
base.OnShutdown();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
|
||||
{
|
||||
return base.OnPowerEvent(powerStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ namespace Torch.Server.ViewModels
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetLogger("Config");
|
||||
private MyConfigDedicated<MyObjectBuilder_SessionSettings> _config;
|
||||
public MyConfigDedicated<MyObjectBuilder_SessionSettings> Model => _config;
|
||||
|
||||
public ConfigDedicatedViewModel() : this(new MyConfigDedicated<MyObjectBuilder_SessionSettings>(""))
|
||||
{
|
||||
@@ -54,9 +55,10 @@ namespace Torch.Server.ViewModels
|
||||
_config.Save(path);
|
||||
}
|
||||
|
||||
public SessionSettingsViewModel SessionSettings { get; }
|
||||
private SessionSettingsViewModel _sessionSettings;
|
||||
public SessionSettingsViewModel SessionSettings { get => _sessionSettings; set { _sessionSettings = value; OnPropertyChanged(); } }
|
||||
|
||||
public ObservableCollection<string> WorldPaths { get; } = new ObservableCollection<string>();
|
||||
public ObservableList<string> WorldPaths { get; } = new ObservableList<string>();
|
||||
private string _administrators;
|
||||
public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } }
|
||||
private string _banned;
|
||||
|
@@ -15,7 +15,7 @@ namespace Torch.Server.ViewModels.Blocks
|
||||
public class BlockViewModel : EntityViewModel
|
||||
{
|
||||
public IMyTerminalBlock Block { get; }
|
||||
public MTObservableCollection<PropertyViewModel> Properties { get; } = new MTObservableCollection<PropertyViewModel>();
|
||||
public ObservableList<PropertyViewModel> Properties { get; } = new ObservableList<PropertyViewModel>();
|
||||
|
||||
public string FullName => $"{Block.CubeGrid.CustomName} - {Block.CustomName}";
|
||||
|
||||
@@ -24,8 +24,11 @@ namespace Torch.Server.ViewModels.Blocks
|
||||
get => Block?.CustomName ?? "null";
|
||||
set
|
||||
{
|
||||
TorchBase.Instance.InvokeBlocking(() => Block.CustomName = value);
|
||||
OnPropertyChanged();
|
||||
TorchBase.Instance.Invoke(() =>
|
||||
{
|
||||
Block.CustomName = value;
|
||||
OnPropertyChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,13 +40,22 @@ namespace Torch.Server.ViewModels.Blocks
|
||||
get => ((MySlimBlock)Block.SlimBlock).BuiltBy;
|
||||
set
|
||||
{
|
||||
TorchBase.Instance.InvokeBlocking(() => ((MySlimBlock)Block.SlimBlock).TransferAuthorship(value));
|
||||
OnPropertyChanged();
|
||||
TorchBase.Instance.Invoke(() =>
|
||||
{
|
||||
((MySlimBlock)Block.SlimBlock).TransferAuthorship(value);
|
||||
OnPropertyChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanStop => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Delete()
|
||||
{
|
||||
Block.CubeGrid.RazeBlock(Block.Position);
|
||||
}
|
||||
|
||||
public BlockViewModel(IMyTerminalBlock block, EntityTreeViewModel tree) : base(block, tree)
|
||||
{
|
||||
Block = block;
|
||||
|
@@ -16,17 +16,15 @@ namespace Torch.Server.ViewModels.Blocks
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
var val = default(T);
|
||||
TorchBase.Instance.InvokeBlocking(() => val = _prop.GetValue(Block.Block));
|
||||
return val;
|
||||
}
|
||||
get => _prop.GetValue(Block.Block);
|
||||
set
|
||||
{
|
||||
TorchBase.Instance.InvokeBlocking(() => _prop.SetValue(Block.Block, value));
|
||||
OnPropertyChanged();
|
||||
Block.RefreshModel();
|
||||
TorchBase.Instance.Invoke(() =>
|
||||
{
|
||||
_prop.SetValue(Block.Block, value);
|
||||
OnPropertyChanged();
|
||||
Block.RefreshModel();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -37,9 +37,15 @@ namespace Torch.Server.ViewModels.Entities
|
||||
|
||||
public virtual bool CanDelete => !(Entity is IMyCharacter);
|
||||
|
||||
public virtual void Delete()
|
||||
{
|
||||
Entity.Close();
|
||||
}
|
||||
|
||||
public EntityViewModel(IMyEntity entity, EntityTreeViewModel tree)
|
||||
{
|
||||
Entity = entity;
|
||||
Tree = tree;
|
||||
}
|
||||
|
||||
public EntityViewModel()
|
||||
|
@@ -1,5 +1,5 @@
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Sandbox.Game.Entities;
|
||||
using Sandbox.ModAPI;
|
||||
using Torch.Server.ViewModels.Blocks;
|
||||
@@ -9,17 +9,16 @@ namespace Torch.Server.ViewModels.Entities
|
||||
public class GridViewModel : EntityViewModel, ILazyLoad
|
||||
{
|
||||
private MyCubeGrid Grid => (MyCubeGrid)Entity;
|
||||
public MTObservableCollection<BlockViewModel> Blocks { get; } = new MTObservableCollection<BlockViewModel>();
|
||||
private static readonly Logger Log = LogManager.GetLogger(nameof(GridViewModel));
|
||||
public ObservableList<BlockViewModel> Blocks { get; } = new ObservableList<BlockViewModel>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DescriptiveName => $"{Name} ({Grid.BlocksCount} blocks)";
|
||||
public string DescriptiveName { get; }
|
||||
|
||||
public GridViewModel() { }
|
||||
|
||||
public GridViewModel(MyCubeGrid grid, EntityTreeViewModel tree) : base(grid, tree)
|
||||
{
|
||||
Log.Debug($"Creating model {Grid.DisplayName}");
|
||||
DescriptiveName = $"{grid.DisplayName} ({grid.BlocksCount} blocks)";
|
||||
Blocks.Add(new BlockViewModel(null, Tree));
|
||||
}
|
||||
|
||||
@@ -28,7 +27,6 @@ namespace Torch.Server.ViewModels.Entities
|
||||
if (obj.FatBlock != null)
|
||||
Blocks.RemoveWhere(b => b.Block.EntityId == obj.FatBlock?.EntityId);
|
||||
|
||||
Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName);
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
|
||||
@@ -36,9 +34,8 @@ namespace Torch.Server.ViewModels.Entities
|
||||
{
|
||||
var block = obj.FatBlock as IMyTerminalBlock;
|
||||
if (block != null)
|
||||
Blocks.Add(new BlockViewModel(block, Tree));
|
||||
Blocks.Insert(new BlockViewModel(block, Tree), b => b.Name);
|
||||
|
||||
Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName);
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
|
||||
@@ -48,20 +45,23 @@ namespace Torch.Server.ViewModels.Entities
|
||||
if (_load)
|
||||
return;
|
||||
|
||||
Log.Debug($"Loading model {Grid.DisplayName}");
|
||||
_load = true;
|
||||
Blocks.Clear();
|
||||
TorchBase.Instance.InvokeBlocking(() =>
|
||||
TorchBase.Instance.Invoke(() =>
|
||||
{
|
||||
foreach (var block in Grid.GetFatBlocks().Where(b => b is IMyTerminalBlock))
|
||||
{
|
||||
Blocks.Add(new BlockViewModel((IMyTerminalBlock)block, Tree));
|
||||
}
|
||||
});
|
||||
Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName);
|
||||
|
||||
Grid.OnBlockAdded += Grid_OnBlockAdded;
|
||||
Grid.OnBlockRemoved += Grid_OnBlockRemoved;
|
||||
Grid.OnBlockAdded += Grid_OnBlockAdded;
|
||||
Grid.OnBlockRemoved += Grid_OnBlockRemoved;
|
||||
|
||||
Tree.ControlDispatcher.BeginInvoke(() =>
|
||||
{
|
||||
Blocks.Sort(b => b.Block.CustomName);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ namespace Torch.Server.ViewModels.Entities
|
||||
|
||||
public override bool CanStop => false;
|
||||
|
||||
public MTObservableCollection<GridViewModel> AttachedGrids { get; } = new MTObservableCollection<GridViewModel>();
|
||||
public ObservableList<GridViewModel> AttachedGrids { get; } = new ObservableList<GridViewModel>();
|
||||
|
||||
public async Task UpdateAttachedGrids()
|
||||
{
|
||||
|
@@ -3,22 +3,28 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using Sandbox.Game.Entities;
|
||||
using Sandbox.Game.Entities.Character;
|
||||
using Torch.Server.ViewModels.Entities;
|
||||
using VRage.Game.ModAPI;
|
||||
using VRage.ModAPI;
|
||||
using System.Windows.Threading;
|
||||
using NLog;
|
||||
|
||||
namespace Torch.Server.ViewModels
|
||||
{
|
||||
public class EntityTreeViewModel : ViewModel
|
||||
{
|
||||
public MTObservableCollection<GridViewModel> Grids { get; set; } = new MTObservableCollection<GridViewModel>();
|
||||
public MTObservableCollection<CharacterViewModel> Characters { get; set; } = new MTObservableCollection<CharacterViewModel>();
|
||||
public MTObservableCollection<EntityViewModel> FloatingObjects { get; set; } = new MTObservableCollection<EntityViewModel>();
|
||||
public MTObservableCollection<VoxelMapViewModel> VoxelMaps { get; set; } = new MTObservableCollection<VoxelMapViewModel>();
|
||||
//TODO: these should be sorted sets for speed
|
||||
public ObservableList<GridViewModel> Grids { get; set; } = new ObservableList<GridViewModel>();
|
||||
public ObservableList<CharacterViewModel> Characters { get; set; } = new ObservableList<CharacterViewModel>();
|
||||
public ObservableList<EntityViewModel> FloatingObjects { get; set; } = new ObservableList<EntityViewModel>();
|
||||
public ObservableList<VoxelMapViewModel> VoxelMaps { get; set; } = new ObservableList<VoxelMapViewModel>();
|
||||
public Dispatcher ControlDispatcher => _control.Dispatcher;
|
||||
|
||||
private EntityViewModel _currentEntity;
|
||||
private UserControl _control;
|
||||
|
||||
public EntityViewModel CurrentEntity
|
||||
{
|
||||
@@ -26,7 +32,12 @@ namespace Torch.Server.ViewModels
|
||||
set { _currentEntity = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
public EntityTreeViewModel()
|
||||
public EntityTreeViewModel(UserControl control)
|
||||
{
|
||||
_control = control;
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
MyEntities.OnEntityAdd += MyEntities_OnEntityAdd;
|
||||
MyEntities.OnEntityRemove += MyEntities_OnEntityRemove;
|
||||
@@ -56,20 +67,16 @@ namespace Torch.Server.ViewModels
|
||||
switch (obj)
|
||||
{
|
||||
case MyCubeGrid grid:
|
||||
if (Grids.All(g => g.Entity.EntityId != obj.EntityId))
|
||||
Grids.Add(new GridViewModel(grid, this));
|
||||
Grids.Insert(new GridViewModel(grid, this), g => g.Name);
|
||||
break;
|
||||
case MyCharacter character:
|
||||
if (Characters.All(g => g.Entity.EntityId != obj.EntityId))
|
||||
Characters.Add(new CharacterViewModel(character, this));
|
||||
Characters.Insert(new CharacterViewModel(character, this), c => c.Name);
|
||||
break;
|
||||
case MyFloatingObject floating:
|
||||
if (FloatingObjects.All(g => g.Entity.EntityId != obj.EntityId))
|
||||
FloatingObjects.Add(new FloatingObjectViewModel(floating, this));
|
||||
FloatingObjects.Insert(new FloatingObjectViewModel(floating, this), f => f.Name);
|
||||
break;
|
||||
case MyVoxelBase voxel:
|
||||
if (VoxelMaps.All(g => g.Entity.EntityId != obj.EntityId))
|
||||
VoxelMaps.Add(new VoxelMapViewModel(voxel, this));
|
||||
VoxelMaps.Insert(new VoxelMapViewModel(voxel, this), v => v.Name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ namespace Torch.Server.ViewModels
|
||||
{
|
||||
public class PluginManagerViewModel : ViewModel
|
||||
{
|
||||
public MTObservableCollection<PluginViewModel> Plugins { get; } = new MTObservableCollection<PluginViewModel>();
|
||||
public ObservableList<PluginViewModel> Plugins { get; } = new ObservableList<PluginViewModel>();
|
||||
|
||||
private PluginViewModel _selectedPlugin;
|
||||
public PluginViewModel SelectedPlugin
|
||||
@@ -24,10 +24,12 @@ namespace Torch.Server.ViewModels
|
||||
|
||||
public PluginManagerViewModel(IPluginManager pluginManager)
|
||||
{
|
||||
foreach (var plugin in pluginManager)
|
||||
Plugins.Add(new PluginViewModel(plugin));
|
||||
pluginManager.PluginsLoaded += PluginManager_PluginsLoaded;
|
||||
}
|
||||
|
||||
private void PluginManager_PluginsLoaded(List<ITorchPlugin> obj)
|
||||
private void PluginManager_PluginsLoaded(IList<ITorchPlugin> obj)
|
||||
{
|
||||
Plugins.Clear();
|
||||
foreach (var plugin in obj)
|
||||
|
@@ -35,7 +35,7 @@ namespace Torch.Server.ViewModels
|
||||
BlockLimits.Add(new BlockLimitViewModel(this, limit.Key, limit.Value));
|
||||
}
|
||||
|
||||
public MTObservableCollection<BlockLimitViewModel> BlockLimits { get; } = new MTObservableCollection<BlockLimitViewModel>();
|
||||
public ObservableList<BlockLimitViewModel> BlockLimits { get; } = new ObservableList<BlockLimitViewModel>();
|
||||
|
||||
#region Multipliers
|
||||
|
||||
@@ -74,6 +74,12 @@ namespace Torch.Server.ViewModels
|
||||
{
|
||||
get => _settings.HackSpeedMultiplier; set { _settings.HackSpeedMultiplier = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="MyObjectBuilder_SessionSettings.WelderSpeedMultiplier"/>
|
||||
public float WelderSpeedMultiplier
|
||||
{
|
||||
get => _settings.WelderSpeedMultiplier; set { _settings.WelderSpeedMultiplier = value; OnPropertyChanged(); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region NPCs
|
||||
|
@@ -5,23 +5,21 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Torch.Server"
|
||||
mc:Ignorable="d">
|
||||
<DockPanel>
|
||||
<DockPanel DockPanel.Dock="Bottom">
|
||||
<Button x:Name="Send" Content="Send" DockPanel.Dock="Right" Width="50" Margin="5,5,5,5" Click="SendButton_Click"></Button>
|
||||
<TextBox x:Name="Message" DockPanel.Dock="Left" Margin="5,5,5,5" KeyDown="Message_OnKeyDown"></TextBox>
|
||||
</DockPanel>
|
||||
<ListView x:Name="ChatItems" ItemsSource="{Binding ChatHistory}" Margin="5,5,5,5">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<WrapPanel>
|
||||
<TextBlock Text="{Binding Timestamp}"/>
|
||||
<TextBlock Text=" "/>
|
||||
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
|
||||
<TextBlock Text=": "/>
|
||||
<TextBlock Text="{Binding Message}"/>
|
||||
</WrapPanel>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</DockPanel>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer x:Name="ChatScroller" Grid.Row="0" Margin="5,5,5,5" HorizontalScrollBarVisibility="Disabled">
|
||||
<TextBlock x:Name="ChatItems" />
|
||||
</ScrollViewer>
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="1" x:Name="Send" Content="Send" DockPanel.Dock="Right" Width="50" Margin="5,5,5,5" Click="SendButton_Click"></Button>
|
||||
<TextBox Grid.Column="0" x:Name="Message" DockPanel.Dock="Left" Margin="5,5,5,5" KeyDown="Message_OnKeyDown"></TextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -41,6 +42,31 @@ namespace Torch.Server
|
||||
_server = (TorchBase)server;
|
||||
_multiplayer = (MultiplayerManager)server.Multiplayer;
|
||||
DataContext = _multiplayer;
|
||||
|
||||
ChatItems.Inlines.Clear();
|
||||
_multiplayer.ChatHistory.ForEach(InsertMessage);
|
||||
if (_multiplayer.ChatHistory is INotifyCollectionChanged ncc)
|
||||
ncc.CollectionChanged += ChatHistory_CollectionChanged;
|
||||
ChatScroller.ScrollToBottom();
|
||||
}
|
||||
|
||||
private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
foreach (IChatMessage msg in e.NewItems)
|
||||
InsertMessage(msg);
|
||||
}
|
||||
|
||||
private void InsertMessage(IChatMessage msg)
|
||||
{
|
||||
bool atBottom = ChatScroller.VerticalOffset + 8 > ChatScroller.ScrollableHeight;
|
||||
var span = new Span();
|
||||
span.Inlines.Add($"{msg.Timestamp} ");
|
||||
span.Inlines.Add(new Run(msg.Name) { Foreground = msg.Name == "Server" ? Brushes.DarkBlue : Brushes.Blue });
|
||||
span.Inlines.Add($": {msg.Message}");
|
||||
span.Inlines.Add(new LineBreak());
|
||||
ChatItems.Inlines.Add(span);
|
||||
if (atBottom)
|
||||
ChatScroller.ScrollToBottom();
|
||||
}
|
||||
|
||||
private void SendButton_Click(object sender, RoutedEventArgs e)
|
||||
@@ -62,22 +88,26 @@ namespace Torch.Server
|
||||
return;
|
||||
|
||||
var commands = _server.Commands;
|
||||
string response = null;
|
||||
if (commands.IsCommand(text))
|
||||
{
|
||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text));
|
||||
_server.InvokeBlocking(() =>
|
||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text));
|
||||
_server.Invoke(() =>
|
||||
{
|
||||
response = commands.HandleCommandFromServer(text);
|
||||
var response = commands.HandleCommandFromServer(text);
|
||||
Dispatcher.BeginInvoke(() => OnMessageEntered_Callback(response));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_server.Multiplayer.SendMessage(text);
|
||||
}
|
||||
Message.Text = "";
|
||||
}
|
||||
|
||||
private void OnMessageEntered_Callback(string response)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(response))
|
||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
|
||||
Message.Text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,15 +10,27 @@
|
||||
<UserControl.DataContext>
|
||||
<viewModels:ConfigDedicatedViewModel />
|
||||
</UserControl.DataContext>
|
||||
<DockPanel>
|
||||
<DockPanel DockPanel.Dock="Top">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<DockPanel Grid.Row="0">
|
||||
<Label Content="World:" DockPanel.Dock="Left" />
|
||||
<Button Content="New World" Margin="3" DockPanel.Dock="Right" Click="NewWorld_OnClick"/>
|
||||
<ComboBox Text="{Binding LoadWorld}" ItemsSource="{Binding WorldPaths}" IsEditable="True" Margin="3" SelectionChanged="Selector_OnSelectionChanged"/>
|
||||
</DockPanel>
|
||||
<DockPanel DockPanel.Dock="Bottom">
|
||||
<StackPanel DockPanel.Dock="Left">
|
||||
<ScrollViewer IsEnabled="True">
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer Grid.Row="0" Margin="3">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<StackPanel Margin="3" DockPanel.Dock="Left">
|
||||
<Label Content="Server Name" />
|
||||
@@ -37,24 +49,6 @@
|
||||
<CheckBox IsChecked="{Binding PauseGameWhenEmpty}" Content="Pause When Empty" Margin="3" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="3">
|
||||
<!--
|
||||
<ListBox ItemsSource="{Binding Banned}" Width="130" Margin="3,0,3,3" Height="100"
|
||||
MouseDoubleClick="Banned_OnMouseDoubleClick" /> -->
|
||||
<!--
|
||||
<ListBox ItemsSource="{Binding Administrators}" Width="130" Margin="3,0,3,3" Height="100"
|
||||
MouseDoubleClick="Administrators_OnMouseDoubleClick">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding SteamId}" Width="100"/>
|
||||
<Label Content="{Binding Name}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox> -->
|
||||
<!--
|
||||
<ListBox ItemsSource="{Binding Mods}" Width="130" Margin="3,0,3,3" Height="100"
|
||||
MouseDoubleClick="Mods_OnMouseDoubleClick" /> -->
|
||||
<Label Content="Mods" />
|
||||
<TextBox Text="{Binding Mods}" Margin="3" Height="100" AcceptsReturn="true" VerticalScrollBarVisibility="Auto"/>
|
||||
<Label Content="Administrators" />
|
||||
@@ -64,23 +58,23 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<Button Content="Save Config" Margin="3" Click="Save_OnClick" />
|
||||
</StackPanel>
|
||||
<ScrollViewer Margin="3" DockPanel.Dock="Right" IsEnabled="True">
|
||||
<Button Grid.Row="1" Content="Save Config" Margin="3" Click="Save_OnClick" />
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Column="1" Margin="3">
|
||||
<StackPanel DataContext="{Binding SessionSettings}">
|
||||
<Expander Header="Block Limits">
|
||||
<StackPanel Margin="10,0,0,0">
|
||||
<DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding MaxBlocksPerPlayer}" Margin="3" Width="70" />
|
||||
<Label Content="Max Blocks Per Player" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding MaxGridSize}" Margin="3" Width="70" />
|
||||
<Label Content="Max Grid Size" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
<Button Content="Add" Margin="3" Click="AddLimit_OnClick" />
|
||||
<ListView ItemsSource="{Binding BlockLimits}" Margin="3">
|
||||
<ListBox.ItemTemplate>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding BlockType}" Width="150" Margin="3" />
|
||||
@@ -88,36 +82,40 @@
|
||||
<Button Content=" X " Margin="3" Click="RemoveLimit_OnClick" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
<Expander Header="Multipliers">
|
||||
<StackPanel Margin="10,0,0,0">
|
||||
<DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding InventorySizeMultiplier}" Margin="3" Width="70" />
|
||||
<Label Content="Inventory Size" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding RefinerySpeedMultiplier}" Margin="3" Width="70" />
|
||||
<Label Content="Refinery Speed" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding AssemblerEfficiencyMultiplier}" Margin="3" Width="70" />
|
||||
<Label Content="Assembler Efficiency" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding AssemblerSpeedMultiplier}" Margin="3" Width="70" />
|
||||
<Label Content="Assembler Speed" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding WelderSpeedMultiplier}" Margin="3" Width="70" />
|
||||
<Label Content="Welder Speed" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding GrinderSpeedMultiplier}" Margin="3" Width="70" />
|
||||
<Label Content="Grinder Speed" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding HackSpeedMultiplier}" Margin="3" Width="70" />
|
||||
<Label Content="Hacking Speed" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
<Expander Header="NPCs">
|
||||
@@ -131,14 +129,14 @@
|
||||
</Expander>
|
||||
<Expander Header="Environment">
|
||||
<StackPanel Margin="10,0,0,0">
|
||||
<DockPanel ToolTip="Increases physics precision at the cost of performance.">
|
||||
<StackPanel Orientation="Horizontal" ToolTip="Increases physics precision at the cost of performance.">
|
||||
<TextBox Text="{Binding PhysicsIterations}" Margin="3" Width="70" />
|
||||
<Label Content="Physics Iterations" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding MaxFloatingObjects}" Margin="3" Width="70" />
|
||||
<Label Content="Max Floating Objects" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
<CheckBox IsChecked="{Binding EnableRealisticSound}" Content="Enable Realistic Sound"
|
||||
Margin="3" />
|
||||
<CheckBox IsChecked="{Binding EnableAirtightness}" Content="Enable Airtightness" Margin="3" />
|
||||
@@ -149,41 +147,41 @@
|
||||
<CheckBox IsChecked="{Binding EnableVoxelDestruction}" Content="Enable Voxel Destruction"
|
||||
Margin="3" />
|
||||
<CheckBox IsChecked="{Binding EnableSunRotation}" Content="Enable Sun Rotation" Margin="3" />
|
||||
<DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding SunRotationInterval}" Margin="3" Width="70" />
|
||||
<Label Content="Sun Rotation Interval (mins)" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
<CheckBox IsChecked="{Binding EnableFlora}" Content="Enable Flora" Margin="3" />
|
||||
<DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding FloraDensity}" Margin="3" Width="70" />
|
||||
<Label Content="Flora Density" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding FloraDensityMultiplier}" Margin="3" Width="70" />
|
||||
<Label Content="Flora Density Multiplier" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding ViewDistance}" Margin="3" Width="70" />
|
||||
<Label Content="View Distance (meters)" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding WorldSize}" Margin="3" Width="70" />
|
||||
<Label Content="World Size (km)" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ComboBox SelectedItem="{Binding EnvironmentHostility}"
|
||||
ItemsSource="{Binding EnvironmentHostilityValues}" Margin="3" Width="100"
|
||||
DockPanel.Dock="Left" />
|
||||
<Label Content="Environment Hostility" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
<Expander Header="Players">
|
||||
<StackPanel Margin="10,0,0,0">
|
||||
<DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding MaxPlayers}" Margin="3" Width="70" />
|
||||
<Label Content="Max Players" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
<CheckBox IsChecked="{Binding EnableThirdPerson}" Content="Enable 3rd Person Camera"
|
||||
Margin="3" />
|
||||
<CheckBox IsChecked="{Binding EnableJetpack}" Content="Enable Jetpack" Margin="3" />
|
||||
@@ -191,20 +189,20 @@
|
||||
<CheckBox IsChecked="{Binding EnableCopyPaste}" Content="Enable Copy/Paste" Margin="3" />
|
||||
<CheckBox IsChecked="{Binding ShowPlayerNamesOnHud}" Content="Show Player Names on HUD"
|
||||
Margin="3" />
|
||||
<DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding SpawnTimeMultiplier}" Margin="3" Width="70" />
|
||||
<Label Content="Respawn Time Multiplier" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
<CheckBox IsChecked="{Binding ResetOwnership}" Content="Reset Ownership" Margin="3" />
|
||||
<CheckBox IsChecked="{Binding SpawnWithTools}" Content="Spawn With Tools" Margin="3" />
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
<Expander Header="Miscellaneous">
|
||||
<StackPanel Margin="10,0,0,0">
|
||||
<DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding AutosaveInterval}" Margin="3" Width="70" />
|
||||
<Label Content="Autosave Interval (minutes)" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
<CheckBox IsChecked="{Binding EnableConvertToStation}" Content="Enable Convert to Station"
|
||||
Margin="3" />
|
||||
|
||||
@@ -223,19 +221,19 @@
|
||||
<CheckBox IsChecked="{Binding EnableWeapons}" Content="Enable Weapons" Margin="3" />
|
||||
<CheckBox IsChecked="{Binding EnableIngameScripts}" Content="Enable Ingame Scripts"
|
||||
Margin="3" />
|
||||
<DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ComboBox SelectedItem="{Binding GameMode}" ItemsSource="{Binding GameModeValues}"
|
||||
Margin="3" Width="100" DockPanel.Dock="Left" />
|
||||
<Label Content="Game Mode" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding MaxBackupSaves}" Margin="3" Width="70" />
|
||||
<Label Content="Max Backup Saves" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -1,33 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
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.Xml.Serialization;
|
||||
using NLog;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Engine.Utils;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Server.Managers;
|
||||
using Torch.Server.ViewModels;
|
||||
using Torch.Views;
|
||||
using VRage;
|
||||
using VRage.Dedicated;
|
||||
using VRage.Game;
|
||||
using VRage.ObjectBuilders;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Torch.Server.Views
|
||||
{
|
||||
@@ -36,110 +11,29 @@ namespace Torch.Server.Views
|
||||
/// </summary>
|
||||
public partial class ConfigControl : UserControl
|
||||
{
|
||||
private readonly Logger Log = LogManager.GetLogger("Config");
|
||||
public MyConfigDedicated<MyObjectBuilder_SessionSettings> Config { get; set; }
|
||||
private ConfigDedicatedViewModel _viewModel;
|
||||
private string _configPath;
|
||||
private TorchConfig _torchConfig;
|
||||
private InstanceManager _instanceManager;
|
||||
|
||||
public ConfigControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void SaveConfig()
|
||||
{
|
||||
_viewModel.Save(_configPath);
|
||||
Log.Info("Saved DS config.");
|
||||
try
|
||||
{
|
||||
//var checkpoint = MyLocalCache.LoadCheckpoint(Config.LoadWorld, out _);
|
||||
MyObjectBuilderSerializer.DeserializeXML(Path.Combine(Config.LoadWorld, "Sandbox.sbc"), out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
||||
if (checkpoint == null)
|
||||
{
|
||||
Log.Error($"Failed to load {Config.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
|
||||
return;
|
||||
}
|
||||
checkpoint.Settings = Config.SessionSettings;
|
||||
checkpoint.Mods.Clear();
|
||||
foreach (var modId in Config.Mods)
|
||||
checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId));
|
||||
|
||||
MyLocalCache.SaveCheckpoint(checkpoint, Config.LoadWorld);
|
||||
Log.Info("Saved world config.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Failed to write sandbox config, changes will not appear on server");
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadDedicatedConfig(TorchConfig torchConfig)
|
||||
{
|
||||
_torchConfig = torchConfig;
|
||||
DataContext = null;
|
||||
MySandboxGame.Config = new MyConfig(MyPerServerSettings.GameNameSafe + ".cfg");
|
||||
var path = Path.Combine(torchConfig.InstancePath, "SpaceEngineers-Dedicated.cfg");
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Log.Error($"Failed to load dedicated config at {path}");
|
||||
DataContext = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(path);
|
||||
Config.Load(path);
|
||||
_configPath = path;
|
||||
|
||||
_viewModel = new ConfigDedicatedViewModel(Config);
|
||||
var worldFolders = Directory.EnumerateDirectories(Path.Combine(torchConfig.InstancePath, "Saves"));
|
||||
|
||||
foreach (var f in worldFolders)
|
||||
_viewModel.WorldPaths.Add(f);
|
||||
|
||||
LoadWorldMods();
|
||||
DataContext = _viewModel;
|
||||
}
|
||||
|
||||
private void LoadWorldMods()
|
||||
{
|
||||
var sandboxPath = Path.Combine(Config.LoadWorld, "Sandbox.sbc");
|
||||
|
||||
if (!File.Exists(sandboxPath))
|
||||
return;
|
||||
|
||||
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
||||
if (checkpoint == null)
|
||||
{
|
||||
Log.Error($"Failed to load {Config.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
|
||||
return;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var mod in checkpoint.Mods)
|
||||
sb.AppendLine(mod.PublishedFileId.ToString());
|
||||
|
||||
_viewModel.Mods = sb.ToString();
|
||||
|
||||
Log.Info("Loaded mod list from world");
|
||||
_instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>();
|
||||
DataContext = _instanceManager.DedicatedConfig;
|
||||
}
|
||||
|
||||
private void Save_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveConfig();
|
||||
_instanceManager.SaveConfig();
|
||||
}
|
||||
|
||||
private void RemoveLimit_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var vm = (BlockLimitViewModel)((Button)sender).DataContext;
|
||||
_viewModel.SessionSettings.BlockLimits.Remove(vm);
|
||||
_instanceManager.DedicatedConfig.SessionSettings.BlockLimits.Remove(vm);
|
||||
}
|
||||
|
||||
private void AddLimit_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_viewModel.SessionSettings.BlockLimits.Add(new BlockLimitViewModel(_viewModel.SessionSettings, "", 0));
|
||||
_instanceManager.DedicatedConfig.SessionSettings.BlockLimits.Add(new BlockLimitViewModel(_instanceManager.DedicatedConfig.SessionSettings, "", 0));
|
||||
}
|
||||
|
||||
private void NewWorld_OnClick(object sender, RoutedEventArgs e)
|
||||
@@ -152,8 +46,7 @@ namespace Torch.Server.Views
|
||||
//The control doesn't update the binding before firing the event.
|
||||
if (e.AddedItems.Count > 0)
|
||||
{
|
||||
Config.LoadWorld = (string)e.AddedItems[0];
|
||||
LoadWorldMods();
|
||||
_instanceManager.SelectWorld((string)e.AddedItems[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,8 +9,12 @@
|
||||
<UserControl.DataContext>
|
||||
<blocks:BlockViewModel />
|
||||
</UserControl.DataContext>
|
||||
<DockPanel x:Name="Stack" Margin="3">
|
||||
<StackPanel DockPanel.Dock="Top">
|
||||
<Grid Margin="3">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0">
|
||||
<Label Content="{Binding FullName}" FontSize="16" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Label Content="Built By: "/>
|
||||
@@ -18,7 +22,7 @@
|
||||
</StackPanel>
|
||||
<Label Content="Properties"/>
|
||||
</StackPanel>
|
||||
<ListView ItemsSource="{Binding Properties}" Margin="3" IsEnabled="True" DockPanel.Dock="Bottom">
|
||||
<ListView Grid.Row="1" ItemsSource="{Binding Properties}" Margin="3" IsEnabled="True">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:PropertyView />
|
||||
@@ -35,5 +39,5 @@
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
</ListView>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -10,13 +10,17 @@
|
||||
<UserControl.Resources>
|
||||
<converters:StringIdConverter x:Key="StringIdConverter"/>
|
||||
</UserControl.Resources>
|
||||
<DockPanel x:Name="Dock">
|
||||
<Label x:Name="Label" Width="150" VerticalAlignment="Center" DockPanel.Dock="Left">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" x:Name="Label" Width="150" VerticalAlignment="Center">
|
||||
<Label.Content>
|
||||
<TextBlock Text="{Binding Name, StringFormat={}{0}: }" />
|
||||
</Label.Content>
|
||||
</Label>
|
||||
<Frame x:Name="Frame" DockPanel.Dock="Right" NavigationUIVisibility="Hidden"/>
|
||||
</DockPanel>
|
||||
<Frame Grid.Column="1" x:Name="Frame" NavigationUIVisibility="Hidden"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
|
@@ -32,10 +32,10 @@ namespace Torch.Server.Views.Blocks
|
||||
{
|
||||
switch (args.NewValue)
|
||||
{
|
||||
case PropertyViewModel<bool> vmBool:
|
||||
case PropertyViewModel<bool> _:
|
||||
InitBool();
|
||||
break;
|
||||
case PropertyViewModel<StringBuilder> vmSb:
|
||||
case PropertyViewModel<StringBuilder> _:
|
||||
InitStringBuilder();
|
||||
break;
|
||||
default:
|
||||
|
@@ -8,17 +8,17 @@
|
||||
xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities"
|
||||
xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.DataContext>
|
||||
<viewModels:EntityTreeViewModel />
|
||||
</UserControl.DataContext>
|
||||
<DockPanel>
|
||||
<DockPanel DockPanel.Dock="Left">
|
||||
<StackPanel DockPanel.Dock="Bottom">
|
||||
<Button Content="Delete" Click="Delete_OnClick" IsEnabled="{Binding CurrentEntity.CanDelete}"
|
||||
Margin="3" />
|
||||
<Button Content="Stop" Click="Stop_OnClick" IsEnabled="{Binding CurrentEntity.CanStop}" Margin="3" />
|
||||
</StackPanel>
|
||||
<TreeView Width="300" Margin="3" DockPanel.Dock="Top" SelectedItemChanged="TreeView_OnSelectedItemChanged" TreeViewItem.Expanded="TreeViewItem_OnExpanded">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition MinWidth="300" Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TreeView Grid.Row="0" Margin="3" DockPanel.Dock="Top" SelectedItemChanged="TreeView_OnSelectedItemChanged" TreeViewItem.Expanded="TreeViewItem_OnExpanded">
|
||||
<TreeView.Resources>
|
||||
<HierarchicalDataTemplate DataType="{x:Type entities:GridViewModel}" ItemsSource="{Binding Blocks}">
|
||||
<TextBlock Text="{Binding DescriptiveName}" />
|
||||
@@ -66,7 +66,12 @@
|
||||
</TreeViewItem.ItemTemplate>
|
||||
</TreeViewItem>
|
||||
</TreeView>
|
||||
</DockPanel>
|
||||
<Frame x:Name="EditorFrame" Margin="3" NavigationUIVisibility="Hidden" />
|
||||
</DockPanel>
|
||||
<StackPanel Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<Button Content="Delete" Click="Delete_OnClick" IsEnabled="{Binding CurrentEntity.CanDelete}"
|
||||
Margin="3" />
|
||||
<Button Content="Stop" Click="Stop_OnClick" IsEnabled="{Binding CurrentEntity.CanStop}" Margin="3" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Frame Grid.Column="1" x:Name="EditorFrame" Margin="3" NavigationUIVisibility="Hidden" />
|
||||
</Grid>
|
||||
</UserControl>
|
@@ -27,12 +27,14 @@ namespace Torch.Server.Views
|
||||
/// </summary>
|
||||
public partial class EntitiesControl : UserControl
|
||||
{
|
||||
public EntityTreeViewModel Entities { get; set; } = new EntityTreeViewModel();
|
||||
public EntityTreeViewModel Entities { get; set; }
|
||||
|
||||
public EntitiesControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
Entities = new EntityTreeViewModel(this);
|
||||
DataContext = Entities;
|
||||
Entities.Init();
|
||||
}
|
||||
|
||||
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
@@ -58,7 +60,7 @@ namespace Torch.Server.Views
|
||||
{
|
||||
if (Entities.CurrentEntity?.Entity is IMyCharacter)
|
||||
return;
|
||||
TorchBase.Instance.Invoke(() => Entities.CurrentEntity?.Entity.Close());
|
||||
TorchBase.Instance.Invoke(() => Entities.CurrentEntity?.Delete());
|
||||
}
|
||||
|
||||
private void Stop_OnClick(object sender, RoutedEventArgs e)
|
||||
|
@@ -10,21 +10,26 @@
|
||||
<UserControl.DataContext>
|
||||
<viewModels:PluginManagerViewModel/>
|
||||
</UserControl.DataContext>
|
||||
<DockPanel>
|
||||
<DockPanel>
|
||||
<Button Content="Open Folder" Margin="3" DockPanel.Dock="Bottom" IsEnabled="false"></Button>
|
||||
<ListView Width="150" ItemsSource="{Binding Plugins}" SelectedItem="{Binding SelectedPlugin}" Margin="3">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ListView Grid.Row="0" ItemsSource="{Binding Plugins}" SelectedItem="{Binding SelectedPlugin}" Margin="3">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</DockPanel>
|
||||
<StackPanel Margin="3">
|
||||
<Label Content="{Binding SelectedPlugin.Name}" FontSize="16"/>
|
||||
<Frame NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<Button Grid.Row="1" Content="Open Folder" Margin="3" DockPanel.Dock="Bottom" IsEnabled="false"/>
|
||||
</Grid>
|
||||
<Frame Grid.Column="1" NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
|
@@ -5,14 +5,22 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Torch.Server"
|
||||
xmlns:views="clr-namespace:Torch.Server.Views"
|
||||
xmlns:converters="clr-namespace:Torch.Server.Views.Converters"
|
||||
mc:Ignorable="d"
|
||||
Title="Torch">
|
||||
<DockPanel>
|
||||
<StackPanel DockPanel.Dock="Top" Margin="5,5,5,5" Orientation="Horizontal">
|
||||
<Window.Resources>
|
||||
<converters:InverseBooleanConverter x:Key="InverseBool"/>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Margin="5,5,5,5" Orientation="Horizontal">
|
||||
<Button x:Name="BtnStart" Content="Start" Height="24" Width="75" Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left" Click="BtnStart_Click" IsDefault="True" />
|
||||
HorizontalAlignment="Left" Click="BtnStart_Click" IsEnabled="{Binding IsRunning, Converter={StaticResource InverseBool}}"/>
|
||||
<Button x:Name="BtnStop" Content="Stop" Height="24" Width="75" Margin="5,0,5,0" HorizontalAlignment="Left"
|
||||
Click="BtnStop_Click" IsEnabled="False" />
|
||||
Click="BtnStop_Click" IsEnabled="{Binding IsRunning}" />
|
||||
<Label>
|
||||
<Label.Content>
|
||||
<TextBlock Text="{Binding State, StringFormat=Status: {0}}"></TextBlock>
|
||||
@@ -29,22 +37,34 @@
|
||||
</Label.Content>
|
||||
</Label>
|
||||
</StackPanel>
|
||||
<TabControl x:Name="TabControl" DockPanel.Dock="Bottom" Margin="5,0,5,5">
|
||||
<TabControl Grid.Row="1" Height="Auto" x:Name="TabControl" Margin="5,0,5,5">
|
||||
<TabItem Header="Configuration">
|
||||
<DockPanel>
|
||||
<DockPanel DockPanel.Dock="Top">
|
||||
<Label Content="Instance Path: " Margin="3" />
|
||||
<TextBox x:Name="InstancePathBox" Margin="3" Height="20"
|
||||
TextChanged="InstancePathBox_OnTextChanged" IsEnabled="False" />
|
||||
</DockPanel>
|
||||
<views:ConfigControl x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" />
|
||||
</DockPanel>
|
||||
<Grid IsEnabled="{Binding IsRunning, Converter={StaticResource InverseBool}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" Content="Instance Path: " Margin="3" />
|
||||
<TextBox Grid.Column="1" x:Name="InstancePathBox" Margin="3" Height="20"
|
||||
LostKeyboardFocus="InstancePathBox_OnLostKeyboardFocus" />
|
||||
</Grid>
|
||||
<views:ConfigControl Grid.Row="1" x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" IsEnabled="{Binding IsRunning, Converter={StaticResource InverseBool}}"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Chat/Players">
|
||||
<DockPanel>
|
||||
<local:PlayerListControl x:Name="PlayerList" DockPanel.Dock="Right" Width="250" />
|
||||
<local:ChatControl x:Name="Chat" />
|
||||
</DockPanel>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="250"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:ChatControl Grid.Column="0" x:Name="Chat" IsEnabled="{Binding IsRunning}"/>
|
||||
<local:PlayerListControl Grid.Column="1" x:Name="PlayerList" DockPanel.Dock="Right"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Entity Manager">
|
||||
<views:EntitiesControl />
|
||||
@@ -53,5 +73,5 @@
|
||||
<views:PluginsControl x:Name="Plugins" />
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</Window>
|
@@ -19,6 +19,7 @@ using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using Sandbox;
|
||||
using Torch.API;
|
||||
using Torch.Server.Managers;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Torch.Server
|
||||
@@ -47,6 +48,7 @@ namespace Torch.Server
|
||||
Chat.BindServer(server);
|
||||
PlayerList.BindServer(server);
|
||||
Plugins.BindServer(server);
|
||||
LoadConfig((TorchConfig)server.Config);
|
||||
}
|
||||
|
||||
public void LoadConfig(TorchConfig config)
|
||||
@@ -57,26 +59,18 @@ namespace Torch.Server
|
||||
_config = config;
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ConfigControl.LoadDedicatedConfig(config);
|
||||
InstancePathBox.Text = config.InstancePath;
|
||||
});
|
||||
}
|
||||
|
||||
private void BtnStart_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_config.Save();
|
||||
((Button) sender).IsEnabled = false;
|
||||
BtnStop.IsEnabled = true;
|
||||
ConfigControl.SaveConfig();
|
||||
_server.GetManager<InstanceManager>().SaveConfig();
|
||||
new Thread(_server.Start).Start();
|
||||
}
|
||||
|
||||
private void BtnStop_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_config.Save();
|
||||
((Button) sender).IsEnabled = false;
|
||||
//HACK: Uncomment when restarting is possible.
|
||||
//BtnStart.IsEnabled = true;
|
||||
_server.Stop();
|
||||
}
|
||||
|
||||
@@ -86,7 +80,6 @@ namespace Torch.Server
|
||||
_config.WindowSize = newSize;
|
||||
var newPos = new Point((int)Left, (int)Top);
|
||||
_config.WindowPosition = newPos;
|
||||
_config.Save();
|
||||
|
||||
if (_server?.State == ServerState.Running)
|
||||
_server.Stop();
|
||||
@@ -97,13 +90,15 @@ namespace Torch.Server
|
||||
//MySandboxGame.Static.Invoke(MySandboxGame.ReloadDedicatedServerSession); use i
|
||||
}
|
||||
|
||||
private void InstancePathBox_OnTextChanged(object sender, TextChangedEventArgs e)
|
||||
private void InstancePathBox_OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||
{
|
||||
var name = ((TextBox)sender).Text;
|
||||
|
||||
_config.InstancePath = name;
|
||||
if (!Directory.Exists(name))
|
||||
return;
|
||||
|
||||
LoadConfig(_config);
|
||||
_config.InstancePath = name;
|
||||
_server.GetManager<InstanceManager>().LoadInstance(_config.InstancePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Extended.Wpf.Toolkit" version="2.9" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.1" targetFramework="net461" />
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
</packages>
|
@@ -1,12 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("Torch Client")]
|
||||
[assembly: AssemblyTitle("Torch Tests")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Torch")]
|
||||
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
#if DEBUG
|
||||
[assembly: AssemblyConfiguration("Debug")]
|
||||
#else
|
||||
[assembly: AssemblyConfiguration("Release")]
|
||||
#endif
|
221
Torch.Tests/ReflectionSystemTest.cs
Normal file
221
Torch.Tests/ReflectionSystemTest.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Torch.Utils;
|
||||
using Xunit;
|
||||
|
||||
namespace Torch.Tests
|
||||
{
|
||||
public class ReflectionSystemTest
|
||||
{
|
||||
static ReflectionSystemTest()
|
||||
{
|
||||
TestUtils.Init();
|
||||
}
|
||||
|
||||
private static ReflectionTestManager _manager = new ReflectionTestManager().Init(typeof(ReflectionTestBinding));
|
||||
public static IEnumerable<object[]> Getters => _manager.Getters;
|
||||
|
||||
public static IEnumerable<object[]> Setters => _manager.Setters;
|
||||
|
||||
public static IEnumerable<object[]> Invokers => _manager.Invokers;
|
||||
|
||||
#region Binding
|
||||
[Theory]
|
||||
[MemberData(nameof(Getters))]
|
||||
public void TestBindingGetter(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Setters))]
|
||||
public void TestBindingSetter(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Invokers))]
|
||||
public void TestBindingInvoker(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Results
|
||||
#region Dummy
|
||||
private class ReflectionTestTarget
|
||||
{
|
||||
public int TestField;
|
||||
public int TestProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Return true when greater or equal than 0
|
||||
/// </summary>
|
||||
public bool TestCall(int k)
|
||||
{
|
||||
return k >= 0;
|
||||
}
|
||||
|
||||
public static int TestFieldStatic;
|
||||
public static int TestPropertyStatic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Return true when greater or equal than 0
|
||||
/// </summary>
|
||||
public static bool TestCallStatic(int k)
|
||||
{
|
||||
return k >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
private class ReflectionTestBinding
|
||||
{
|
||||
[ReflectedGetter(Name = "TestField")]
|
||||
public static Func<ReflectionTestTarget, int> TestFieldGetter;
|
||||
[ReflectedSetter(Name = "TestField")]
|
||||
public static Action<ReflectionTestTarget, int> TestFieldSetter;
|
||||
|
||||
[ReflectedGetter(Name = "TestProperty")]
|
||||
public static Func<ReflectionTestTarget, int> TestPropertyGetter;
|
||||
[ReflectedSetter(Name = "TestProperty")]
|
||||
public static Action<ReflectionTestTarget, int> TestPropertySetter;
|
||||
|
||||
[ReflectedMethod]
|
||||
public static Func<ReflectionTestTarget, int, bool> TestCall;
|
||||
|
||||
|
||||
[ReflectedGetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
|
||||
public static Func<int> TestStaticFieldGetter;
|
||||
[ReflectedSetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
|
||||
public static Action<int> TestStaticFieldSetter;
|
||||
|
||||
[ReflectedGetter(Name = "TestPropertyStatic", Type = typeof(ReflectionTestTarget))]
|
||||
public static Func<int> TestStaticPropertyGetter;
|
||||
[ReflectedSetter(Name = "TestPropertyStatic", Type = typeof(ReflectionTestTarget))]
|
||||
public static Action<int> TestStaticPropertySetter;
|
||||
|
||||
[ReflectedStaticMethod(Type = typeof(ReflectionTestTarget))]
|
||||
public static Func<int, bool> TestCallStatic;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private readonly Random _rand = new Random();
|
||||
private int AcquireRandomNum()
|
||||
{
|
||||
return _rand.Next();
|
||||
}
|
||||
|
||||
#region Instance
|
||||
[Fact]
|
||||
public void TestInstanceFieldGet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
var target = new ReflectionTestTarget
|
||||
{
|
||||
TestField = testNumber
|
||||
};
|
||||
Assert.Equal(testNumber, ReflectionTestBinding.TestFieldGetter.Invoke(target));
|
||||
}
|
||||
[Fact]
|
||||
public void TestInstanceFieldSet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
var target = new ReflectionTestTarget();
|
||||
ReflectionTestBinding.TestFieldSetter.Invoke(target, testNumber);
|
||||
Assert.Equal(testNumber, target.TestField);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestInstancePropertyGet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
var target = new ReflectionTestTarget
|
||||
{
|
||||
TestProperty = testNumber
|
||||
};
|
||||
Assert.Equal(testNumber, ReflectionTestBinding.TestPropertyGetter.Invoke(target));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestInstancePropertySet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
var target = new ReflectionTestTarget();
|
||||
ReflectionTestBinding.TestPropertySetter.Invoke(target, testNumber);
|
||||
Assert.Equal(testNumber, target.TestProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestInstanceInvoke()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
var target = new ReflectionTestTarget();
|
||||
Assert.True(ReflectionTestBinding.TestCall.Invoke(target, 1));
|
||||
Assert.False(ReflectionTestBinding.TestCall.Invoke(target, -1));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Static
|
||||
[Fact]
|
||||
public void TestStaticFieldGet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
ReflectionTestTarget.TestFieldStatic = testNumber;
|
||||
Assert.Equal(testNumber, ReflectionTestBinding.TestStaticFieldGetter.Invoke());
|
||||
}
|
||||
[Fact]
|
||||
public void TestStaticFieldSet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
ReflectionTestBinding.TestStaticFieldSetter.Invoke(testNumber);
|
||||
Assert.Equal(testNumber, ReflectionTestTarget.TestFieldStatic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStaticPropertyGet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
ReflectionTestTarget.TestPropertyStatic = testNumber;
|
||||
Assert.Equal(testNumber, ReflectionTestBinding.TestStaticPropertyGetter.Invoke());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStaticPropertySet()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
int testNumber = AcquireRandomNum();
|
||||
ReflectionTestBinding.TestStaticPropertySetter.Invoke(testNumber);
|
||||
Assert.Equal(testNumber, ReflectionTestTarget.TestPropertyStatic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStaticInvoke()
|
||||
{
|
||||
ReflectedManager.Process(typeof(ReflectionTestBinding));
|
||||
Assert.True(ReflectionTestBinding.TestCallStatic.Invoke(1));
|
||||
Assert.False(ReflectionTestBinding.TestCallStatic.Invoke(-1));
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
}
|
72
Torch.Tests/ReflectionTestManager.cs
Normal file
72
Torch.Tests/ReflectionTestManager.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Torch.Utils;
|
||||
|
||||
namespace Torch.Tests
|
||||
{
|
||||
public class ReflectionTestManager
|
||||
{
|
||||
#region FieldProvider
|
||||
public struct FieldRef
|
||||
{
|
||||
public FieldInfo Field;
|
||||
|
||||
public FieldRef(FieldInfo f)
|
||||
{
|
||||
Field = f;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Field == null)
|
||||
return "Ignored";
|
||||
return Field.DeclaringType?.FullName + "." + Field.Name;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HashSet<object[]> _getters = new HashSet<object[]>();
|
||||
private readonly HashSet<object[]> _setters = new HashSet<object[]>();
|
||||
private readonly HashSet<object[]> _invokers = new HashSet<object[]>();
|
||||
|
||||
public ReflectionTestManager()
|
||||
{
|
||||
_getters.Add(new object[] { new FieldRef(null) });
|
||||
_setters.Add(new object[] { new FieldRef(null) });
|
||||
_invokers.Add(new object[] { new FieldRef(null) });
|
||||
}
|
||||
|
||||
public ReflectionTestManager Init(Assembly asm)
|
||||
{
|
||||
foreach (Type type in asm.GetTypes())
|
||||
Init(type);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReflectionTestManager Init(Type type)
|
||||
{
|
||||
foreach (FieldInfo field in type.GetFields(BindingFlags.Static |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic))
|
||||
{
|
||||
if (field.GetCustomAttribute<ReflectedMethodAttribute>() != null)
|
||||
_invokers.Add(new object[] { new FieldRef(field) });
|
||||
if (field.GetCustomAttribute<ReflectedGetterAttribute>() != null)
|
||||
_getters.Add(new object[] { new FieldRef(field) });
|
||||
if (field.GetCustomAttribute<ReflectedSetterAttribute>() != null)
|
||||
_setters.Add(new object[] { new FieldRef(field) });
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public IEnumerable<object[]> Getters => _getters;
|
||||
|
||||
public IEnumerable<object[]> Setters => _setters;
|
||||
|
||||
public IEnumerable<object[]> Invokers => _invokers;
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
35
Torch.Tests/TestUtils.cs
Normal file
35
Torch.Tests/TestUtils.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Torch.Utils;
|
||||
|
||||
namespace Torch.Tests
|
||||
{
|
||||
public sealed class TestUtils
|
||||
{
|
||||
public static void Init()
|
||||
{
|
||||
if (_torchResolver == null)
|
||||
_torchResolver = new TorchAssemblyResolver(GetGameBinaries());
|
||||
}
|
||||
|
||||
private static string GetGameBinaries()
|
||||
{
|
||||
string dir = Environment.CurrentDirectory;
|
||||
while (!string.IsNullOrWhiteSpace(dir))
|
||||
{
|
||||
string gameBin = Path.Combine(dir, "GameBinaries");
|
||||
if (Directory.Exists(gameBin))
|
||||
return gameBin;
|
||||
|
||||
dir = Path.GetDirectoryName(dir);
|
||||
}
|
||||
throw new Exception("GetGameBinaries failed to find a folder named GameBinaries in the directory tree");
|
||||
}
|
||||
|
||||
private static TorchAssemblyResolver _torchResolver;
|
||||
}
|
||||
}
|
91
Torch.Tests/Torch.Tests.csproj
Normal file
91
Torch.Tests/Torch.Tests.csproj
Normal file
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Torch.Tests</RootNamespace>
|
||||
<AssemblyName>Torch.Tests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<NoWarn>1591,0649</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>$(SolutionDir)\bin-test\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>$(SolutionDir)\bin-test\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<DocumentationFile>$(SolutionDir)\bin-test\x64\Release\Torch.Tests.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<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>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.assert, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.core, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.execution.desktop, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||
<Link>Properties\AssemblyVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ReflectionTestManager.cs" />
|
||||
<Compile Include="ReflectionSystemTest.cs" />
|
||||
<Compile Include="TestUtils.cs" />
|
||||
<Compile Include="TorchReflectionTest.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
|
||||
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
|
||||
<Name>Torch.API</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Torch\Torch.csproj">
|
||||
<Project>{7e01635c-3b67-472e-bcd6-c5539564f214}</Project>
|
||||
<Name>Torch</Name>
|
||||
<Private>True</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
</Project>
|
65
Torch.Tests/TorchReflectionTest.cs
Normal file
65
Torch.Tests/TorchReflectionTest.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using Torch.Utils;
|
||||
using Xunit;
|
||||
|
||||
namespace Torch.Tests
|
||||
{
|
||||
public class TorchReflectionTest
|
||||
{
|
||||
static TorchReflectionTest()
|
||||
{
|
||||
TestUtils.Init();
|
||||
}
|
||||
|
||||
private static ReflectionTestManager _manager;
|
||||
|
||||
private static ReflectionTestManager Manager()
|
||||
{
|
||||
if (_manager != null)
|
||||
return _manager;
|
||||
|
||||
return _manager = new ReflectionTestManager().Init(typeof(TorchBase).Assembly);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> Getters => Manager().Getters;
|
||||
|
||||
public static IEnumerable<object[]> Setters => Manager().Setters;
|
||||
|
||||
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
||||
|
||||
#region Binding
|
||||
[Theory]
|
||||
[MemberData(nameof(Getters))]
|
||||
public void TestBindingGetter(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Setters))]
|
||||
public void TestBindingSetter(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Invokers))]
|
||||
public void TestBindingInvoker(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
12
Torch.Tests/packages.config
Normal file
12
Torch.Tests/packages.config
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||
<package id="xunit" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.abstractions" version="2.0.1" targetFramework="net461" />
|
||||
<package id="xunit.assert" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.core" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.extensibility.core" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.extensibility.execution" version="2.2.0" targetFramework="net461" />
|
||||
<package id="xunit.runner.console" version="2.2.0" targetFramework="net461" developmentDependency="true" />
|
||||
</packages>
|
31
Torch.sln
31
Torch.sln
@@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26430.14
|
||||
VisualStudioVersion = 15.0.26730.8
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch", "Torch\Torch.csproj", "{7E01635C-3B67-472E-BCD6-C5539564F214}"
|
||||
EndProject
|
||||
@@ -16,6 +16,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
NLog.config = NLog.config
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Tests", "Torch.Tests\Torch.Tests.csproj", "{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Server.Tests", "Torch.Server.Tests\Torch.Server.Tests.csproj", "{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Client.Tests", "Torch.Client.Tests\Torch.Client.Tests.csproj", "{632E78C0-0DAC-4B71-B411-2F1B333CC310}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Versioning", "Versioning", "{762F6A0D-55EF-4173-8CDE-309D183F40C4}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Versioning\AssemblyVersion.cs = Versioning\AssemblyVersion.cs
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
@@ -38,8 +49,26 @@ Global
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.Build.0 = Debug|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.ActiveCfg = Release|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.Build.0 = Release|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|x64.Build.0 = Debug|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|x64.ActiveCfg = Release|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|x64.Build.0 = Release|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Debug|x64.Build.0 = Debug|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.ActiveCfg = Release|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.Build.0 = Release|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|x64.ActiveCfg = Release|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{762F6A0D-55EF-4173-8CDE-309D183F40C4} = {7AD02A71-1D4C-48F9-A8C1-789A5512424F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BB51D91F-958D-4B63-A897-3C40642ACD3E}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@@ -10,7 +10,7 @@ using VRage.Network;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
public struct ChatMessage : IChatMessage
|
||||
public class ChatMessage : IChatMessage
|
||||
{
|
||||
public DateTime Timestamp { get; }
|
||||
public ulong SteamId { get; }
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@@ -10,15 +9,18 @@ using System.Windows.Threading;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
[Obsolete("Use ObservableList<T>.")]
|
||||
public class MTObservableCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
public override event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
|
||||
if (collectionChanged != null)
|
||||
foreach (NotifyCollectionChangedEventHandler nh in collectionChanged.GetInvocationList())
|
||||
foreach (var del in collectionChanged.GetInvocationList())
|
||||
{
|
||||
var nh = (NotifyCollectionChangedEventHandler)del;
|
||||
var dispObj = nh.Target as DispatcherObject;
|
||||
|
||||
var dispatcher = dispObj?.Dispatcher;
|
||||
|
@@ -9,6 +9,7 @@ using System.Windows.Threading;
|
||||
|
||||
namespace Torch.Collections
|
||||
{
|
||||
[Serializable]
|
||||
public class ObservableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
186
Torch/Collections/ObservableList.cs
Normal file
186
Torch/Collections/ObservableList.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
/// <summary>
|
||||
/// An observable version of <see cref="List{T}"/>.
|
||||
/// </summary>
|
||||
public class ObservableList<T> : IList<T>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||
{
|
||||
private List<T> _internalList = new List<T>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
_internalList.Clear();
|
||||
OnPropertyChanged(nameof(Count));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return _internalList.Contains(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
_internalList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(T item)
|
||||
{
|
||||
var oldIndex = _internalList.IndexOf(item);
|
||||
if (!_internalList.Remove(item))
|
||||
return false;
|
||||
|
||||
OnPropertyChanged(nameof(Count));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, oldIndex));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _internalList.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(T item)
|
||||
{
|
||||
_internalList.Add(item);
|
||||
OnPropertyChanged(nameof(Count));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, Count - 1));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int IndexOf(T item) => _internalList.IndexOf(item);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
_internalList.Insert(index, item);
|
||||
OnPropertyChanged(nameof(Count));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an item in order based on the provided selector and comparer. This will only work properly on a pre-sorted list.
|
||||
/// </summary>
|
||||
public void Insert<TKey>(T item, Func<T, TKey> selector, IComparer<TKey> comparer = null)
|
||||
{
|
||||
comparer = comparer ?? Comparer<TKey>.Default;
|
||||
var key1 = selector(item);
|
||||
for (var i = 0; i < _internalList.Count; i++)
|
||||
{
|
||||
var key2 = selector(_internalList[i]);
|
||||
if (comparer.Compare(key1, key2) < 1)
|
||||
{
|
||||
Insert(i, item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Add(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
var old = this[index];
|
||||
_internalList.RemoveAt(index);
|
||||
OnPropertyChanged(nameof(Count));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old, index));
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => _internalList[index];
|
||||
set
|
||||
{
|
||||
var old = _internalList[index];
|
||||
if (old.Equals(value))
|
||||
return;
|
||||
|
||||
_internalList[index] = value;
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, old, index));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the list using the given selector and comparer./>
|
||||
/// </summary>
|
||||
public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer = null)
|
||||
{
|
||||
comparer = comparer ?? Comparer<TKey>.Default;
|
||||
var sortedItems = _internalList.OrderBy(selector, comparer).ToList();
|
||||
|
||||
_internalList = sortedItems;
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items that satisfy the given condition.
|
||||
/// </summary>
|
||||
public void RemoveWhere(Func<T, bool> condition)
|
||||
{
|
||||
for (var i = Count - 1; i > 0; i--)
|
||||
{
|
||||
if (condition?.Invoke(this[i]) ?? false)
|
||||
RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
var collectionChanged = CollectionChanged;
|
||||
if (collectionChanged != null)
|
||||
foreach (var del in collectionChanged.GetInvocationList())
|
||||
{
|
||||
var nh = (NotifyCollectionChangedEventHandler)del;
|
||||
var dispObj = nh.Target as DispatcherObject;
|
||||
|
||||
var dispatcher = dispObj?.Dispatcher;
|
||||
if (dispatcher != null && !dispatcher.CheckAccess())
|
||||
{
|
||||
dispatcher.BeginInvoke(() => nh.Invoke(this, e), DispatcherPriority.DataBind);
|
||||
continue;
|
||||
}
|
||||
|
||||
nh.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return _internalList.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)_internalList).GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,17 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
public class CommandLine
|
||||
/// <summary>
|
||||
/// Base class that adds tools for setting type properties through the command line.
|
||||
/// </summary>
|
||||
public abstract class CommandLine
|
||||
{
|
||||
private readonly string _argPrefix;
|
||||
private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>();
|
||||
|
||||
public CommandLine(string argPrefix = "-")
|
||||
protected CommandLine(string argPrefix = "-")
|
||||
{
|
||||
_argPrefix = argPrefix;
|
||||
foreach (var prop in GetType().GetProperties())
|
||||
|
@@ -8,6 +8,7 @@ using NLog;
|
||||
using Torch.API;
|
||||
using Torch.API.Plugins;
|
||||
using Torch.Commands.Permissions;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
|
||||
namespace Torch.Commands
|
||||
@@ -26,6 +27,7 @@ namespace Torch.Commands
|
||||
private readonly MethodInfo _method;
|
||||
private ParameterInfo[] _parameters;
|
||||
private int? _requiredParamCount;
|
||||
private static readonly Logger Log = LogManager.GetLogger(nameof(Command));
|
||||
|
||||
public Command(ITorchPlugin plugin, MethodInfo commandMethod)
|
||||
{
|
||||
@@ -60,7 +62,7 @@ namespace Torch.Commands
|
||||
_parameters = commandMethod.GetParameters();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append($"/{string.Join(" ", Path)} ");
|
||||
sb.Append($"!{string.Join(" ", Path)} ");
|
||||
for (var i = 0; i < _parameters.Length; i++)
|
||||
{
|
||||
var param = _parameters[i];
|
||||
@@ -84,31 +86,41 @@ namespace Torch.Commands
|
||||
|
||||
public bool TryInvoke(CommandContext context)
|
||||
{
|
||||
var parameters = new object[_parameters.Length];
|
||||
|
||||
if (context.Args.Count < _requiredParamCount)
|
||||
return false;
|
||||
|
||||
//Convert args from string
|
||||
for (var i = 0; i < _parameters.Length && i < context.Args.Count; i++)
|
||||
try
|
||||
{
|
||||
if (context.Args[i].TryConvert(_parameters[i].ParameterType, out object obj))
|
||||
parameters[i] = obj;
|
||||
else
|
||||
var parameters = new object[_parameters.Length];
|
||||
|
||||
if (context.Args.Count < _requiredParamCount)
|
||||
return false;
|
||||
}
|
||||
|
||||
//Fill remaining parameters with default values
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
//Convert args from string
|
||||
for (var i = 0; i < _parameters.Length && i < context.Args.Count; i++)
|
||||
{
|
||||
if (context.Args[i].TryConvert(_parameters[i].ParameterType, out object obj))
|
||||
parameters[i] = obj;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
//Fill remaining parameters with default values
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
if (parameters[i] == null)
|
||||
parameters[i] = _parameters[i].DefaultValue;
|
||||
}
|
||||
|
||||
var moduleInstance = (CommandModule)Activator.CreateInstance(Module);
|
||||
moduleInstance.Context = context;
|
||||
_method.Invoke(moduleInstance, parameters);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (parameters[i] == null)
|
||||
parameters[i] = _parameters[i].DefaultValue;
|
||||
context.Respond(e.Message, "Error", MyFontEnum.Red);
|
||||
Log.Error($"Command '{SyntaxHelp}' from '{Plugin.Name ?? "Torch"}' threw an exception. Args: {string.Join(", ", context.Args)}");
|
||||
Log.Error(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
var moduleInstance = (CommandModule)Activator.CreateInstance(Module);
|
||||
moduleInstance.Context = context;
|
||||
_method.Invoke(moduleInstance, parameters);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,16 +20,18 @@ namespace Torch.Commands
|
||||
|
||||
public CommandTree Commands { get; set; } = new CommandTree();
|
||||
private Logger _log = LogManager.GetLogger(nameof(CommandManager));
|
||||
[Dependency]
|
||||
private ChatManager _chatManager;
|
||||
|
||||
public CommandManager(ITorchBase torch, char prefix = '/') : base(torch)
|
||||
public CommandManager(ITorchBase torch, char prefix = '!') : base(torch)
|
||||
{
|
||||
Prefix = prefix;
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
public override void Attach()
|
||||
{
|
||||
RegisterCommandModule(typeof(TorchCommands));
|
||||
Torch.GetManager<ChatManager>().MessageRecieved += HandleCommand;
|
||||
_chatManager.MessageRecieved += HandleCommand;
|
||||
}
|
||||
|
||||
public bool HasPermission(ulong steamId, Command command)
|
||||
@@ -40,7 +42,7 @@ namespace Torch.Commands
|
||||
|
||||
public bool IsCommand(string command)
|
||||
{
|
||||
return command.Length > 1 && command[0] == Prefix;
|
||||
return !string.IsNullOrEmpty(command) && command[0] == Prefix;
|
||||
}
|
||||
|
||||
public void RegisterCommandModule(Type moduleType, ITorchPlugin plugin = null)
|
||||
|
@@ -1,10 +1,14 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using Sandbox.ModAPI;
|
||||
using Torch;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Commands.Permissions;
|
||||
using Torch.Managers;
|
||||
using VRage.Game.ModAPI;
|
||||
@@ -40,8 +44,43 @@ namespace Torch.Commands
|
||||
}
|
||||
else
|
||||
{
|
||||
var topNodeNames = commandManager.Commands.Root.Select(x => x.Key);
|
||||
Context.Respond($"Top level commands: {string.Join(", ", topNodeNames)}");
|
||||
Context.Respond($"Use the {commandManager.Prefix}longhelp command and check your Comms menu for a full list of commands.");
|
||||
}
|
||||
}
|
||||
|
||||
[Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")]
|
||||
public void LongHelp()
|
||||
{
|
||||
var commandManager = Context.Torch.Managers.GetManager<CommandManager>();
|
||||
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
|
||||
|
||||
if (node != null)
|
||||
{
|
||||
var command = node.Command;
|
||||
var children = node.Subcommands.Select(x => x.Key);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (command != null)
|
||||
{
|
||||
sb.AppendLine($"Syntax: {command.SyntaxHelp}");
|
||||
sb.Append(command.HelpText);
|
||||
}
|
||||
|
||||
if (node.Subcommands.Count() != 0)
|
||||
sb.Append($"\nSubcommands: {string.Join(", ", children)}");
|
||||
|
||||
Context.Respond(sb.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
var sb = new StringBuilder("Available commands:\n");
|
||||
foreach (var command in commandManager.Commands.WalkTree())
|
||||
{
|
||||
if (command.IsCommand)
|
||||
sb.AppendLine($"{command.Command.SyntaxHelp}\n {command.Command.HelpText}");
|
||||
}
|
||||
Context.Respond(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +101,68 @@ namespace Torch.Commands
|
||||
}
|
||||
|
||||
[Command("stop", "Stops the server.")]
|
||||
public void Stop()
|
||||
public void Stop(bool save = true)
|
||||
{
|
||||
Context.Respond("Stopping server.");
|
||||
if (save)
|
||||
Context.Torch.Save(Context.Player?.IdentityId ?? 0).Wait();
|
||||
Context.Torch.Stop();
|
||||
}
|
||||
|
||||
[Command("restart", "Restarts the server.")]
|
||||
public void Restart(int countdownSeconds = 10, bool save = true)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var countdown = RestartCountdown(countdownSeconds).GetEnumerator();
|
||||
while (countdown.MoveNext())
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private IEnumerable RestartCountdown(int countdown)
|
||||
{
|
||||
for (var i = countdown; i >= 0; i--)
|
||||
{
|
||||
if (i >= 60 && i % 60 == 0)
|
||||
{
|
||||
Context.Torch.Multiplayer.SendMessage($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
|
||||
yield return null;
|
||||
}
|
||||
else if (i > 0)
|
||||
{
|
||||
if (i < 11)
|
||||
Context.Torch.Multiplayer.SendMessage($"Restarting server in {i} second{Pluralize(i)}.");
|
||||
yield return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.Torch.Invoke(() =>
|
||||
{
|
||||
Context.Torch.Save(0).Wait();
|
||||
Context.Torch.Restart();
|
||||
});
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string Pluralize(int num)
|
||||
{
|
||||
return num == 1 ? "" : "s";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a save of the game.
|
||||
/// Caller id defaults to 0 in the case of triggering the chat command from server.
|
||||
/// </summary>
|
||||
[Command("save", "Saves the game.")]
|
||||
public void Save()
|
||||
{
|
||||
Context.Respond("Saving game.");
|
||||
Context.Torch.Save(Context.Player?.IdentityId ?? 0);
|
||||
}
|
||||
}
|
||||
}
|
17
Torch/DispatcherExtensions.cs
Normal file
17
Torch/DispatcherExtensions.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
public static class DispatcherExtensions
|
||||
{
|
||||
public static DispatcherOperation BeginInvoke(this Dispatcher dispatcher, Action action, DispatcherPriority priority = DispatcherPriority.Normal)
|
||||
{
|
||||
return dispatcher.BeginInvoke(priority, action);
|
||||
}
|
||||
}
|
||||
}
|
@@ -27,16 +27,19 @@ namespace Torch.Managers
|
||||
internal void RaiseMessageRecieved(ChatMsg msg, ref bool sendToOthers) =>
|
||||
MessageRecieved?.Invoke(msg, ref sendToOthers);
|
||||
|
||||
[Dependency]
|
||||
private INetworkManager _networkManager;
|
||||
|
||||
public ChatManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
public override void Attach()
|
||||
{
|
||||
try
|
||||
{
|
||||
Torch.GetManager<INetworkManager>().RegisterNetworkHandler(new ChatIntercept(this));
|
||||
_networkManager.RegisterNetworkHandler(new ChatIntercept(this));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -45,7 +48,7 @@ namespace Torch.Managers
|
||||
}
|
||||
}
|
||||
|
||||
private void Static_ChatMessageReceived(ulong arg1, string arg2, SteamSDK.ChatEntryTypeEnum arg3)
|
||||
private void Static_ChatMessageReceived(ulong arg1, string arg2)
|
||||
{
|
||||
var msg = new ChatMsg {Author = arg1, Text = arg2};
|
||||
var sendToOthers = true;
|
||||
|
309
Torch/Managers/DependencyManager.cs
Normal file
309
Torch/Managers/DependencyManager.cs
Normal file
@@ -0,0 +1,309 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NLog;
|
||||
using Torch.API.Managers;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
public sealed class DependencyManager : IDependencyManager
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private class DependencyInfo
|
||||
{
|
||||
private readonly Manager.DependencyAttribute _attribute;
|
||||
internal Type DependencyType => Field.FieldType;
|
||||
internal FieldInfo Field { get; private set; }
|
||||
internal bool Optional => _attribute.Optional;
|
||||
internal bool Ordered => _attribute.Ordered;
|
||||
|
||||
public DependencyInfo(FieldInfo field)
|
||||
{
|
||||
Field = field;
|
||||
_attribute = field.GetCustomAttribute<Manager.DependencyAttribute>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a registered instance of a manager.
|
||||
/// </summary>
|
||||
private class ManagerInstance
|
||||
{
|
||||
public IManager Instance { get; private set; }
|
||||
|
||||
internal readonly List<DependencyInfo> Dependencies;
|
||||
internal readonly HashSet<Type> SuppliedManagers;
|
||||
internal readonly HashSet<ManagerInstance> Dependents;
|
||||
|
||||
public ManagerInstance(IManager manager)
|
||||
{
|
||||
Instance = manager;
|
||||
|
||||
SuppliedManagers = new HashSet<Type>();
|
||||
Dependencies = new List<DependencyInfo>();
|
||||
Dependents = new HashSet<ManagerInstance>();
|
||||
var openBases = new Queue<Type>();
|
||||
openBases.Enqueue(manager.GetType());
|
||||
while (openBases.TryDequeue(out Type type))
|
||||
{
|
||||
if (!SuppliedManagers.Add(type))
|
||||
continue;
|
||||
|
||||
foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
if (field.HasAttribute<Manager.DependencyAttribute>())
|
||||
Dependencies.Add(new DependencyInfo(field));
|
||||
|
||||
foreach (Type parent in type.GetInterfaces())
|
||||
openBases.Enqueue(parent);
|
||||
if (type.BaseType != null)
|
||||
openBases.Enqueue(type.BaseType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by <see cref="DependencyManager"/> internally to topologically sort the dependency list.
|
||||
/// </summary>
|
||||
public int UnsolvedDependencies { get; set; }
|
||||
}
|
||||
|
||||
private readonly Dictionary<Type, ManagerInstance> _dependencySatisfaction;
|
||||
private readonly List<ManagerInstance> _registeredManagers;
|
||||
private readonly List<ManagerInstance> _orderedManagers;
|
||||
private readonly IDependencyProvider[] _parentProviders;
|
||||
|
||||
public DependencyManager(params IDependencyProvider[] parents)
|
||||
{
|
||||
_dependencySatisfaction = new Dictionary<Type, ManagerInstance>();
|
||||
_registeredManagers = new List<ManagerInstance>();
|
||||
_orderedManagers = new List<ManagerInstance>();
|
||||
_parentProviders = parents.Distinct().ToArray();
|
||||
}
|
||||
|
||||
private void AddDependencySatisfaction(ManagerInstance instance)
|
||||
{
|
||||
foreach (Type supplied in instance.SuppliedManagers)
|
||||
if (_dependencySatisfaction.ContainsKey(supplied))
|
||||
// When we already have a manager supplying this component we have to unregister it.
|
||||
_dependencySatisfaction[supplied] = null;
|
||||
else
|
||||
_dependencySatisfaction.Add(supplied, instance);
|
||||
}
|
||||
|
||||
private void RebuildDependencySatisfaction()
|
||||
{
|
||||
_dependencySatisfaction.Clear();
|
||||
foreach (ManagerInstance manager in _registeredManagers)
|
||||
AddDependencySatisfaction(manager);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool AddManager(IManager manager)
|
||||
{
|
||||
if (_initialized)
|
||||
throw new InvalidOperationException("Can't add new managers to an initialized dependency manager");
|
||||
// Protect against adding a manager derived from an existing manager
|
||||
if (_registeredManagers.Any(x => x.Instance.GetType().IsInstanceOfType(manager)))
|
||||
return false;
|
||||
// Protect against adding a manager when an existing manager derives from it.
|
||||
// ReSharper disable once ConvertIfStatementToReturnStatement
|
||||
if (_registeredManagers.Any(x => manager.GetType().IsInstanceOfType(x.Instance)))
|
||||
return false;
|
||||
|
||||
ManagerInstance instance = new ManagerInstance(manager);
|
||||
_registeredManagers.Add(instance);
|
||||
AddDependencySatisfaction(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ClearManagers()
|
||||
{
|
||||
if (_initialized)
|
||||
throw new InvalidOperationException("Can't remove managers from an initialized dependency manager");
|
||||
|
||||
_registeredManagers.Clear();
|
||||
_dependencySatisfaction.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool RemoveManager(IManager manager)
|
||||
{
|
||||
if (_initialized)
|
||||
throw new InvalidOperationException("Can't remove managers from an initialized dependency manager");
|
||||
if (manager == null)
|
||||
return false;
|
||||
for (int i = 0; i < _registeredManagers.Count; i++)
|
||||
if (_registeredManagers[i].Instance == manager)
|
||||
{
|
||||
_registeredManagers.RemoveAtFast(i);
|
||||
RebuildDependencySatisfaction();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
// Resets the previous sort results
|
||||
#region Reset
|
||||
_orderedManagers.Clear();
|
||||
foreach (ManagerInstance manager in _registeredManagers)
|
||||
manager.Dependents.Clear();
|
||||
#endregion
|
||||
|
||||
// Creates the dependency graph
|
||||
#region Prepare
|
||||
var dagQueue = new List<ManagerInstance>();
|
||||
foreach (ManagerInstance manager in _registeredManagers)
|
||||
{
|
||||
var inFactor = 0;
|
||||
foreach (DependencyInfo dependency in manager.Dependencies)
|
||||
{
|
||||
if (_dependencySatisfaction.TryGetValue(dependency.DependencyType, out var dependencyInstance))
|
||||
{
|
||||
if (dependency.Ordered)
|
||||
{
|
||||
inFactor++;
|
||||
dependencyInstance.Dependents.Add(manager);
|
||||
}
|
||||
}
|
||||
else if (!dependency.Optional && _parentProviders.All(x => x.GetManager(dependency.DependencyType) == null))
|
||||
_log.Warn("Unable to satisfy dependency {0} ({1}) of {2}.", dependency.DependencyType.Name,
|
||||
dependency.Field.Name, manager.Instance.GetType().Name);
|
||||
}
|
||||
manager.UnsolvedDependencies = inFactor;
|
||||
dagQueue.Add(manager);
|
||||
}
|
||||
#endregion
|
||||
|
||||
// Sorts the dependency graph into _orderedManagers
|
||||
#region Sort
|
||||
var tmpQueue = new List<ManagerInstance>();
|
||||
while (dagQueue.Any())
|
||||
{
|
||||
tmpQueue.Clear();
|
||||
for (var i = 0; i < dagQueue.Count; i++)
|
||||
{
|
||||
if (dagQueue[i].UnsolvedDependencies == 0)
|
||||
tmpQueue.Add(dagQueue[i]);
|
||||
else
|
||||
dagQueue[i - tmpQueue.Count] = dagQueue[i];
|
||||
}
|
||||
dagQueue.RemoveRange(dagQueue.Count - tmpQueue.Count, tmpQueue.Count);
|
||||
if (tmpQueue.Count == 0)
|
||||
{
|
||||
_log.Fatal("Dependency loop detected in the following managers:");
|
||||
foreach (ManagerInstance manager in dagQueue)
|
||||
{
|
||||
_log.Fatal(" + {0} has {1} unsolved dependencies.", manager.Instance.GetType().FullName, manager.UnsolvedDependencies);
|
||||
_log.Fatal(" - Dependencies: {0}",
|
||||
string.Join(", ", manager.Dependencies.Select(x => x.DependencyType.Name + (x.Optional ? " (Optional)" : ""))));
|
||||
_log.Fatal(" - Dependents: {0}",
|
||||
string.Join(", ", manager.Dependents.Select(x => x.Instance.GetType().Name)));
|
||||
}
|
||||
throw new InvalidOperationException("Unable to satisfy all required manager dependencies");
|
||||
}
|
||||
// Update the number of unsolved dependencies
|
||||
foreach (ManagerInstance manager in tmpQueue)
|
||||
foreach (ManagerInstance dependent in manager.Dependents)
|
||||
dependent.UnsolvedDependencies--;
|
||||
// tmpQueue.Sort(); If we have priorities this is where to sort them.
|
||||
_orderedManagers.AddRange(tmpQueue);
|
||||
}
|
||||
_log.Debug("Dependency tree satisfied. Load order is:");
|
||||
foreach (ManagerInstance manager in _orderedManagers)
|
||||
{
|
||||
_log.Debug(" - {0}", manager.Instance.GetType().FullName);
|
||||
_log.Debug(" - Dependencies: {0}",
|
||||
string.Join(", ", manager.Dependencies.Select(x => x.DependencyType.Name + (x.Optional ? " (Optional)" : ""))));
|
||||
_log.Debug(" - Dependents: {0}",
|
||||
string.Join(", ", manager.Dependents.Select(x => x.Instance.GetType().Name)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Updates the dependency fields with the correct manager instances
|
||||
#region Satisfy
|
||||
foreach (ManagerInstance manager in _registeredManagers)
|
||||
{
|
||||
manager.Dependents.Clear();
|
||||
foreach (DependencyInfo dependency in manager.Dependencies)
|
||||
dependency.Field.SetValue(manager.Instance, GetManager(dependency.DependencyType));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
private bool _initialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the dependency manager, and all its registered managers.
|
||||
/// </summary>
|
||||
public void Attach()
|
||||
{
|
||||
if (_initialized)
|
||||
throw new InvalidOperationException("Can't start the dependency manager more than once");
|
||||
_initialized = true;
|
||||
Sort();
|
||||
foreach (ManagerInstance manager in _orderedManagers)
|
||||
manager.Instance.Attach();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the dependency manager, and all its registered managers.
|
||||
/// </summary>
|
||||
public void Detach()
|
||||
{
|
||||
if (!_initialized)
|
||||
throw new InvalidOperationException("Can't dispose an uninitialized dependency manager");
|
||||
for (int i = _orderedManagers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
_orderedManagers[i].Instance.Detach();
|
||||
foreach (DependencyInfo field in _orderedManagers[i].Dependencies)
|
||||
field.Field.SetValue(_orderedManagers[i].Instance, null);
|
||||
}
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IManager> AttachOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_initialized)
|
||||
throw new InvalidOperationException("Can't determine dependency load order when uninitialized");
|
||||
foreach (ManagerInstance k in _orderedManagers)
|
||||
yield return k.Instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<IManager> DetachOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_initialized)
|
||||
throw new InvalidOperationException("Can't determine dependency load order when uninitialized");
|
||||
for (int i = _orderedManagers.Count - 1; i >= 0; i--)
|
||||
yield return _orderedManagers[i].Instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IManager GetManager(Type type)
|
||||
{
|
||||
// ReSharper disable once ConvertIfStatementToReturnStatement
|
||||
if (_dependencySatisfaction.TryGetValue(type, out ManagerInstance mgr))
|
||||
return mgr.Instance;
|
||||
foreach (IDependencyProvider provider in _parentProviders)
|
||||
{
|
||||
IManager entry = provider.GetManager(type);
|
||||
if (entry != null)
|
||||
return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
51
Torch/Managers/FilesystemManager.cs
Normal file
51
Torch/Managers/FilesystemManager.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Torch.API;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
public class FilesystemManager : Manager
|
||||
{
|
||||
/// <summary>
|
||||
/// Temporary directory for Torch that is cleared every time the program is started.
|
||||
/// </summary>
|
||||
public string TempDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Directory that contains the current Torch assemblies.
|
||||
/// </summary>
|
||||
public string TorchDirectory { get; }
|
||||
|
||||
public FilesystemManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
var temp = Path.Combine(Path.GetTempPath(), "Torch");
|
||||
TempDirectory = Directory.CreateDirectory(temp).FullName;
|
||||
var torch = new FileInfo(typeof(FilesystemManager).Assembly.Location).Directory.FullName;
|
||||
TorchDirectory = torch;
|
||||
|
||||
ClearTemp();
|
||||
}
|
||||
|
||||
private void ClearTemp()
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(TempDirectory, "*", SearchOption.AllDirectories))
|
||||
File.Delete(file);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the given file (if it exists) to a temporary directory that will be cleared the next time the application starts.
|
||||
/// </summary>
|
||||
public void SoftDelete(string file)
|
||||
{
|
||||
if (!File.Exists(file))
|
||||
return;
|
||||
var rand = Path.GetRandomFileName();
|
||||
var dest = Path.Combine(TempDirectory, rand);
|
||||
File.Move(file, dest);
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,6 +16,46 @@ namespace Torch.Managers
|
||||
|
||||
public abstract class Manager : IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates a field is a dependency of this parent manager.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class NetworkManager : Manager { }
|
||||
/// public class ChatManager : Manager {
|
||||
/// [Dependency(Optional = false)]
|
||||
/// private NetworkManager _network;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class DependencyAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// If this dependency isn't required.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The tagged field can be null if, and only if, this is true.
|
||||
/// </remarks>
|
||||
public bool Optional { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Dependency must be loaded before and unloaded after the containing manager.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class NetworkManager : Manager { }
|
||||
/// public class ChatManager : Manager {
|
||||
/// [Dependency(Ordered = true)]
|
||||
/// private NetworkManager _network;
|
||||
/// }
|
||||
/// </code>
|
||||
/// Load order will be NetworkManager, then ChatManager.
|
||||
/// Unload order will be ChatManager, then NetworkManager
|
||||
/// </example>
|
||||
public bool Ordered { get; set; } = true;
|
||||
}
|
||||
|
||||
protected ITorchBase Torch { get; }
|
||||
|
||||
protected Manager(ITorchBase torchInstance)
|
||||
@@ -23,7 +63,12 @@ namespace Torch.Managers
|
||||
Torch = torchInstance;
|
||||
}
|
||||
|
||||
public virtual void Init()
|
||||
public virtual void Attach()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void Detach()
|
||||
{
|
||||
|
||||
}
|
||||
|
@@ -16,6 +16,8 @@ using NLog;
|
||||
using Torch;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Game.Entities.Character;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
using Sandbox.Game.World;
|
||||
using Sandbox.ModAPI;
|
||||
@@ -24,30 +26,43 @@ using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Collections;
|
||||
using Torch.Commands;
|
||||
using Torch.Utils;
|
||||
using Torch.ViewModels;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
using VRage.GameServices;
|
||||
using VRage.Library.Collections;
|
||||
using VRage.Network;
|
||||
using VRage.Steam;
|
||||
using VRage.Utils;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a proxy to the game's multiplayer-related functions.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public class MultiplayerManager : Manager, IMultiplayerManager
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public event Action<IPlayer> PlayerJoined;
|
||||
/// <inheritdoc />
|
||||
public event Action<IPlayer> PlayerLeft;
|
||||
/// <inheritdoc />
|
||||
public event MessageReceivedDel MessageReceived;
|
||||
|
||||
public MTObservableCollection<IChatMessage> ChatHistory { get; } = new MTObservableCollection<IChatMessage>();
|
||||
public IList<IChatMessage> ChatHistory { get; } = new ObservableList<IChatMessage>();
|
||||
public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>();
|
||||
public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer;
|
||||
private static readonly Logger _log = LogManager.GetLogger(nameof(MultiplayerManager));
|
||||
private static readonly Logger _chatLog = LogManager.GetLogger("Chat");
|
||||
private Dictionary<MyPlayer.PlayerId, MyPlayer> _onlinePlayers;
|
||||
private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager));
|
||||
private static readonly Logger ChatLog = LogManager.GetLogger("Chat");
|
||||
|
||||
[ReflectedGetter(Name = "m_players")]
|
||||
private static Func<MyPlayerCollection, Dictionary<MyPlayer.PlayerId, MyPlayer>> _onlinePlayers;
|
||||
|
||||
[Dependency]
|
||||
private ChatManager _chatManager;
|
||||
[Dependency]
|
||||
private CommandManager _commandManager;
|
||||
[Dependency]
|
||||
private NetworkManager _networkManager;
|
||||
|
||||
internal MultiplayerManager(ITorchBase torch) : base(torch)
|
||||
{
|
||||
@@ -55,22 +70,24 @@ namespace Torch.Managers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
public override void Attach()
|
||||
{
|
||||
Torch.SessionLoaded += OnSessionLoaded;
|
||||
Torch.GetManager<ChatManager>().MessageRecieved += Instance_MessageRecieved;
|
||||
_chatManager.MessageRecieved += Instance_MessageRecieved;
|
||||
}
|
||||
|
||||
private void Instance_MessageRecieved(ChatMsg msg, ref bool sendToOthers)
|
||||
{
|
||||
var message = ChatMessage.FromChatMsg(msg);
|
||||
ChatHistory.Add(message);
|
||||
_chatLog.Info($"{message.Name}: {message.Message}");
|
||||
ChatLog.Info($"{message.Name}: {message.Message}");
|
||||
MessageReceived?.Invoke(message, ref sendToOthers);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void BanPlayer(ulong steamId, bool banned = true)
|
||||
{
|
||||
Torch.Invoke(() =>
|
||||
@@ -81,77 +98,90 @@ namespace Torch.Managers
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMyPlayer GetPlayerByName(string name)
|
||||
{
|
||||
ValidateOnlinePlayersList();
|
||||
return _onlinePlayers.FirstOrDefault(x => x.Value.DisplayName == name).Value;
|
||||
return _onlinePlayers.Invoke(MySession.Static.Players).FirstOrDefault(x => x.Value.DisplayName == name).Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMyPlayer GetPlayerBySteamId(ulong steamId)
|
||||
{
|
||||
ValidateOnlinePlayersList();
|
||||
_onlinePlayers.TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p);
|
||||
_onlinePlayers.Invoke(MySession.Static.Players).TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p);
|
||||
return p;
|
||||
}
|
||||
|
||||
public ulong GetSteamId(long identityId)
|
||||
{
|
||||
foreach (var kv in _onlinePlayers.Invoke(MySession.Static.Players))
|
||||
{
|
||||
if (kv.Value.Identity.IdentityId == identityId)
|
||||
return kv.Key.SteamId;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetSteamUsername(ulong steamId)
|
||||
{
|
||||
return MyMultiplayer.Static.GetMemberName(steamId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message in chat.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Red)
|
||||
{
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", message));
|
||||
var commands = Torch.GetManager<CommandManager>();
|
||||
if (commands.IsCommand(message))
|
||||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, message));
|
||||
if (_commandManager.IsCommand(message))
|
||||
{
|
||||
var response = commands.HandleCommandFromServer(message);
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
|
||||
var response = _commandManager.HandleCommandFromServer(message);
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, response));
|
||||
}
|
||||
else
|
||||
{
|
||||
var msg = new ScriptedChatMsg { Author = author, Font = font, Target = playerId, Text = message };
|
||||
MyMultiplayerBase.SendScriptedChatMessage(ref msg);
|
||||
}
|
||||
}
|
||||
var character = MySession.Static.Players.TryGetIdentity(playerId)?.Character;
|
||||
var steamId = GetSteamId(playerId);
|
||||
if (character == null)
|
||||
return;
|
||||
|
||||
private void ValidateOnlinePlayersList()
|
||||
{
|
||||
if (_onlinePlayers == null)
|
||||
_onlinePlayers = MySession.Static.Players.GetPrivateField<Dictionary<MyPlayer.PlayerId, MyPlayer>>("m_players");
|
||||
var addToGlobalHistoryMethod = typeof(MyCharacter).GetMethod("OnGlobalMessageSuccess", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
_networkManager.RaiseEvent(addToGlobalHistoryMethod, character, steamId, steamId, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSessionLoaded()
|
||||
{
|
||||
_log.Info("Initializing Steam auth");
|
||||
Log.Info("Initializing Steam auth");
|
||||
MyMultiplayer.Static.ClientKicked += OnClientKicked;
|
||||
MyMultiplayer.Static.ClientLeft += OnClientLeft;
|
||||
|
||||
ValidateOnlinePlayersList();
|
||||
|
||||
//TODO: Move these with the methods?
|
||||
RemoveHandlers();
|
||||
SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse;
|
||||
SteamServerAPI.Instance.GameServer.UserGroupStatus += UserGroupStatus;
|
||||
_members = (List<ulong>)typeof(MyDedicatedServerBase).GetField("m_members", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
||||
_waitingForGroup = (HashSet<ulong>)typeof(MyDedicatedServerBase).GetField("m_waitingForGroup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
||||
_log.Info("Steam auth initialized");
|
||||
if (!RemoveHandlers())
|
||||
{
|
||||
Log.Error("Steam auth failed to initialize");
|
||||
return;
|
||||
}
|
||||
MyGameService.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse;
|
||||
MyGameService.GameServer.UserGroupStatusResponse += UserGroupStatusResponse;
|
||||
Log.Info("Steam auth initialized");
|
||||
}
|
||||
|
||||
private void OnClientKicked(ulong steamId)
|
||||
{
|
||||
OnClientLeft(steamId, ChatMemberStateChangeEnum.Kicked);
|
||||
OnClientLeft(steamId, MyChatMemberStateChangeEnum.Kicked);
|
||||
}
|
||||
|
||||
private void OnClientLeft(ulong steamId, ChatMemberStateChangeEnum stateChange)
|
||||
private void OnClientLeft(ulong steamId, MyChatMemberStateChangeEnum stateChange)
|
||||
{
|
||||
Players.TryGetValue(steamId, out PlayerViewModel vm);
|
||||
if (vm == null)
|
||||
vm = new PlayerViewModel(steamId);
|
||||
_log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}.");
|
||||
Log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}.");
|
||||
PlayerLeft?.Invoke(vm);
|
||||
Players.Remove(steamId);
|
||||
}
|
||||
@@ -159,8 +189,12 @@ namespace Torch.Managers
|
||||
//TODO: Split the following into a new file?
|
||||
//These methods override some Keen code to allow us full control over client authentication.
|
||||
//This lets us have a server set to private (admins only) or friends (friends of all listed admins)
|
||||
private List<ulong> _members;
|
||||
private HashSet<ulong> _waitingForGroup;
|
||||
[ReflectedGetter(Name = "m_members")]
|
||||
private static Func<MyDedicatedServerBase, List<ulong>> _members;
|
||||
[ReflectedGetter(Name = "m_waitingForGroup")]
|
||||
private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup;
|
||||
[ReflectedGetter(Name = "m_kickedClients")]
|
||||
private static Func<MyMultiplayerBase, Dictionary<ulong, int>> _kickedClients;
|
||||
//private HashSet<ulong> _waitingForFriends;
|
||||
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
|
||||
//private IMultiplayer _multiplayerImplementation;
|
||||
@@ -168,152 +202,137 @@ namespace Torch.Managers
|
||||
/// <summary>
|
||||
/// Removes Keen's hooks into some Steam events so we have full control over client authentication
|
||||
/// </summary>
|
||||
private static void RemoveHandlers()
|
||||
private static bool RemoveHandlers()
|
||||
{
|
||||
var eventField = typeof(GameServer).GetField("<backing_store>ValidateAuthTicketResponse", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (eventField?.GetValue(SteamServerAPI.Instance.GameServer) is MulticastDelegate eventDel)
|
||||
MethodInfo methodValidateAuthTicket = typeof(MyDedicatedServerBase).GetMethod("GameServer_ValidateAuthTicketResponse",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (methodValidateAuthTicket == null)
|
||||
{
|
||||
foreach (var handle in eventDel.GetInvocationList())
|
||||
{
|
||||
if (handle.Method.Name == "GameServer_ValidateAuthTicketResponse")
|
||||
{
|
||||
SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse -= handle as ValidateAuthTicketResponse;
|
||||
_log.Debug("Removed GameServer_ValidateAuthTicketResponse");
|
||||
}
|
||||
}
|
||||
Log.Error("Unable to find the GameServer_ValidateAuthTicketResponse method to unhook");
|
||||
return false;
|
||||
}
|
||||
eventField = typeof(GameServer).GetField("<backing_store>UserGroupStatus", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
eventDel = eventField?.GetValue(SteamServerAPI.Instance.GameServer) as MulticastDelegate;
|
||||
if (eventDel != null)
|
||||
var eventValidateAuthTicket = Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.ValidateAuthTicketResponse))
|
||||
.FirstOrDefault(x => x.Method == methodValidateAuthTicket) as Action<ulong, JoinResult, ulong>;
|
||||
if (eventValidateAuthTicket == null)
|
||||
{
|
||||
foreach (var handle in eventDel.GetInvocationList())
|
||||
{
|
||||
if (handle.Method.Name == "GameServer_UserGroupStatus")
|
||||
{
|
||||
SteamServerAPI.Instance.GameServer.UserGroupStatus -= handle as UserGroupStatus;
|
||||
_log.Debug("Removed GameServer_UserGroupStatus");
|
||||
}
|
||||
}
|
||||
Log.Error(
|
||||
"Unable to unhook the GameServer_ValidateAuthTicketResponse method from GameServer.ValidateAuthTicketResponse");
|
||||
Log.Debug(" Want to unhook {0}", methodValidateAuthTicket);
|
||||
Log.Debug(" Registered handlers: ");
|
||||
foreach (Delegate method in Reflection.GetInstanceEvent(MyGameService.GameServer,
|
||||
nameof(MyGameService.GameServer.ValidateAuthTicketResponse)))
|
||||
Log.Debug(" - " + method.Method);
|
||||
return false;
|
||||
}
|
||||
|
||||
MethodInfo methodUserGroupStatus = typeof(MyDedicatedServerBase).GetMethod("GameServer_UserGroupStatus",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (methodUserGroupStatus == null)
|
||||
{
|
||||
Log.Error("Unable to find the GameServer_UserGroupStatus method to unhook");
|
||||
return false;
|
||||
}
|
||||
var eventUserGroupStatus = Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse))
|
||||
.FirstOrDefault(x => x.Method == methodUserGroupStatus)
|
||||
as Action<ulong, ulong, bool, bool>;
|
||||
if (eventUserGroupStatus == null)
|
||||
{
|
||||
Log.Error("Unable to unhook the GameServer_UserGroupStatus method from GameServer.UserGroupStatus");
|
||||
Log.Debug(" Want to unhook {0}", methodUserGroupStatus);
|
||||
Log.Debug(" Registered handlers: ");
|
||||
foreach (Delegate method in Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse)))
|
||||
Log.Debug(" - " + method.Method);
|
||||
return false;
|
||||
}
|
||||
|
||||
MyGameService.GameServer.ValidateAuthTicketResponse -=
|
||||
eventValidateAuthTicket;
|
||||
MyGameService.GameServer.UserGroupStatusResponse -=
|
||||
eventUserGroupStatus;
|
||||
return true;
|
||||
}
|
||||
|
||||
//Largely copied from SE
|
||||
private void ValidateAuthTicketResponse(ulong steamID, AuthSessionResponseEnum response, ulong ownerSteamID)
|
||||
private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner)
|
||||
{
|
||||
_log.Info($"Server ValidateAuthTicketResponse ({response}), owner: {ownerSteamID}");
|
||||
|
||||
if (steamID != ownerSteamID)
|
||||
Log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}");
|
||||
if (IsClientBanned.Invoke(MyMultiplayer.Static, steamOwner) || MySandboxGame.ConfigDedicated.Banned.Contains(steamOwner))
|
||||
{
|
||||
_log.Info($"User {steamID} is using a game owned by {ownerSteamID}. Tracking...");
|
||||
_gameOwnerIds[steamID] = ownerSteamID;
|
||||
|
||||
if (MySandboxGame.ConfigDedicated.Banned.Contains(ownerSteamID))
|
||||
{
|
||||
_log.Info($"Game owner {ownerSteamID} is banned. Banning and rejecting client {steamID}...");
|
||||
UserRejected(steamID, JoinResult.BannedByAdmins);
|
||||
BanPlayer(steamID);
|
||||
}
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins);
|
||||
RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||
}
|
||||
|
||||
if (response == AuthSessionResponseEnum.OK)
|
||||
else if (IsClientKicked.Invoke(MyMultiplayer.Static, steamOwner))
|
||||
{
|
||||
if (MySession.Static.MaxPlayers > 0 && _members.Count - 1 >= MySession.Static.MaxPlayers)
|
||||
{
|
||||
UserRejected(steamID, JoinResult.ServerFull);
|
||||
}
|
||||
else if (MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) /*|| MySandboxGame.ConfigDedicated.Administrators.Contains(MyDedicatedServerBase.ConvertSteamIDFrom64(steamID))*/)
|
||||
{
|
||||
UserAccepted(steamID);
|
||||
}
|
||||
else if (MySandboxGame.ConfigDedicated.GroupID == 0)
|
||||
{
|
||||
switch (MySession.Static.OnlineMode)
|
||||
{
|
||||
case MyOnlineModeEnum.PUBLIC:
|
||||
UserAccepted(steamID);
|
||||
break;
|
||||
case MyOnlineModeEnum.PRIVATE:
|
||||
UserRejected(steamID, JoinResult.NotInGroup);
|
||||
break;
|
||||
case MyOnlineModeEnum.FRIENDS:
|
||||
//TODO: actually verify friendship
|
||||
UserRejected(steamID, JoinResult.NotInGroup);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (SteamServerAPI.Instance.GetAccountType(MySandboxGame.ConfigDedicated.GroupID) != AccountType.Clan)
|
||||
{
|
||||
UserRejected(steamID, JoinResult.GroupIdInvalid);
|
||||
}
|
||||
else if (SteamServerAPI.Instance.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
|
||||
{
|
||||
// Returns false when there's no connection to Steam
|
||||
_waitingForGroup.Add(steamID);
|
||||
}
|
||||
else
|
||||
{
|
||||
UserRejected(steamID, JoinResult.SteamServersOffline);
|
||||
}
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently);
|
||||
RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||
}
|
||||
else
|
||||
if (response != JoinResult.OK)
|
||||
{
|
||||
JoinResult joinResult = JoinResult.TicketInvalid;
|
||||
switch (response)
|
||||
{
|
||||
case AuthSessionResponseEnum.AuthTicketCanceled:
|
||||
joinResult = JoinResult.TicketCanceled;
|
||||
break;
|
||||
case AuthSessionResponseEnum.AuthTicketInvalidAlreadyUsed:
|
||||
joinResult = JoinResult.TicketAlreadyUsed;
|
||||
break;
|
||||
case AuthSessionResponseEnum.LoggedInElseWhere:
|
||||
joinResult = JoinResult.LoggedInElseWhere;
|
||||
break;
|
||||
case AuthSessionResponseEnum.NoLicenseOrExpired:
|
||||
joinResult = JoinResult.NoLicenseOrExpired;
|
||||
break;
|
||||
case AuthSessionResponseEnum.UserNotConnectedToSteam:
|
||||
joinResult = JoinResult.UserNotConnected;
|
||||
break;
|
||||
case AuthSessionResponseEnum.VACBanned:
|
||||
joinResult = JoinResult.VACBanned;
|
||||
break;
|
||||
case AuthSessionResponseEnum.VACCheckTimedOut:
|
||||
joinResult = JoinResult.VACCheckTimedOut;
|
||||
break;
|
||||
}
|
||||
|
||||
UserRejected(steamID, joinResult);
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response);
|
||||
return;
|
||||
}
|
||||
if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit)
|
||||
{
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.ServerFull);
|
||||
return;
|
||||
}
|
||||
if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
|
||||
MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) ||
|
||||
MySandboxGame.ConfigDedicated.Administrators.Contains(ConvertSteamIDFrom64(steamID)))
|
||||
{
|
||||
this.UserAccepted(steamID);
|
||||
return;
|
||||
}
|
||||
if (GetServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
|
||||
{
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid);
|
||||
return;
|
||||
}
|
||||
if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
|
||||
{
|
||||
_waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID);
|
||||
return;
|
||||
}
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline);
|
||||
}
|
||||
|
||||
private void UserGroupStatus(ulong userId, ulong groupId, bool member, bool officer)
|
||||
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
|
||||
{
|
||||
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Remove(userId))
|
||||
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId))
|
||||
{
|
||||
if (member || officer)
|
||||
{
|
||||
UserAccepted(userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
UserRejected(userId, JoinResult.NotInGroup);
|
||||
}
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup);
|
||||
}
|
||||
}
|
||||
|
||||
private void UserAccepted(ulong steamId)
|
||||
{
|
||||
typeof(MyDedicatedServerBase).GetMethod("UserAccepted", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId});
|
||||
var vm = new PlayerViewModel(steamId) {State = ConnectionState.Connected};
|
||||
_log.Info($"Player {vm.Name} joined ({vm.SteamId})");
|
||||
UserAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId);
|
||||
|
||||
var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected };
|
||||
Log.Info($"Player {vm.Name} joined ({vm.SteamId})");
|
||||
Players.Add(steamId, vm);
|
||||
PlayerJoined?.Invoke(vm);
|
||||
}
|
||||
|
||||
private void UserRejected(ulong steamId, JoinResult reason)
|
||||
{
|
||||
typeof(MyDedicatedServerBase).GetMethod("UserRejected", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId, reason});
|
||||
}
|
||||
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase))]
|
||||
private static Func<ulong, string> ConvertSteamIDFrom64;
|
||||
|
||||
[ReflectedStaticMethod(Type = typeof(MyGameService))]
|
||||
private static Func<ulong, MyGameServiceAccountType> GetServerAccountType;
|
||||
|
||||
[ReflectedMethod(Name = "UserAccepted")]
|
||||
private static Action<MyDedicatedServerBase, ulong> UserAcceptedImpl;
|
||||
|
||||
[ReflectedMethod]
|
||||
private static Action<MyDedicatedServerBase, ulong, JoinResult> UserRejected;
|
||||
[ReflectedMethod]
|
||||
private static Func<MyMultiplayerBase, ulong, bool> IsClientBanned;
|
||||
[ReflectedMethod]
|
||||
private static Func<MyMultiplayerBase, ulong, bool> IsClientKicked;
|
||||
[ReflectedMethod]
|
||||
private static Action<MyMultiplayerBase, ulong> RaiseClientKicked;
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Utils;
|
||||
using VRage;
|
||||
using VRage.Library.Collections;
|
||||
using VRage.Network;
|
||||
@@ -20,12 +21,15 @@ namespace Torch.Managers
|
||||
private static Logger _log = LogManager.GetLogger(nameof(NetworkManager));
|
||||
|
||||
private const string MyTransportLayerField = "TransportLayer";
|
||||
private const string TypeTableField = "m_typeTable";
|
||||
private const string TransportHandlersField = "m_handlers";
|
||||
private MyTypeTable m_typeTable = new MyTypeTable();
|
||||
private HashSet<INetworkHandler> _networkHandlers = new HashSet<INetworkHandler>();
|
||||
private bool _init;
|
||||
|
||||
[ReflectedGetter(Name = "m_typeTable")]
|
||||
private static Func<MyReplicationLayerBase, MyTypeTable> _typeTableGetter;
|
||||
[ReflectedGetter(Name = "m_methodInfoLookup")]
|
||||
private static Func<MyEventTable, Dictionary<MethodInfo, CallSite>> _methodInfoLookupGetter;
|
||||
|
||||
public NetworkManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
|
||||
@@ -43,10 +47,6 @@ namespace Torch.Managers
|
||||
|
||||
var transportLayerType = transportLayerField.FieldType;
|
||||
|
||||
var replicationLayerType = typeof(MyReplicationLayerBase);
|
||||
if (!Reflection.HasField(replicationLayerType, TypeTableField))
|
||||
throw new TypeLoadException("Could not find TypeTable field");
|
||||
|
||||
if (!Reflection.HasField(transportLayerType, TransportHandlersField))
|
||||
throw new TypeLoadException("Could not find Handlers field");
|
||||
|
||||
@@ -63,7 +63,12 @@ namespace Torch.Managers
|
||||
/// <summary>
|
||||
/// Loads the network intercept system
|
||||
/// </summary>
|
||||
public override void Init()
|
||||
public override void Attach()
|
||||
{
|
||||
Torch.SessionLoaded += OnSessionLoaded;
|
||||
}
|
||||
|
||||
private void OnSessionLoaded()
|
||||
{
|
||||
if (_init)
|
||||
return;
|
||||
@@ -73,7 +78,6 @@ namespace Torch.Managers
|
||||
if (!ReflectionUnitTest())
|
||||
throw new InvalidOperationException("Reflection unit test failed.");
|
||||
|
||||
m_typeTable = typeof(MyReplicationLayerBase).GetField(TypeTableField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(MyMultiplayer.ReplicationLayer) as MyTypeTable;
|
||||
//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);
|
||||
@@ -121,8 +125,7 @@ namespace Torch.Managers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Fatal(ex);
|
||||
_log.Fatal(ex, "~Error processing event!");
|
||||
_log.Error(ex);
|
||||
//crash after logging, bad things could happen if we continue on with bad data
|
||||
throw;
|
||||
}
|
||||
@@ -142,7 +145,7 @@ namespace Torch.Managers
|
||||
object obj;
|
||||
if (networkId.IsInvalid) // Static event
|
||||
{
|
||||
site = m_typeTable.StaticEventTable.Get(eventId);
|
||||
site = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).StaticEventTable.Get(eventId);
|
||||
obj = null;
|
||||
}
|
||||
else // Instance event
|
||||
@@ -152,7 +155,7 @@ namespace Torch.Managers
|
||||
{
|
||||
return;
|
||||
}
|
||||
var typeInfo = m_typeTable.Get(sendAs.GetType());
|
||||
var typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(sendAs.GetType());
|
||||
var eventCount = typeInfo.EventTable.Count;
|
||||
if (eventId < eventCount) // Directly
|
||||
{
|
||||
@@ -162,7 +165,7 @@ namespace Torch.Managers
|
||||
else // Through proxy
|
||||
{
|
||||
obj = ((IMyProxyTarget)sendAs).Target;
|
||||
typeInfo = m_typeTable.Get(obj.GetType());
|
||||
typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(obj.GetType());
|
||||
site = typeInfo.EventTable.Get(eventId - (uint)eventCount); // Subtract max id of Proxy
|
||||
}
|
||||
}
|
||||
@@ -195,8 +198,8 @@ namespace Torch.Managers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Fatal(ex);
|
||||
_log.Fatal(ex, "Error when returning control to game server!");
|
||||
_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;
|
||||
}
|
||||
@@ -239,11 +242,11 @@ namespace Torch.Managers
|
||||
/// <param name="method"></param>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="args"></param>
|
||||
public void RaiseEvent(MethodInfo method, object obj, params object[] args)
|
||||
public void RaiseEvent(MethodInfo method, object obj, params object[] args)
|
||||
{
|
||||
//default(EndpointId) tells the network to broadcast the message
|
||||
RaiseEvent(method, obj, default(EndpointId), args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an event to one client by SteamId
|
||||
@@ -253,9 +256,9 @@ namespace Torch.Managers
|
||||
/// <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);
|
||||
}
|
||||
{
|
||||
RaiseEvent(method, obj, new EndpointId(steamId), args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an event to one client
|
||||
@@ -276,7 +279,7 @@ namespace Torch.Managers
|
||||
if (obj != null && owner == null)
|
||||
throw new InvalidCastException("Provided event target is not of type IMyEventOwner!");
|
||||
|
||||
if(!method.HasAttribute<EventAttribute>())
|
||||
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
|
||||
@@ -306,7 +309,7 @@ namespace Torch.Managers
|
||||
var parameters = method.GetParameters();
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
if (argTypes[i] != parameters[i].ParameterType)
|
||||
if (argTypes[i + 1] != parameters[i].ParameterType)
|
||||
throw new TypeLoadException($"Type mismatch on method parameters. Expected {string.Join(", ", parameters.Select(p => p.ParameterType.ToString()))} got {string.Join(", ", argTypes.Select(t => t.ToString()))}");
|
||||
}
|
||||
|
||||
@@ -323,10 +326,10 @@ namespace Torch.Managers
|
||||
/// <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
|
||||
RaiseStaticEvent(method, default(EndpointId), args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a static event to one client by SteamId
|
||||
@@ -335,9 +338,9 @@ namespace Torch.Managers
|
||||
/// <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);
|
||||
}
|
||||
{
|
||||
RaiseEvent(method, null, new EndpointId(steamId), args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a static event to one client
|
||||
@@ -351,18 +354,17 @@ namespace Torch.Managers
|
||||
}
|
||||
|
||||
private CallSite TryGetStaticCallSite(MethodInfo method)
|
||||
{
|
||||
var methodLookup = (Dictionary<MethodInfo, CallSite>)typeof(MyEventTable).GetField("m_methodInfoLookup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(m_typeTable.StaticEventTable);
|
||||
if (!methodLookup.TryGetValue(method, out CallSite result))
|
||||
{
|
||||
MyTypeTable typeTable = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer);
|
||||
if (!_methodInfoLookupGetter.Invoke(typeTable.StaticEventTable).TryGetValue(method, out CallSite result))
|
||||
throw new MissingMemberException("Provided event target not found!");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private CallSite TryGetCallSite(MethodInfo method, object arg)
|
||||
{
|
||||
var typeInfo = m_typeTable.Get(arg.GetType());
|
||||
var methodLookup = (Dictionary<MethodInfo, CallSite>)typeof(MyEventTable).GetField("m_methodInfoLookup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(typeInfo.EventTable);
|
||||
if (!methodLookup.TryGetValue(method, out CallSite result))
|
||||
private CallSite TryGetCallSite(MethodInfo method, object arg)
|
||||
{
|
||||
MySynchronizedTypeInfo typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(arg.GetType());
|
||||
if (!_methodInfoLookupGetter.Invoke(typeInfo.EventTable).TryGetValue(method, out CallSite result))
|
||||
throw new MissingMemberException("Provided event target not found!");
|
||||
return result;
|
||||
}
|
||||
|
@@ -5,42 +5,33 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Sandbox;
|
||||
using Sandbox.ModAPI;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.Plugins;
|
||||
using Torch.Commands;
|
||||
using Torch.Managers;
|
||||
using Torch.Updater;
|
||||
using VRage.Plugins;
|
||||
using VRage.Collections;
|
||||
using VRage.Library.Collections;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
public class PluginManager : IPluginManager
|
||||
/// <inheritdoc />
|
||||
public class PluginManager : Manager, IPluginManager
|
||||
{
|
||||
private readonly ITorchBase _torch;
|
||||
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
|
||||
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
||||
[Dependency]
|
||||
private UpdateManager _updateManager;
|
||||
[Dependency]
|
||||
private CommandManager _commandManager;
|
||||
|
||||
public List<ITorchPlugin> Plugins { get; } = new List<ITorchPlugin>();
|
||||
/// <inheritdoc />
|
||||
public IList<ITorchPlugin> Plugins { get; } = new ObservableList<ITorchPlugin>();
|
||||
|
||||
public float LastUpdateMs => _lastUpdateMs;
|
||||
private volatile float _lastUpdateMs;
|
||||
public event Action<IList<ITorchPlugin>> PluginsLoaded;
|
||||
|
||||
public event Action<List<ITorchPlugin>> PluginsLoaded;
|
||||
|
||||
public PluginManager(ITorchBase torch)
|
||||
public PluginManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
_torch = torch;
|
||||
|
||||
if (!Directory.Exists(PluginDir))
|
||||
Directory.CreateDirectory(PluginDir);
|
||||
}
|
||||
@@ -50,17 +41,14 @@ namespace Torch.Managers
|
||||
/// </summary>
|
||||
public void UpdatePlugins()
|
||||
{
|
||||
var s = Stopwatch.StartNew();
|
||||
foreach (var plugin in Plugins)
|
||||
plugin.Update();
|
||||
s.Stop();
|
||||
_lastUpdateMs = (float)s.Elapsed.TotalMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unloads all plugins.
|
||||
/// </summary>
|
||||
public void DisposePlugins()
|
||||
public override void Detach()
|
||||
{
|
||||
foreach (var plugin in Plugins)
|
||||
plugin.Dispose();
|
||||
@@ -70,40 +58,39 @@ namespace Torch.Managers
|
||||
|
||||
private void DownloadPlugins()
|
||||
{
|
||||
_log.Info("Downloading plugins");
|
||||
var updater = new PluginUpdater(this);
|
||||
|
||||
var folders = Directory.GetDirectories(PluginDir);
|
||||
var taskList = new List<Task>();
|
||||
if (_torch.Config.RedownloadPlugins)
|
||||
_log.Warn("Force downloading all plugins because the RedownloadPlugins flag is set in the config");
|
||||
|
||||
//Copy list because we don't want to modify the config.
|
||||
var toDownload = Torch.Config.Plugins.ToList();
|
||||
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
var manifestPath = Path.Combine(folder, "manifest.xml");
|
||||
if (!File.Exists(manifestPath))
|
||||
{
|
||||
_log.Info($"No manifest in {folder}, skipping");
|
||||
_log.Debug($"No manifest in {folder}, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
_log.Info($"Checking for updates for {folder}");
|
||||
var manifest = PluginManifest.Load(manifestPath);
|
||||
taskList.Add(updater.CheckAndUpdate(manifest, _torch.Config.RedownloadPlugins));
|
||||
toDownload.RemoveAll(x => string.Compare(manifest.Repository, x, StringComparison.InvariantCultureIgnoreCase) == 0);
|
||||
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
|
||||
}
|
||||
|
||||
foreach (var repository in toDownload)
|
||||
{
|
||||
var manifest = new PluginManifest {Repository = repository, Version = "0.0"};
|
||||
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
|
||||
}
|
||||
|
||||
Task.WaitAll(taskList.ToArray());
|
||||
_torch.Config.RedownloadPlugins = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads and creates instances of all plugins in the <see cref="PluginDir"/> folder.
|
||||
/// </summary>
|
||||
public void Init()
|
||||
/// <inheritdoc />
|
||||
public void LoadPlugins()
|
||||
{
|
||||
var commands = ((TorchBase)_torch).Commands;
|
||||
|
||||
if (_torch.Config.AutomaticUpdates)
|
||||
if (Torch.Config.ShouldUpdatePlugins)
|
||||
DownloadPlugins();
|
||||
else
|
||||
_log.Warn("Automatic plugin updates are disabled.");
|
||||
@@ -129,22 +116,23 @@ namespace Torch.Managers
|
||||
throw new TypeLoadException($"Plugin '{type.FullName}' is missing a {nameof(PluginAttribute)}");
|
||||
|
||||
_log.Info($"Loading plugin {plugin.Name} ({plugin.Version})");
|
||||
plugin.StoragePath = _torch.Config.InstancePath;
|
||||
plugin.StoragePath = Torch.Config.InstancePath;
|
||||
Plugins.Add(plugin);
|
||||
|
||||
commands.RegisterPluginCommands(plugin);
|
||||
_commandManager.RegisterPluginCommands(plugin);
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error($"Error loading plugin '{type.FullName}'");
|
||||
_log.Error(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Plugins.ForEach(p => p.Init(_torch));
|
||||
PluginsLoaded?.Invoke(Plugins);
|
||||
Plugins.ForEach(p => p.Init(Torch));
|
||||
PluginsLoaded?.Invoke(Plugins.ToList());
|
||||
}
|
||||
|
||||
public IEnumerator<ITorchPlugin> GetEnumerator()
|
||||
|
@@ -16,7 +16,7 @@ namespace Torch.Managers
|
||||
{
|
||||
private MyScriptWhitelist _whitelist;
|
||||
|
||||
public void Init()
|
||||
public void Attach()
|
||||
{
|
||||
_whitelist = MyScriptCompiler.Static.Whitelist;
|
||||
MyScriptCompiler.Static.AddConditionalCompilationSymbols("TORCH");
|
||||
@@ -41,6 +41,11 @@ namespace Torch.Managers
|
||||
Log.Info(whitelist);*/
|
||||
}
|
||||
|
||||
public void Detach()
|
||||
{
|
||||
// TODO unregister whitelist patches
|
||||
}
|
||||
|
||||
public void UnwhitelistType(Type t)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
@@ -1,32 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.IO.Packaging;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Octokit;
|
||||
using SteamSDK;
|
||||
using Torch.API;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles updating of the DS and Torch plugins.
|
||||
/// </summary>
|
||||
public class UpdateManager : IDisposable
|
||||
public class UpdateManager : Manager
|
||||
{
|
||||
private Timer _updatePollTimer;
|
||||
private GitHubClient _gitClient = new GitHubClient(new ProductHeaderValue("Torch"));
|
||||
private string _torchDir = new FileInfo(typeof(UpdateManager).Assembly.Location).DirectoryName;
|
||||
private Logger _log = LogManager.GetLogger(nameof(UpdateManager));
|
||||
[Dependency]
|
||||
private FilesystemManager _fsManager;
|
||||
|
||||
public UpdateManager()
|
||||
public UpdateManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
_updatePollTimer = new Timer(CheckForUpdates, this, TimeSpan.Zero, TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
private void CheckForUpdates(object state)
|
||||
{
|
||||
|
||||
//_updatePollTimer = new Timer(TimerElapsed, this, TimeSpan.Zero, TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
public override void Attach()
|
||||
{
|
||||
CheckAndUpdateTorch();
|
||||
}
|
||||
|
||||
private void TimerElapsed(object state)
|
||||
{
|
||||
CheckAndUpdateTorch();
|
||||
}
|
||||
|
||||
private async Task<Tuple<Version, string>> GetLatestRelease(string owner, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
var latest = await _gitClient.Repository.Release.GetLatest(owner, name).ConfigureAwait(false);
|
||||
if (latest == null)
|
||||
return new Tuple<Version, string>(new Version(), null);
|
||||
|
||||
var zip = latest.Assets.FirstOrDefault(x => x.Name.Contains(".zip"));
|
||||
var versionName = Regex.Match(latest.TagName, "(\\d+\\.)+\\d+").ToString();
|
||||
if (string.IsNullOrWhiteSpace(versionName))
|
||||
{
|
||||
_log.Warn("Unable to parse tag {0} for {1}/{2}", latest.TagName, owner, name);
|
||||
versionName = "0.0";
|
||||
}
|
||||
return new Tuple<Version, string>(new Version(versionName), zip?.BrowserDownloadUrl);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error($"An error occurred getting release information for '{owner}/{name}'");
|
||||
_log.Error(e);
|
||||
return new Tuple<Version, string>(new Version(), null);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckAndUpdatePlugin(PluginManifest manifest)
|
||||
{
|
||||
if (!Torch.Config.GetPluginUpdates)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var name = manifest.Repository.Split('/');
|
||||
if (name.Length != 2)
|
||||
{
|
||||
_log.Error($"'{manifest.Repository}' is not a valid GitHub repository.");
|
||||
return;
|
||||
}
|
||||
|
||||
var currentVersion = new Version(manifest.Version);
|
||||
var releaseInfo = await GetLatestRelease(name[0], name[1]).ConfigureAwait(false);
|
||||
if (releaseInfo.Item1 > currentVersion)
|
||||
{
|
||||
_log.Warn($"Updating {manifest.Repository} from version {currentVersion} to version {releaseInfo.Item1}");
|
||||
var updateName = Path.Combine(_fsManager.TempDirectory, $"{name[0]}_{name[1]}.zip");
|
||||
var updatePath = Path.Combine(_torchDir, "Plugins");
|
||||
await new WebClient().DownloadFileTaskAsync(new Uri(releaseInfo.Item2), updateName).ConfigureAwait(false);
|
||||
UpdateFromZip(updateName, updatePath);
|
||||
File.Delete(updateName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info($"{manifest.Repository} is up to date. ({currentVersion})");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error($"An error occured downloading the plugin update for {manifest.Repository}.");
|
||||
_log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async void CheckAndUpdateTorch()
|
||||
{
|
||||
// Doesn't work properly or reliably, TODO update when Jenkins is fully configured
|
||||
return;
|
||||
|
||||
if (!Torch.Config.GetTorchUpdates)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var releaseInfo = await GetLatestRelease("TorchAPI", "Torch").ConfigureAwait(false);
|
||||
if (releaseInfo.Item1 > Torch.TorchVersion)
|
||||
{
|
||||
_log.Warn($"Updating Torch from version {Torch.TorchVersion} to version {releaseInfo.Item1}");
|
||||
var updateName = Path.Combine(_fsManager.TempDirectory, "torchupdate.zip");
|
||||
new WebClient().DownloadFile(new Uri(releaseInfo.Item2), updateName);
|
||||
UpdateFromZip(updateName, _torchDir);
|
||||
File.Delete(updateName);
|
||||
_log.Warn($"Torch version {releaseInfo.Item1} has been installed, please restart Torch to finish the process.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info("Torch is up to date.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error("An error occured downloading the Torch update.");
|
||||
_log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFromZip(string zipFile, string extractPath)
|
||||
{
|
||||
using (var zip = ZipFile.OpenRead(zipFile))
|
||||
{
|
||||
foreach (var file in zip.Entries)
|
||||
{
|
||||
_log.Debug($"Unzipping {file.FullName}");
|
||||
var targetFile = Path.Combine(extractPath, file.FullName);
|
||||
_fsManager.SoftDelete(targetFile);
|
||||
}
|
||||
|
||||
zip.ExtractToDirectory(extractPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Detach()
|
||||
{
|
||||
_updatePollTimer?.Dispose();
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user