Merge branch 'Patron' of https://github.com/TorchAPI/Torch into survival
This commit is contained in:
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@@ -31,7 +31,7 @@ node {
|
|||||||
|
|
||||||
stage('Build') {
|
stage('Build') {
|
||||||
currentBuild.description = bat(returnStdout: true, script: '@powershell -File Versioning/version.ps1').trim()
|
currentBuild.description = bat(returnStdout: true, script: '@powershell -File Versioning/version.ps1').trim()
|
||||||
if (env.BRANCH_NAME == "master") {
|
if (env.BRANCH_NAME == "Patron") {
|
||||||
buildMode = "Release"
|
buildMode = "Release"
|
||||||
} else {
|
} else {
|
||||||
buildMode = "Debug"
|
buildMode = "Debug"
|
||||||
|
@@ -154,6 +154,8 @@ namespace Torch.API
|
|||||||
/// Raised when the server's Init() method has completed.
|
/// Raised when the server's Init() method has completed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action<ITorchServer> Initialized;
|
event Action<ITorchServer> Initialized;
|
||||||
|
|
||||||
|
TimeSpan ElapsedPlayTime { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
|
using VRage.Game;
|
||||||
|
|
||||||
namespace Torch.API.Session
|
namespace Torch.API.Session
|
||||||
{
|
{
|
||||||
@@ -47,5 +49,29 @@ namespace Torch.API.Session
|
|||||||
/// <returns>true if removed, false if not present</returns>
|
/// <returns>true if removed, false if not present</returns>
|
||||||
/// <exception cref="ArgumentNullException">If the factory is null</exception>
|
/// <exception cref="ArgumentNullException">If the factory is null</exception>
|
||||||
bool RemoveFactory(SessionManagerFactoryDel factory);
|
bool RemoveFactory(SessionManagerFactoryDel factory);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a mod to be injected into client's world download.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool AddOverrideMod(ulong modId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a mod from the injected mod list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool RemoveOverrideMod(ulong modId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List over mods that will be injected into client world downloads.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<MyObjectBuilder_Checkpoint.ModItem> OverrideMods { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised when injected mod list changes.
|
||||||
|
/// </summary>
|
||||||
|
event Action<CollectionChangeEventArgs> OverrideModsChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -111,8 +111,15 @@ quit";
|
|||||||
init.ContinueWith(x => _server.Start());
|
init.ContinueWith(x => _server.Start());
|
||||||
|
|
||||||
Log.Info("Showing UI");
|
Log.Info("Showing UI");
|
||||||
Console.SetOut(TextWriter.Null);
|
|
||||||
NativeMethods.FreeConsole();
|
#if !DEBUG
|
||||||
|
if (!_config.IndependentConsole)
|
||||||
|
{
|
||||||
|
Console.SetOut(TextWriter.Null);
|
||||||
|
NativeMethods.FreeConsole();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
new TorchUI(_server).ShowDialog();
|
new TorchUI(_server).ShowDialog();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -14,6 +15,7 @@ using Sandbox.Game;
|
|||||||
using Sandbox.Game.Gui;
|
using Sandbox.Game.Gui;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
|
using Torch.Collections;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
using Torch.Mod;
|
using Torch.Mod;
|
||||||
using Torch.Server.ViewModels;
|
using Torch.Server.ViewModels;
|
||||||
@@ -102,7 +104,8 @@ namespace Torch.Server.Managers
|
|||||||
//remove the Torch mod to avoid running multiple copies of it
|
//remove the Torch mod to avoid running multiple copies of it
|
||||||
DedicatedConfig.SelectedWorld.Checkpoint.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
DedicatedConfig.SelectedWorld.Checkpoint.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
||||||
foreach (var m in DedicatedConfig.SelectedWorld.Checkpoint.Mods)
|
foreach (var m in DedicatedConfig.SelectedWorld.Checkpoint.Mods)
|
||||||
DedicatedConfig.Mods.Add(m.PublishedFileId);
|
DedicatedConfig.Mods.Add(new ModItemInfo(m));
|
||||||
|
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +119,8 @@ namespace Torch.Server.Managers
|
|||||||
//remove the Torch mod to avoid running multiple copies of it
|
//remove the Torch mod to avoid running multiple copies of it
|
||||||
DedicatedConfig.SelectedWorld.Checkpoint.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
DedicatedConfig.SelectedWorld.Checkpoint.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
||||||
foreach (var m in DedicatedConfig.SelectedWorld.Checkpoint.Mods)
|
foreach (var m in DedicatedConfig.SelectedWorld.Checkpoint.Mods)
|
||||||
DedicatedConfig.Mods.Add(m.PublishedFileId);
|
DedicatedConfig.Mods.Add(new ModItemInfo(m));
|
||||||
|
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,11 +131,10 @@ namespace Torch.Server.Managers
|
|||||||
|
|
||||||
private void ImportWorldConfig(WorldViewModel world, bool modsOnly = true)
|
private void ImportWorldConfig(WorldViewModel world, bool modsOnly = true)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var mods = new MtObservableList<ModItemInfo>();
|
||||||
foreach (var mod in world.Checkpoint.Mods)
|
foreach (var mod in world.Checkpoint.Mods)
|
||||||
sb.AppendLine(mod.PublishedFileId.ToString());
|
mods.Add(new ModItemInfo(mod));
|
||||||
|
DedicatedConfig.Mods = mods;
|
||||||
DedicatedConfig.Mods = world.Checkpoint.Mods.Select(x => x.PublishedFileId).ToList();
|
|
||||||
|
|
||||||
|
|
||||||
Log.Debug("Loaded mod list from world");
|
Log.Debug("Loaded mod list from world");
|
||||||
@@ -159,7 +162,10 @@ namespace Torch.Server.Managers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DedicatedConfig.Mods = checkpoint.Mods.Select(x => x.PublishedFileId).ToList();
|
var mods = new MtObservableList<ModItemInfo>();
|
||||||
|
foreach (var mod in checkpoint.Mods)
|
||||||
|
mods.Add(new ModItemInfo(mod));
|
||||||
|
DedicatedConfig.Mods = mods;
|
||||||
|
|
||||||
Log.Debug("Loaded mod list from world");
|
Log.Debug("Loaded mod list from world");
|
||||||
|
|
||||||
@@ -201,9 +207,14 @@ namespace Torch.Server.Managers
|
|||||||
checkpoint.SessionName = DedicatedConfig.WorldName;
|
checkpoint.SessionName = DedicatedConfig.WorldName;
|
||||||
checkpoint.Settings = DedicatedConfig.SessionSettings;
|
checkpoint.Settings = DedicatedConfig.SessionSettings;
|
||||||
checkpoint.Mods.Clear();
|
checkpoint.Mods.Clear();
|
||||||
|
|
||||||
foreach (var modId in DedicatedConfig.Mods)
|
foreach (var mod in DedicatedConfig.Mods)
|
||||||
checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId));
|
{
|
||||||
|
var savedMod = new MyObjectBuilder_Checkpoint.ModItem(mod.Name, mod.PublishedFileId, mod.FriendlyName);
|
||||||
|
savedMod.IsDependency = mod.IsDependency;
|
||||||
|
checkpoint.Mods.Add(savedMod);
|
||||||
|
}
|
||||||
|
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||||
|
|
||||||
MyObjectBuilderSerializer.SerializeXML(sandboxPath, false, checkpoint);
|
MyObjectBuilderSerializer.SerializeXML(sandboxPath, false, checkpoint);
|
||||||
|
|
||||||
|
@@ -81,6 +81,9 @@
|
|||||||
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
|
<HintPath>..\GameBinaries\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Win32.Registry, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Microsoft.Win32.Registry.4.4.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
<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>
|
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
@@ -88,6 +91,9 @@
|
|||||||
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
|
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="protobuf-net, Version=2.1.0.0, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\protobuf-net.2.1.0\lib\net451\protobuf-net.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
||||||
@@ -127,6 +133,12 @@
|
|||||||
<Reference Include="System.Data" />
|
<Reference Include="System.Data" />
|
||||||
<Reference Include="System.Drawing" />
|
<Reference Include="System.Drawing" />
|
||||||
<Reference Include="System.IO.Compression.FileSystem" />
|
<Reference Include="System.IO.Compression.FileSystem" />
|
||||||
|
<Reference Include="System.Security.AccessControl, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\System.Security.AccessControl.4.4.0\lib\net461\System.Security.AccessControl.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Security.Principal.Windows, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\System.Security.Principal.Windows.4.4.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="System.ServiceProcess" />
|
<Reference Include="System.ServiceProcess" />
|
||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
@@ -246,15 +258,21 @@
|
|||||||
<Compile Include="ViewModels\Entities\CharacterViewModel.cs" />
|
<Compile Include="ViewModels\Entities\CharacterViewModel.cs" />
|
||||||
<Compile Include="ViewModels\ConfigDedicatedViewModel.cs" />
|
<Compile Include="ViewModels\ConfigDedicatedViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Entities\EntityControlViewModel.cs" />
|
<Compile Include="ViewModels\Entities\EntityControlViewModel.cs" />
|
||||||
|
<Compile Include="ViewModels\ModItemInfo.cs" />
|
||||||
<Compile Include="ViewModels\SessionSettingsViewModel.cs" />
|
<Compile Include="ViewModels\SessionSettingsViewModel.cs" />
|
||||||
<Compile Include="Views\Converters\DefinitionToIdConverter.cs" />
|
<Compile Include="Views\Converters\DefinitionToIdConverter.cs" />
|
||||||
<Compile Include="Views\Converters\BooleanAndConverter.cs" />
|
<Compile Include="Views\Converters\BooleanAndConverter.cs" />
|
||||||
<Compile Include="Views\Converters\ListConverter.cs" />
|
<Compile Include="Views\Converters\ListConverter.cs" />
|
||||||
<Compile Include="MultiTextWriter.cs" />
|
<Compile Include="MultiTextWriter.cs" />
|
||||||
<Compile Include="RichTextBoxWriter.cs" />
|
<Compile Include="RichTextBoxWriter.cs" />
|
||||||
|
<Compile Include="Views\Converters\ListConverterWorkshopId.cs" />
|
||||||
|
<Compile Include="Views\Converters\ModToIdConverter.cs" />
|
||||||
<Compile Include="Views\Entities\CharacterView.xaml.cs">
|
<Compile Include="Views\Entities\CharacterView.xaml.cs">
|
||||||
<DependentUpon>CharacterView.xaml</DependentUpon>
|
<DependentUpon>CharacterView.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Views\ModListControl.xaml.cs">
|
||||||
|
<DependentUpon>ModListControl.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Views\ThemeControl.xaml.cs">
|
<Compile Include="Views\ThemeControl.xaml.cs">
|
||||||
<DependentUpon>ThemeControl.xaml</DependentUpon>
|
<DependentUpon>ThemeControl.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -415,6 +433,10 @@
|
|||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Include="Views\ModListControl.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
<Page Include="Views\PluginsControl.xaml">
|
<Page Include="Views\PluginsControl.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
@@ -63,8 +63,8 @@ namespace Torch.Server
|
|||||||
public bool EnableWhitelist { get; set; } = false;
|
public bool EnableWhitelist { get; set; } = false;
|
||||||
public HashSet<ulong> Whitelist { get; set; } = new HashSet<ulong>();
|
public HashSet<ulong> Whitelist { get; set; } = new HashSet<ulong>();
|
||||||
|
|
||||||
internal Point WindowSize { get; set; } = new Point(800, 600);
|
public Point WindowSize { get; set; } = new Point(800, 600);
|
||||||
internal Point WindowPosition { get; set; } = new Point();
|
public Point WindowPosition { get; set; } = new Point();
|
||||||
|
|
||||||
public string LastUsedTheme { get; set; } = "Torch Theme";
|
public string LastUsedTheme { get; set; } = "Torch Theme";
|
||||||
|
|
||||||
@@ -72,10 +72,13 @@ namespace Torch.Server
|
|||||||
[Obsolete("Use vanilla reserved slot config")]
|
[Obsolete("Use vanilla reserved slot config")]
|
||||||
public HashSet<ulong> ReservedPlayers { get; set; } = new HashSet<ulong>();
|
public HashSet<ulong> ReservedPlayers { get; set; } = new HashSet<ulong>();
|
||||||
|
|
||||||
//Prevent reserved players being written to disk, but allow it to bre read
|
//Prevent reserved players being written to disk, but allow it to be read
|
||||||
//remove this when ReservedPlayers is removed
|
//remove this when ReservedPlayers is removed
|
||||||
private bool ShouldSerializeReservedPlayers() => false;
|
private bool ShouldSerializeReservedPlayers() => false;
|
||||||
|
|
||||||
|
[Arg("console", "Keeps a separate console window open after the main UI loads.")]
|
||||||
|
public bool IndependentConsole { get; set; } = false;
|
||||||
|
|
||||||
[XmlIgnore]
|
[XmlIgnore]
|
||||||
private string _path;
|
private string _path;
|
||||||
|
|
||||||
|
@@ -195,8 +195,7 @@ namespace Torch.Server
|
|||||||
public override void Init(object gameInstance)
|
public override void Init(object gameInstance)
|
||||||
{
|
{
|
||||||
base.Init(gameInstance);
|
base.Init(gameInstance);
|
||||||
var game = gameInstance as MySandboxGame;
|
if (gameInstance is MySandboxGame && MySession.Static != null)
|
||||||
if (game != null && MySession.Static != null)
|
|
||||||
State = ServerState.Running;
|
State = ServerState.Running;
|
||||||
else
|
else
|
||||||
State = ServerState.Stopped;
|
State = ServerState.Stopped;
|
||||||
|
@@ -10,6 +10,8 @@ using Torch.Collections;
|
|||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
|
using Torch.Utils.SteamWorkshopTools;
|
||||||
|
using Torch.Collections;
|
||||||
|
|
||||||
namespace Torch.Server.ViewModels
|
namespace Torch.Server.ViewModels
|
||||||
{
|
{
|
||||||
@@ -29,6 +31,7 @@ namespace Torch.Server.ViewModels
|
|||||||
_config = configDedicated;
|
_config = configDedicated;
|
||||||
_config.IgnoreLastSession = true;
|
_config.IgnoreLastSession = true;
|
||||||
SessionSettings = new SessionSettingsViewModel(_config.SessionSettings);
|
SessionSettings = new SessionSettingsViewModel(_config.SessionSettings);
|
||||||
|
Task.Run(() => UpdateAllModInfosAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save(string path = null)
|
public void Save(string path = null)
|
||||||
@@ -73,14 +76,61 @@ namespace Torch.Server.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAllModInfosAsync(Action<string> messageHandler = null)
|
||||||
|
{
|
||||||
|
if (Mods.Count() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ids = Mods.Select(m => m.PublishedFileId);
|
||||||
|
var workshopService = WebAPI.Instance;
|
||||||
|
Dictionary<ulong, PublishedItemDetails> modInfos = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
modInfos = (await workshopService.GetPublishedFileDetails(ids.ToArray()));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info($"Mods Info successfully retrieved!");
|
||||||
|
|
||||||
|
foreach (var mod in Mods)
|
||||||
|
{
|
||||||
|
if (!modInfos.ContainsKey(mod.PublishedFileId) || modInfos[mod.PublishedFileId] == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Failed to retrieve info for mod with workshop id '{mod.PublishedFileId}'!");
|
||||||
|
}
|
||||||
|
//else if (!modInfo.Tags.Contains(""))
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mod.FriendlyName = modInfos[mod.PublishedFileId].Title;
|
||||||
|
mod.Description = modInfos[mod.PublishedFileId].Description;
|
||||||
|
//mod.Name = modInfos[mod.PublishedFileId].FileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public List<string> Administrators { get => _config.Administrators; set => SetValue(x => _config.Administrators = x, value); }
|
public List<string> Administrators { get => _config.Administrators; set => SetValue(x => _config.Administrators = x, value); }
|
||||||
|
|
||||||
public List<ulong> Banned { get => _config.Banned; set => SetValue(x => _config.Banned = x, value); }
|
public List<ulong> Banned { get => _config.Banned; set => SetValue(x => _config.Banned = x, value); }
|
||||||
|
|
||||||
|
private MtObservableList<ModItemInfo> _mods = new MtObservableList<ModItemInfo>();
|
||||||
|
public MtObservableList<ModItemInfo> Mods
|
||||||
|
{
|
||||||
|
get => _mods;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetValue(x => _mods = x, value);
|
||||||
|
Task.Run(() => UpdateAllModInfosAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<ulong> Reserved { get => _config.Reserved; set => SetValue(x => _config.Reserved = x, value); }
|
public List<ulong> Reserved { get => _config.Reserved; set => SetValue(x => _config.Reserved = x, value); }
|
||||||
|
|
||||||
private List<ulong> _mods = new List<ulong>();
|
|
||||||
public List<ulong> Mods { get => _mods; set => SetValue(x => _mods = x, value); }
|
|
||||||
|
|
||||||
public int AsteroidAmount { get => _config.AsteroidAmount; set => SetValue(x => _config.AsteroidAmount = x, value); }
|
public int AsteroidAmount { get => _config.AsteroidAmount; set => SetValue(x => _config.AsteroidAmount = x, value); }
|
||||||
|
|
||||||
|
@@ -1,8 +1,14 @@
|
|||||||
using System.Windows.Controls;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox.Game.Entities;
|
||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
using Torch.Collections;
|
using Torch.Collections;
|
||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
|
using Torch.Utils;
|
||||||
using VRage.Game.Entity;
|
using VRage.Game.Entity;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
using VRage.ModAPI;
|
using VRage.ModAPI;
|
||||||
@@ -14,6 +20,8 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
{
|
{
|
||||||
protected EntityTreeViewModel Tree { get; }
|
protected EntityTreeViewModel Tree { get; }
|
||||||
|
|
||||||
|
private static Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
private IMyEntity _backing;
|
private IMyEntity _backing;
|
||||||
public IMyEntity Entity
|
public IMyEntity Entity
|
||||||
{
|
{
|
||||||
@@ -43,6 +51,75 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string _descriptiveName;
|
||||||
|
public string DescriptiveName
|
||||||
|
{
|
||||||
|
get => _descriptiveName ?? (_descriptiveName = GetSortedName(EntityTreeViewModel.SortEnum.Name));
|
||||||
|
set => _descriptiveName = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetSortedName(EntityTreeViewModel.SortEnum sort)
|
||||||
|
{
|
||||||
|
switch (sort)
|
||||||
|
{
|
||||||
|
case EntityTreeViewModel.SortEnum.Name:
|
||||||
|
return Name;
|
||||||
|
case EntityTreeViewModel.SortEnum.Size:
|
||||||
|
return $"{Name} ({Entity.WorldVolume.Radius * 2:N}m)";
|
||||||
|
case EntityTreeViewModel.SortEnum.Speed:
|
||||||
|
return $"{Name} ({Entity.Physics?.LinearVelocity.Length() ?? 0:N}m/s)";
|
||||||
|
case EntityTreeViewModel.SortEnum.BlockCount:
|
||||||
|
if (Entity is MyCubeGrid grid)
|
||||||
|
return $"{Name} ({grid.BlocksCount} blocks)";
|
||||||
|
return Name;
|
||||||
|
case EntityTreeViewModel.SortEnum.DistFromCenter:
|
||||||
|
return $"{Name} ({Entity.GetPosition().Length():N}m)";
|
||||||
|
case EntityTreeViewModel.SortEnum.Owner:
|
||||||
|
if (Entity is MyCubeGrid g)
|
||||||
|
return $"{Name} ({g.GetGridOwnerName()})";
|
||||||
|
return Name;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(sort), sort, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual int CompareToSort(EntityViewModel other, EntityTreeViewModel.SortEnum sort)
|
||||||
|
{
|
||||||
|
switch (sort)
|
||||||
|
{
|
||||||
|
case EntityTreeViewModel.SortEnum.Name:
|
||||||
|
return string.Compare(Name, other.Name, StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
case EntityTreeViewModel.SortEnum.Size:
|
||||||
|
return Entity.WorldVolume.Radius.CompareTo(other.Entity.WorldVolume.Radius);
|
||||||
|
case EntityTreeViewModel.SortEnum.Speed:
|
||||||
|
if (Entity.Physics == null)
|
||||||
|
{
|
||||||
|
if (other.Entity.Physics == null)
|
||||||
|
return 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (other.Entity.Physics == null)
|
||||||
|
return 1;
|
||||||
|
return Entity.Physics.LinearVelocity.LengthSquared().CompareTo(other.Entity.Physics.LinearVelocity.LengthSquared());
|
||||||
|
case EntityTreeViewModel.SortEnum.BlockCount:
|
||||||
|
{
|
||||||
|
if (Entity is MyCubeGrid ga && other.Entity is MyCubeGrid gb)
|
||||||
|
return ga.BlocksCount.CompareTo(gb.BlocksCount);
|
||||||
|
goto case EntityTreeViewModel.SortEnum.Name;
|
||||||
|
}
|
||||||
|
case EntityTreeViewModel.SortEnum.DistFromCenter:
|
||||||
|
return Entity.GetPosition().LengthSquared().CompareTo(other.Entity.GetPosition().LengthSquared());
|
||||||
|
case EntityTreeViewModel.SortEnum.Owner:
|
||||||
|
{
|
||||||
|
if (Entity is MyCubeGrid ga && other.Entity is MyCubeGrid gb)
|
||||||
|
return string.Compare(ga.GetGridOwnerName(), gb.GetGridOwnerName(), StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
goto case EntityTreeViewModel.SortEnum.Name;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(sort), sort, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public virtual string Position
|
public virtual string Position
|
||||||
{
|
{
|
||||||
get => Entity?.GetPosition().ToString();
|
get => Entity?.GetPosition().ToString();
|
||||||
@@ -76,5 +153,20 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Comparer : IComparer<EntityViewModel>
|
||||||
|
{
|
||||||
|
private EntityTreeViewModel.SortEnum _sort;
|
||||||
|
|
||||||
|
public Comparer(EntityTreeViewModel.SortEnum sort)
|
||||||
|
{
|
||||||
|
_sort = sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Compare(EntityViewModel x, EntityViewModel y)
|
||||||
|
{
|
||||||
|
return x.CompareToSort(y, _sort);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -50,17 +50,14 @@ namespace Torch.Server.ViewModels.Entities
|
|||||||
Blocks { get; } =
|
Blocks { get; } =
|
||||||
new MtObservableSortedDictionary<MyCubeBlockDefinition, MtObservableSortedDictionary<long, BlockViewModel>>(
|
new MtObservableSortedDictionary<MyCubeBlockDefinition, MtObservableSortedDictionary<long, BlockViewModel>>(
|
||||||
CubeBlockDefinitionComparer.Default);
|
CubeBlockDefinitionComparer.Default);
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string DescriptiveName { get; }
|
|
||||||
|
|
||||||
public GridViewModel()
|
public GridViewModel()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public GridViewModel(MyCubeGrid grid, EntityTreeViewModel tree) : base(grid, tree)
|
public GridViewModel(MyCubeGrid grid, EntityTreeViewModel tree) : base(grid, tree)
|
||||||
{
|
{
|
||||||
DescriptiveName = $"{grid.DisplayName} ({grid.BlocksCount} blocks)";
|
//DescriptiveName = $"{grid.DisplayName} ({grid.BlocksCount} blocks)";
|
||||||
Blocks.Add(_fillerDefinition, new MtObservableSortedDictionary<long, BlockViewModel>());
|
Blocks.Add(_fillerDefinition, new MtObservableSortedDictionary<long, BlockViewModel>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,11 +12,21 @@ using VRage.ModAPI;
|
|||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Torch.Collections;
|
using Torch.Collections;
|
||||||
|
using Torch.Server.Views.Entities;
|
||||||
|
|
||||||
namespace Torch.Server.ViewModels
|
namespace Torch.Server.ViewModels
|
||||||
{
|
{
|
||||||
public class EntityTreeViewModel : ViewModel
|
public class EntityTreeViewModel : ViewModel
|
||||||
{
|
{
|
||||||
|
public enum SortEnum
|
||||||
|
{
|
||||||
|
Name,
|
||||||
|
Size,
|
||||||
|
Speed,
|
||||||
|
Owner,
|
||||||
|
BlockCount,
|
||||||
|
DistFromCenter,
|
||||||
|
}
|
||||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
//TODO: these should be sorted sets for speed
|
//TODO: these should be sorted sets for speed
|
||||||
@@ -26,7 +36,13 @@ namespace Torch.Server.ViewModels
|
|||||||
public MtObservableSortedDictionary<long, VoxelMapViewModel> VoxelMaps { get; set; } = new MtObservableSortedDictionary<long, VoxelMapViewModel>();
|
public MtObservableSortedDictionary<long, VoxelMapViewModel> VoxelMaps { get; set; } = new MtObservableSortedDictionary<long, VoxelMapViewModel>();
|
||||||
public Dispatcher ControlDispatcher => _control.Dispatcher;
|
public Dispatcher ControlDispatcher => _control.Dispatcher;
|
||||||
|
|
||||||
|
public SortedView<GridViewModel> SortedGrids { get; }
|
||||||
|
public SortedView<CharacterViewModel> SortedCharacters { get; }
|
||||||
|
public SortedView<EntityViewModel> SortedFloatingObjects { get; }
|
||||||
|
public SortedView<VoxelMapViewModel> SortedVoxelMaps { get; }
|
||||||
|
|
||||||
private EntityViewModel _currentEntity;
|
private EntityViewModel _currentEntity;
|
||||||
|
private SortEnum _currentSort;
|
||||||
private UserControl _control;
|
private UserControl _control;
|
||||||
|
|
||||||
public EntityViewModel CurrentEntity
|
public EntityViewModel CurrentEntity
|
||||||
@@ -35,6 +51,12 @@ namespace Torch.Server.ViewModels
|
|||||||
set { _currentEntity = value; OnPropertyChanged(nameof(CurrentEntity)); }
|
set { _currentEntity = value; OnPropertyChanged(nameof(CurrentEntity)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SortEnum CurrentSort
|
||||||
|
{
|
||||||
|
get => _currentSort;
|
||||||
|
set => SetValue(ref _currentSort, value);
|
||||||
|
}
|
||||||
|
|
||||||
// I hate you today WPF
|
// I hate you today WPF
|
||||||
public EntityTreeViewModel() : this(null)
|
public EntityTreeViewModel() : this(null)
|
||||||
{
|
{
|
||||||
@@ -43,6 +65,11 @@ namespace Torch.Server.ViewModels
|
|||||||
public EntityTreeViewModel(UserControl control)
|
public EntityTreeViewModel(UserControl control)
|
||||||
{
|
{
|
||||||
_control = control;
|
_control = control;
|
||||||
|
var comparer = new EntityViewModel.Comparer(_currentSort);
|
||||||
|
SortedGrids = new SortedView<GridViewModel>(Grids.Values, comparer);
|
||||||
|
SortedCharacters = new SortedView<CharacterViewModel>(Characters.Values, comparer);
|
||||||
|
SortedFloatingObjects = new SortedView<EntityViewModel>(FloatingObjects.Values, comparer);
|
||||||
|
SortedVoxelMaps = new SortedView<VoxelMapViewModel>(VoxelMaps.Values, comparer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
@@ -85,16 +112,16 @@ namespace Torch.Server.ViewModels
|
|||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
case MyCubeGrid grid:
|
case MyCubeGrid grid:
|
||||||
Grids.Add(obj.EntityId, new GridViewModel(grid, this));
|
Grids.Add(grid.EntityId, new GridViewModel(grid, this));
|
||||||
break;
|
break;
|
||||||
case MyCharacter character:
|
case MyCharacter character:
|
||||||
Characters.Add(obj.EntityId, new CharacterViewModel(character, this));
|
Characters.Add(character.EntityId, new CharacterViewModel(character, this));
|
||||||
break;
|
break;
|
||||||
case MyFloatingObject floating:
|
case MyFloatingObject floating:
|
||||||
FloatingObjects.Add(obj.EntityId, new FloatingObjectViewModel(floating, this));
|
FloatingObjects.Add(floating.EntityId, new FloatingObjectViewModel(floating, this));
|
||||||
break;
|
break;
|
||||||
case MyVoxelBase voxel:
|
case MyVoxelBase voxel:
|
||||||
VoxelMaps.Add(obj.EntityId, new VoxelMapViewModel(voxel, this));
|
VoxelMaps.Add(voxel.EntityId, new VoxelMapViewModel(voxel, this));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
131
Torch.Server/ViewModels/ModItemInfo.cs
Normal file
131
Torch.Server/ViewModels/ModItemInfo.cs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using NLog;
|
||||||
|
using VRage.Game;
|
||||||
|
using Torch.Server.Annotations;
|
||||||
|
using Torch.Utils.SteamWorkshopTools;
|
||||||
|
|
||||||
|
namespace Torch.Server.ViewModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper around VRage.Game.Objectbuilder_Checkpoint.ModItem
|
||||||
|
/// that holds additional meta information
|
||||||
|
/// (e.g. workshop description)
|
||||||
|
/// </summary>
|
||||||
|
public class ModItemInfo : ViewModel
|
||||||
|
{
|
||||||
|
MyObjectBuilder_Checkpoint.ModItem _modItem;
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Human friendly name of the mod
|
||||||
|
/// </summary>
|
||||||
|
public string FriendlyName
|
||||||
|
{
|
||||||
|
get { return _modItem.FriendlyName; }
|
||||||
|
set {
|
||||||
|
SetValue(ref _modItem.FriendlyName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Workshop ID of the mod
|
||||||
|
/// </summary>
|
||||||
|
public ulong PublishedFileId
|
||||||
|
{
|
||||||
|
get { return _modItem.PublishedFileId; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetValue(ref _modItem.PublishedFileId, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Local filename of the mod
|
||||||
|
/// </summary>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return _modItem.Name; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetValue(ref _modItem.FriendlyName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the mod was added
|
||||||
|
/// because another mod depends on it
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDependency
|
||||||
|
{
|
||||||
|
get { return _modItem.IsDependency; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetValue(ref _modItem.IsDependency, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _description;
|
||||||
|
/// <summary>
|
||||||
|
/// Workshop description of the mod
|
||||||
|
/// </summary>
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get { return _description; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetValue(ref _description, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor, returns a new ModItemInfo instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mod">The wrapped mod</param>
|
||||||
|
public ModItemInfo(MyObjectBuilder_Checkpoint.ModItem mod)
|
||||||
|
{
|
||||||
|
_modItem = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve information about the
|
||||||
|
/// wrapped mod from the workhop asynchronously
|
||||||
|
/// via the Steam web API.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> UpdateModInfoAsync()
|
||||||
|
{
|
||||||
|
var msg = "";
|
||||||
|
var workshopService = WebAPI.Instance;
|
||||||
|
PublishedItemDetails modInfo = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
modInfo = (await workshopService.GetPublishedFileDetails(new ulong[] { PublishedFileId }))?[PublishedFileId];
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
Log.Error(e.Message);
|
||||||
|
}
|
||||||
|
if (modInfo == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Failed to retrieve mod with workshop id '{PublishedFileId}'!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//else if (!modInfo.Tags.Contains(""))
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Info($"Mod Info successfully retrieved!");
|
||||||
|
FriendlyName = modInfo.Title;
|
||||||
|
Description = modInfo.Description;
|
||||||
|
//Name = modInfo.FileName;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -58,7 +58,7 @@
|
|||||||
<RowDefinition />
|
<RowDefinition />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Disabled">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
@@ -95,19 +95,6 @@
|
|||||||
<CheckBox IsChecked="{Binding AutodetectDependencies}" Content="Auto Detect Dependencies" Margin="3" />
|
<CheckBox IsChecked="{Binding AutodetectDependencies}" Content="Auto Detect Dependencies" Margin="3" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1" Margin="3">
|
<StackPanel Grid.Column="1" Margin="3">
|
||||||
<Label Content="Mods" />
|
|
||||||
<TextBox Margin="3" Height="60" AcceptsReturn="true" VerticalScrollBarVisibility="Auto"
|
|
||||||
Style="{StaticResource ValidatedTextBox}">
|
|
||||||
<TextBox.Text>
|
|
||||||
<Binding Path="Mods" UpdateSourceTrigger="PropertyChanged"
|
|
||||||
ValidatesOnDataErrors="True" NotifyOnValidationError="True"
|
|
||||||
Converter="{StaticResource ListConverterUInt64}">
|
|
||||||
<Binding.ValidationRules>
|
|
||||||
<validationRules:ListConverterValidationRule Type="system:UInt64" />
|
|
||||||
</Binding.ValidationRules>
|
|
||||||
</Binding>
|
|
||||||
</TextBox.Text>
|
|
||||||
</TextBox>
|
|
||||||
<Label Content="Administrators" />
|
<Label Content="Administrators" />
|
||||||
<TextBox Text="{Binding Administrators, Converter={StaticResource ListConverterString}}"
|
<TextBox Text="{Binding Administrators, Converter={StaticResource ListConverterString}}"
|
||||||
Margin="3"
|
Margin="3"
|
||||||
|
77
Torch.Server/Views/Converters/ListConverterWorkshopId.cs
Normal file
77
Torch.Server/Views/Converters/ListConverterWorkshopId.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using Torch.Server.ViewModels;
|
||||||
|
using VRage.Game;
|
||||||
|
|
||||||
|
namespace Torch.Server.Views.Converters
|
||||||
|
{
|
||||||
|
class ListConverterWorkshopId : IValueConverter
|
||||||
|
{
|
||||||
|
public Type Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a list of ModItemInfo objects into a list of their workshop IDs (PublishedFileIds).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="valueList">
|
||||||
|
/// Expected to contain a list of ModItemInfo objects
|
||||||
|
/// </param>
|
||||||
|
/// <param name="targetType">This parameter will be ignored</param>
|
||||||
|
/// <param name="parameter">This parameter will be ignored</param>
|
||||||
|
/// <param name="culture"> This parameter will be ignored</param>
|
||||||
|
/// <returns>A string containing the workshop ids of all mods, one per line</returns>
|
||||||
|
public object Convert(object valueList, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (!(valueList is IList list))
|
||||||
|
throw new InvalidOperationException("Value is not the proper type.");
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var item in list)
|
||||||
|
{
|
||||||
|
sb.AppendLine(((ModItemInfo) item).PublishedFileId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a list of workshop ids into a list of ModItemInfo objects
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">A string containing workshop ids separated by new lines</param>
|
||||||
|
/// <param name="targetType">This parameter will be ignored</param>
|
||||||
|
/// <param name="parameter">
|
||||||
|
/// A list of ModItemInfos which should
|
||||||
|
/// contain the requestted mods
|
||||||
|
/// (or they will be dropped)
|
||||||
|
/// </param>
|
||||||
|
/// <param name="culture">This parameter will be ignored</param>
|
||||||
|
/// <returns>A list of ModItemInfo objects</returns>
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(Type));
|
||||||
|
var mods = parameter as ICollection<ModItemInfo>;
|
||||||
|
if (mods == null)
|
||||||
|
throw new ArgumentException("parameter needs to be of type ICollection<ModItemInfo>!");
|
||||||
|
var input = ((string)value).Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var item in input)
|
||||||
|
{
|
||||||
|
if( ulong.TryParse(item, out ulong id))
|
||||||
|
{
|
||||||
|
var mod = mods.FirstOrDefault((m) => m.PublishedFileId == id);
|
||||||
|
if (mod != null)
|
||||||
|
list.Add(mod);
|
||||||
|
else
|
||||||
|
list.Add(new MyObjectBuilder_Checkpoint.ModItem(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
Torch.Server/Views/Converters/ModToIdConverter.cs
Normal file
53
Torch.Server/Views/Converters/ModToIdConverter.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Torch.Server.ViewModels;
|
||||||
|
using NLog;
|
||||||
|
using Torch.Collections;
|
||||||
|
|
||||||
|
namespace Torch.Server.Views.Converters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A converter to get the index of a ModItemInfo object within a collection of ModItemInfo objects
|
||||||
|
/// </summary>
|
||||||
|
public class ModToListIdConverter : IMultiValueConverter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a ModItemInfo object into its index within a Collection of ModItemInfo objects
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values">
|
||||||
|
/// Expected to contain a ModItemInfo object at index 0
|
||||||
|
/// and a Collection of ModItemInfo objects at index 1
|
||||||
|
/// </param>
|
||||||
|
/// <param name="targetType">This parameter will be ignored</param>
|
||||||
|
/// <param name="parameter">This parameter will be ignored</param>
|
||||||
|
/// <param name="culture"> This parameter will be ignored</param>
|
||||||
|
/// <returns>the index of the mod within the provided mod list.</returns>
|
||||||
|
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
//if (targetType != typeof(int))
|
||||||
|
// throw new NotSupportedException("ModToIdConverter can only convert mods into int values or vise versa!");
|
||||||
|
var mod = (ModItemInfo) values[0];
|
||||||
|
var theModList = (MtObservableList<ModItemInfo>) values[1];
|
||||||
|
return theModList.IndexOf(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// It is not supported to reverse this converter
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="values"></param>
|
||||||
|
/// <param name="targetType"></param>
|
||||||
|
/// <param name="parameter"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns>Raises a NotSupportedException</returns>
|
||||||
|
public object[] ConvertBack(object values, Type[] targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModToIdConverter can not convert back!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -22,10 +22,11 @@
|
|||||||
<Grid Grid.Column="0">
|
<Grid Grid.Column="0">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition />
|
<RowDefinition />
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TreeView Grid.Row="0" Margin="3" DockPanel.Dock="Top" SelectedItemChanged="TreeView_OnSelectedItemChanged"
|
<TreeView Grid.Row="0" Margin="3" DockPanel.Dock="Top" SelectedItemChanged="TreeView_OnSelectedItemChanged"
|
||||||
TreeViewItem.Expanded="TreeViewItem_OnExpanded">
|
TreeViewItem.Expanded="TreeViewItem_OnExpanded" Name="EntityTree">
|
||||||
<TreeView.Resources>
|
<TreeView.Resources>
|
||||||
<HierarchicalDataTemplate DataType="{x:Type entities:GridViewModel}"
|
<HierarchicalDataTemplate DataType="{x:Type entities:GridViewModel}"
|
||||||
ItemsSource="{Binding Path=Blocks}">
|
ItemsSource="{Binding Path=Blocks}">
|
||||||
@@ -46,46 +47,47 @@
|
|||||||
</HierarchicalDataTemplate>
|
</HierarchicalDataTemplate>
|
||||||
<HierarchicalDataTemplate DataType="{x:Type entities:VoxelMapViewModel}"
|
<HierarchicalDataTemplate DataType="{x:Type entities:VoxelMapViewModel}"
|
||||||
ItemsSource="{Binding AttachedGrids}">
|
ItemsSource="{Binding AttachedGrids}">
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Text="{Binding DescriptiveName}" />
|
||||||
</HierarchicalDataTemplate>
|
</HierarchicalDataTemplate>
|
||||||
</TreeView.Resources>
|
</TreeView.Resources>
|
||||||
<TreeViewItem ItemsSource="{Binding Path=Grids.Values}">
|
<TreeViewItem ItemsSource="{Binding Path=SortedGrids}">
|
||||||
<TreeViewItem.Header>
|
<TreeViewItem.Header>
|
||||||
<TextBlock Text="{Binding Grids.Count, StringFormat=Grids ({0})}" />
|
<TextBlock Text="{Binding SortedGrids.Count, StringFormat=Grids ({0})}" />
|
||||||
</TreeViewItem.Header>
|
</TreeViewItem.Header>
|
||||||
</TreeViewItem>
|
</TreeViewItem>
|
||||||
<TreeViewItem ItemsSource="{Binding Characters.Values}">
|
<TreeViewItem ItemsSource="{Binding SortedCharacters}">
|
||||||
<TreeViewItem.Header>
|
<TreeViewItem.Header>
|
||||||
<TextBlock Text="{Binding Characters.Count, StringFormat=Characters ({0})}" />
|
<TextBlock Text="{Binding SortedCharacters.Count, StringFormat=Characters ({0})}" />
|
||||||
</TreeViewItem.Header>
|
</TreeViewItem.Header>
|
||||||
<TreeViewItem.ItemTemplate>
|
<TreeViewItem.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Text="{Binding DescriptiveName}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</TreeViewItem.ItemTemplate>
|
</TreeViewItem.ItemTemplate>
|
||||||
</TreeViewItem>
|
</TreeViewItem>
|
||||||
<TreeViewItem ItemsSource="{Binding VoxelMaps.Values}">
|
<TreeViewItem ItemsSource="{Binding SortedVoxelMaps}">
|
||||||
<TreeViewItem.Header>
|
<TreeViewItem.Header>
|
||||||
<TextBlock Text="{Binding VoxelMaps.Count, StringFormat=Voxel Maps ({0})}" />
|
<TextBlock Text="{Binding SortedVoxelMaps.Count, StringFormat=Voxel Maps ({0})}" />
|
||||||
</TreeViewItem.Header>
|
</TreeViewItem.Header>
|
||||||
<TreeViewItem.ItemTemplate>
|
<TreeViewItem.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Text="{Binding DescriptiveName}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</TreeViewItem.ItemTemplate>
|
</TreeViewItem.ItemTemplate>
|
||||||
</TreeViewItem>
|
</TreeViewItem>
|
||||||
<TreeViewItem ItemsSource="{Binding FloatingObjects.Values}">
|
<TreeViewItem ItemsSource="{Binding SortedFloatingObjects}">
|
||||||
<TreeViewItem.Header>
|
<TreeViewItem.Header>
|
||||||
<TextBlock Text="{Binding FloatingObjects.Count, StringFormat=Floating Objects ({0})}" />
|
<TextBlock Text="{Binding SortedFloatingObjects.Count, StringFormat=Floating Objects ({0})}" />
|
||||||
</TreeViewItem.Header>
|
</TreeViewItem.Header>
|
||||||
<TreeViewItem.ItemTemplate>
|
<TreeViewItem.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Text="{Binding DescriptiveName}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</TreeViewItem.ItemTemplate>
|
</TreeViewItem.ItemTemplate>
|
||||||
</TreeViewItem>
|
</TreeViewItem>
|
||||||
</TreeView>
|
</TreeView>
|
||||||
<StackPanel Grid.Row="1" DockPanel.Dock="Bottom">
|
<ComboBox Grid.Row="1" Margin="3" Name="SortCombo" SelectionChanged="SortCombo_SelectionChanged"/>
|
||||||
|
<StackPanel Grid.Row="2" DockPanel.Dock="Bottom">
|
||||||
<Button Content="Delete" Click="Delete_OnClick" IsEnabled="{Binding CurrentEntity.CanDelete}"
|
<Button Content="Delete" Click="Delete_OnClick" IsEnabled="{Binding CurrentEntity.CanDelete}"
|
||||||
Margin="3" />
|
Margin="3" />
|
||||||
<Button Content="Stop" Click="Stop_OnClick" IsEnabled="{Binding CurrentEntity.CanStop}" Margin="3" />
|
<Button Content="Stop" Click="Stop_OnClick" IsEnabled="{Binding CurrentEntity.CanStop}" Margin="3" />
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -13,6 +14,7 @@ using System.Windows.Media.Imaging;
|
|||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using Torch.Collections;
|
||||||
using Torch.Server.ViewModels;
|
using Torch.Server.ViewModels;
|
||||||
using Torch.Server.ViewModels.Blocks;
|
using Torch.Server.ViewModels.Blocks;
|
||||||
using Torch.Server.ViewModels.Entities;
|
using Torch.Server.ViewModels.Entities;
|
||||||
@@ -29,14 +31,17 @@ namespace Torch.Server.Views
|
|||||||
{
|
{
|
||||||
public EntityTreeViewModel Entities { get; set; }
|
public EntityTreeViewModel Entities { get; set; }
|
||||||
|
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
public EntitiesControl()
|
public EntitiesControl()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Entities = new EntityTreeViewModel(this);
|
Entities = new EntityTreeViewModel(this);
|
||||||
DataContext = Entities;
|
DataContext = Entities;
|
||||||
Entities.Init();
|
Entities.Init();
|
||||||
|
SortCombo.ItemsSource = Enum.GetNames(typeof(EntityTreeViewModel.SortEnum));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||||
{
|
{
|
||||||
if (e.NewValue is EntityViewModel vm)
|
if (e.NewValue is EntityViewModel vm)
|
||||||
@@ -77,5 +82,30 @@ namespace Torch.Server.Views
|
|||||||
if (item.DataContext is ILazyLoad l)
|
if (item.DataContext is ILazyLoad l)
|
||||||
l.Load();
|
l.Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SortCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var sort = (EntityTreeViewModel.SortEnum)SortCombo.SelectedIndex;
|
||||||
|
|
||||||
|
var comparer = new EntityViewModel.Comparer(sort);
|
||||||
|
|
||||||
|
Task[] sortTasks = new Task[4];
|
||||||
|
|
||||||
|
Entities.CurrentSort = sort;
|
||||||
|
Entities.SortedCharacters.SetComparer(comparer);
|
||||||
|
Entities.SortedFloatingObjects.SetComparer(comparer);
|
||||||
|
Entities.SortedGrids.SetComparer(comparer);
|
||||||
|
Entities.SortedVoxelMaps.SetComparer(comparer);
|
||||||
|
|
||||||
|
foreach (var i in Entities.SortedCharacters)
|
||||||
|
i.DescriptiveName = i.GetSortedName(sort);
|
||||||
|
foreach (var i in Entities.SortedFloatingObjects)
|
||||||
|
i.DescriptiveName = i.GetSortedName(sort);
|
||||||
|
foreach (var i in Entities.SortedGrids)
|
||||||
|
i.DescriptiveName = i.GetSortedName(sort);
|
||||||
|
foreach (var i in Entities.SortedVoxelMaps)
|
||||||
|
i.DescriptiveName = i.GetSortedName(sort);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
127
Torch.Server/Views/ModListControl.xaml
Normal file
127
Torch.Server/Views/ModListControl.xaml
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<UserControl x:Class="Torch.Server.Views.ModListControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:viewModels="clr-namespace:Torch.Server.ViewModels"
|
||||||
|
xmlns:s="clr-namespace:System"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
MouseMove="UserControl_MouseMove">
|
||||||
|
<!--<UserControl.DataContext>
|
||||||
|
<viewModels:ConfigDedicatedViewModel />
|
||||||
|
</UserControl.DataContext>-->
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="Resources.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
<Style TargetType="Grid" x:Key="RootGridStyle">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Mode=OneWay, UpdateSourceTrigger=PropertyChanged, BindingGroupName=RootEnabledBinding}" Value="{x:Null}">
|
||||||
|
<Setter Property="IsEnabled" Value="False"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<UserControl.DataContext>
|
||||||
|
<viewModels:ConfigDedicatedViewModel />
|
||||||
|
</UserControl.DataContext>
|
||||||
|
<Grid Style="{StaticResource RootGridStyle}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="500px"/>
|
||||||
|
<ColumnDefinition Width="10px"/>
|
||||||
|
<ColumnDefinition Width="*" MinWidth="200px"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="80px"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<DataGrid Name="ModList" Grid.Column="0" Grid.ColumnSpan="1" ItemsSource="{Binding UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Sorting="ModList_Sorting"
|
||||||
|
SelectionMode="Single"
|
||||||
|
SelectionUnit="FullRow"
|
||||||
|
AllowDrop="True"
|
||||||
|
CanUserReorderColumns="False"
|
||||||
|
CanUserSortColumns="True"
|
||||||
|
PreviewMouseLeftButtonDown="ModList_MouseLeftButtonDown"
|
||||||
|
MouseLeftButtonUp="ModList_MouseLeftButtonUp"
|
||||||
|
SelectedCellsChanged="ModList_Selected"
|
||||||
|
AutoGenerateColumns="False">
|
||||||
|
<!--:DesignSource="{d:DesignInstance Type={x:Type MyObjectBuilder_Checkpoint:ModItem, CreateList=True}}">-->
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="Load Order"
|
||||||
|
Width="Auto"
|
||||||
|
IsReadOnly="True">
|
||||||
|
<DataGridTextColumn.Binding>
|
||||||
|
<MultiBinding Converter="{StaticResource ModToListIdConverter}" StringFormat="{}{0}">
|
||||||
|
<Binding />
|
||||||
|
<Binding ElementName="ModList" Path="DataContext"></Binding>
|
||||||
|
</MultiBinding>
|
||||||
|
</DataGridTextColumn.Binding>
|
||||||
|
</DataGridTextColumn>
|
||||||
|
<DataGridTextColumn Header="Workshop Id"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Binding="{Binding PublishedFileId, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}">
|
||||||
|
|
||||||
|
</DataGridTextColumn>
|
||||||
|
<DataGridTextColumn Header="Name"
|
||||||
|
Width="*"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Binding="{Binding FriendlyName, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}">
|
||||||
|
</DataGridTextColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
<DataGrid.ItemContainerStyle>
|
||||||
|
<Style TargetType="DataGridRow">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsDependency}" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="#222222"/>
|
||||||
|
<Setter Property="Background" Value="#FFCCAA"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding IsDependency}" Value="True"/>
|
||||||
|
<Condition Binding="{Binding ElementName=ShowDependencyModsCheckBox, Path=IsChecked}" Value="False"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter Property="Height" Value="0px"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.ItemContainerStyle>
|
||||||
|
</DataGrid>
|
||||||
|
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Background="#1b2838">
|
||||||
|
<TextBlock Name="ModDescription" TextWrapping="Wrap" Foreground="White" Padding="2px"
|
||||||
|
Text="{Binding ElementName=ModList, Path=SelectedItem.Description}">
|
||||||
|
</TextBlock>
|
||||||
|
</ScrollViewer>
|
||||||
|
<Grid Grid.Row="2" Margin="0 0 0 6px">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition MinHeight="40px"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<CheckBox Name="ShowDependencyModsCheckBox" VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Left" Margin="6px 0" Grid.Column="0" Grid.Row="0"/>
|
||||||
|
<Label Content="Show Dependency Mods" Padding="0" Margin="6px 0" Grid.Column="1" VerticalAlignment="Center"/>
|
||||||
|
<Label Content="ID/URL:" Padding="0" Margin="6px 0" HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center" Grid.Column="0" Grid.Row="1"/>
|
||||||
|
<TextBox Name="AddModIDTextBox" Grid.Column="1" VerticalContentAlignment="Center"
|
||||||
|
HorizontalAlignment="Stretch" MinWidth="100px" Margin="6px 4px" Grid.Row="1"/>
|
||||||
|
<Button Content="Add" Grid.Column="2" Margin="6px 0" Width="60px" Height="40px" Click="AddBtn_OnClick" Grid.Row="1"/>
|
||||||
|
<Button Content="Remove" Grid.Column="3" Margin="6px 0" Width="60px" Height="40px" Click="RemoveBtn_OnClick" Grid.Row="1"
|
||||||
|
IsEnabled="{Binding ElementName=ModList, Path=SelectedItems.Count}"/>
|
||||||
|
<Button Content="Bulk Edit" Grid.Column="4" Margin="6px 0" Width="60px" Height="40px" Click="BulkButton_OnClick" Grid.Row="1"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Button Content="Save Config" Grid.Row="2" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="6px" Grid.Column="3" Width="80px" Height="40px" Click="SaveBtn_OnClick"/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
284
Torch.Server/Views/ModListControl.xaml.cs
Normal file
284
Torch.Server/Views/ModListControl.xaml.cs
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using VRage.Game;
|
||||||
|
using NLog;
|
||||||
|
using Torch.Server.Managers;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Server.ViewModels;
|
||||||
|
using Torch.Server.Annotations;
|
||||||
|
using Torch.Collections;
|
||||||
|
using Torch.Views;
|
||||||
|
|
||||||
|
namespace Torch.Server.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for ModListControl.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class ModListControl : UserControl, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private static Logger Log = LogManager.GetLogger(nameof(ModListControl));
|
||||||
|
private InstanceManager _instanceManager;
|
||||||
|
ModItemInfo _draggedMod;
|
||||||
|
bool _hasOrderChanged = false;
|
||||||
|
bool _isSortedByLoadOrder = true;
|
||||||
|
|
||||||
|
//private List<BindingExpression> _bindingExpressions = new List<BindingExpression>();
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for ModListControl
|
||||||
|
/// </summary>
|
||||||
|
public ModListControl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>();
|
||||||
|
_instanceManager.InstanceLoaded += _instanceManager_InstanceLoaded;
|
||||||
|
//var mods = _instanceManager.DedicatedConfig?.Mods;
|
||||||
|
//if( mods != null)
|
||||||
|
// DataContext = new ObservableCollection<MyObjectBuilder_Checkpoint.ModItem>();
|
||||||
|
DataContext = _instanceManager.DedicatedConfig?.Mods;
|
||||||
|
|
||||||
|
// Gets called once all children are loaded
|
||||||
|
//Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(ApplyStyles));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ModListControl_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetSorting()
|
||||||
|
{
|
||||||
|
CollectionViewSource.GetDefaultView(ModList.ItemsSource).SortDescriptions.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void _instanceManager_InstanceLoaded(ConfigDedicatedViewModel obj)
|
||||||
|
{
|
||||||
|
Log.Info("Instance loaded.");
|
||||||
|
Dispatcher.Invoke(() => {
|
||||||
|
DataContext = obj?.Mods ?? new MtObservableList<ModItemInfo>();
|
||||||
|
UpdateLayout();
|
||||||
|
((MtObservableList<ModItemInfo>)DataContext).CollectionChanged += OnModlistUpdate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnModlistUpdate(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
ModList.Items.Refresh();
|
||||||
|
//if (e.Action == NotifyCollectionChangedAction.Remove)
|
||||||
|
// _instanceManager.SaveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveBtn_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_instanceManager.SaveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void AddBtn_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (TryExtractId(AddModIDTextBox.Text, out ulong id))
|
||||||
|
{
|
||||||
|
var mod = new ModItemInfo(new MyObjectBuilder_Checkpoint.ModItem(id));
|
||||||
|
//mod.PublishedFileId = id;
|
||||||
|
_instanceManager.DedicatedConfig.Mods.Add(mod);
|
||||||
|
Task.Run(mod.UpdateModInfoAsync)
|
||||||
|
.ContinueWith((t) =>
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
_instanceManager.DedicatedConfig.Save();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
AddModIDTextBox.Text = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModIDTextBox.BorderBrush = Brushes.Red;
|
||||||
|
Log.Warn("Invalid mod id!");
|
||||||
|
MessageBox.Show("Invalid mod id!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void RemoveBtn_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var modList = ((MtObservableList<ModItemInfo>)DataContext);
|
||||||
|
if (ModList.SelectedItem is ModItemInfo mod && modList.Contains(mod))
|
||||||
|
modList.Remove(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryExtractId(string input, out ulong result)
|
||||||
|
{
|
||||||
|
var match = Regex.Match(input, @"(?<=id=)\d+").Value;
|
||||||
|
|
||||||
|
bool success;
|
||||||
|
if (string.IsNullOrEmpty(match))
|
||||||
|
success = ulong.TryParse(input, out result);
|
||||||
|
else
|
||||||
|
success = ulong.TryParse(match, out result);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ModList_Sorting(object sender, DataGridSortingEventArgs e)
|
||||||
|
{
|
||||||
|
Log.Info($"Sorting by '{e.Column.Header}'");
|
||||||
|
if (e.Column == ModList.Columns[0])
|
||||||
|
{
|
||||||
|
var dataView = CollectionViewSource.GetDefaultView(ModList.ItemsSource);
|
||||||
|
dataView.SortDescriptions.Clear();
|
||||||
|
dataView.Refresh();
|
||||||
|
_isSortedByLoadOrder = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_isSortedByLoadOrder = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ModList_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
//return;
|
||||||
|
|
||||||
|
_draggedMod = (ModItemInfo) TryFindRowAtPoint((UIElement) sender, e.GetPosition(ModList))?.DataContext;
|
||||||
|
|
||||||
|
//DraggedMod = (ModItemInfo) ModList.SelectedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataGridRow TryFindRowAtPoint(UIElement reference, Point point)
|
||||||
|
{
|
||||||
|
var element = reference.InputHitTest(point) as DependencyObject;
|
||||||
|
if (element == null)
|
||||||
|
return null;
|
||||||
|
if (element is DataGridRow row)
|
||||||
|
return row;
|
||||||
|
else
|
||||||
|
return TryFindParent<DataGridRow>(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T TryFindParent<T>(DependencyObject child) where T : DependencyObject
|
||||||
|
{
|
||||||
|
DependencyObject parent;
|
||||||
|
if (child == null)
|
||||||
|
return null;
|
||||||
|
if (child is ContentElement contentElement)
|
||||||
|
{
|
||||||
|
parent = ContentOperations.GetParent(contentElement);
|
||||||
|
if (parent == null && child is FrameworkContentElement fce)
|
||||||
|
parent = fce.Parent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parent = VisualTreeHelper.GetParent(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent is T result)
|
||||||
|
return result;
|
||||||
|
else
|
||||||
|
return TryFindParent<T>(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UserControl_MouseMove(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (_draggedMod == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_isSortedByLoadOrder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var targetMod = (ModItemInfo)TryFindRowAtPoint((UIElement)sender, e.GetPosition(ModList))?.DataContext;
|
||||||
|
if( targetMod != null && !ReferenceEquals(_draggedMod, targetMod))
|
||||||
|
{
|
||||||
|
_hasOrderChanged = true;
|
||||||
|
var modList = (MtObservableList<ModItemInfo>)DataContext;
|
||||||
|
//modList.Move(modList.IndexOf(_draggedMod), modList.IndexOf(targetMod));
|
||||||
|
modList.RemoveAt(modList.IndexOf(_draggedMod));
|
||||||
|
modList.Insert(modList.IndexOf(targetMod), _draggedMod);
|
||||||
|
ModList.Items.Refresh();
|
||||||
|
ModList.SelectedItem = _draggedMod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ModList_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isSortedByLoadOrder)
|
||||||
|
{
|
||||||
|
var targetMod = (ModItemInfo)TryFindRowAtPoint((UIElement)sender, e.GetPosition(ModList))?.DataContext;
|
||||||
|
if (targetMod != null && !ReferenceEquals(_draggedMod, targetMod))
|
||||||
|
{
|
||||||
|
var msg = "Drag and drop is only available when sorted by load order!";
|
||||||
|
Log.Warn(msg);
|
||||||
|
MessageBox.Show(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//if (DraggedMod != null && HasOrderChanged)
|
||||||
|
//Log.Info("Dragging over, saving...");
|
||||||
|
//_instanceManager.SaveConfig();
|
||||||
|
_draggedMod = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
[NotifyPropertyChangedInvocator]
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ModList_Selected(object sender, SelectedCellsChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_draggedMod != null)
|
||||||
|
ModList.SelectedItem = _draggedMod;
|
||||||
|
else if( e.AddedCells.Count > 0)
|
||||||
|
ModList.SelectedItem = e.AddedCells[0].Item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BulkButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var editor = new CollectionEditor();
|
||||||
|
|
||||||
|
//let's see just how poorly we can do this
|
||||||
|
var modList = ((MtObservableList<ModItemInfo>)DataContext).ToList();
|
||||||
|
var idList = modList.Select(m => m.PublishedFileId).ToList();
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
//blocking
|
||||||
|
editor.Edit<ulong>(idList, "Mods");
|
||||||
|
|
||||||
|
modList.RemoveAll(m => !idList.Contains(m.PublishedFileId));
|
||||||
|
foreach (var id in idList)
|
||||||
|
{
|
||||||
|
if (!modList.Any(m => m.PublishedFileId == id))
|
||||||
|
{
|
||||||
|
var mod = new ModItemInfo(new MyObjectBuilder_Checkpoint.ModItem(id));
|
||||||
|
tasks.Add(Task.Run(mod.UpdateModInfoAsync));
|
||||||
|
modList.Add(mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_instanceManager.DedicatedConfig.Mods.Clear();
|
||||||
|
foreach (var mod in modList)
|
||||||
|
_instanceManager.DedicatedConfig.Mods.Add(mod);
|
||||||
|
|
||||||
|
if (tasks.Any())
|
||||||
|
Task.WaitAll(tasks.ToArray());
|
||||||
|
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
_instanceManager.DedicatedConfig.Save();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -18,5 +18,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
<converters:ListConverter x:Key="ListConverterString" Type="system:String"/>
|
<converters:ListConverter x:Key="ListConverterString" Type="system:String"/>
|
||||||
<converters:ListConverter x:Key="ListConverterUInt64" Type="system:UInt64"/>
|
<converters:ListConverter x:Key="ListConverterUInt64" Type="system:UInt64"/>
|
||||||
|
<converters:ModToListIdConverter x:Key="ModToListIdConverter"/>
|
||||||
|
<converters:ListConverterWorkshopId x:Key="ListConverterWorkshopId"/>
|
||||||
<converters:BooleanAndConverter x:Key="BooleanAndConverter"/>
|
<converters:BooleanAndConverter x:Key="BooleanAndConverter"/>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
@@ -80,6 +80,9 @@
|
|||||||
<views:ConfigControl Grid.Row="1" x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" IsEnabled="{Binding CanRun}"/>
|
<views:ConfigControl Grid.Row="1" x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" IsEnabled="{Binding CanRun}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
<TabItem Header="Mods">
|
||||||
|
<views:ModListControl/>
|
||||||
|
</TabItem>
|
||||||
<TabItem Header="Chat/Players">
|
<TabItem Header="Chat/Players">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
@@ -102,6 +102,8 @@ namespace Torch.Server
|
|||||||
var newPos = new Point((int)Left, (int)Top);
|
var newPos = new Point((int)Left, (int)Top);
|
||||||
_config.WindowPosition = newPos;
|
_config.WindowPosition = newPos;
|
||||||
|
|
||||||
|
_config.Save(); //you idiot
|
||||||
|
|
||||||
if (_server?.State == ServerState.Running)
|
if (_server?.State == ServerState.Running)
|
||||||
_server.Stop();
|
_server.Stop();
|
||||||
|
|
||||||
|
@@ -74,8 +74,9 @@ namespace Torch.Server
|
|||||||
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
|
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
string worldName = string.IsNullOrEmpty(WorldName.Text) ? _currentItem.Name : WorldName.Text;
|
string worldName = string.IsNullOrEmpty(WorldName.Text) ? _currentItem.Name : WorldName.Text;
|
||||||
var worldPath = Path.Combine("Instance", "Saves", worldName);
|
|
||||||
var checkpoint= _currentItem.Checkpoint;
|
var worldPath = Path.Combine(TorchBase.Instance.Config.InstancePath, "Saves", worldName);
|
||||||
|
var checkpoint = _currentItem.Checkpoint;
|
||||||
if (Directory.Exists(worldPath))
|
if (Directory.Exists(worldPath))
|
||||||
{
|
{
|
||||||
MessageBox.Show("World already exists with that name.");
|
MessageBox.Show("World already exists with that name.");
|
||||||
|
@@ -2,7 +2,12 @@
|
|||||||
<packages>
|
<packages>
|
||||||
<package id="ControlzEx" version="3.0.2.4" targetFramework="net461" />
|
<package id="ControlzEx" version="3.0.2.4" targetFramework="net461" />
|
||||||
<package id="MahApps.Metro" version="1.6.1" targetFramework="net461" />
|
<package id="MahApps.Metro" version="1.6.1" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Win32.Registry" version="4.4.0" targetFramework="net461" />
|
||||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
|
||||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||||
|
<package id="protobuf-net" version="2.1.0" targetFramework="net461" />
|
||||||
|
<package id="SteamKit2" version="2.1.0" targetFramework="net461" />
|
||||||
|
<package id="System.Security.AccessControl" version="4.4.0" targetFramework="net461" />
|
||||||
|
<package id="System.Security.Principal.Windows" version="4.4.0" targetFramework="net461" />
|
||||||
</packages>
|
</packages>
|
@@ -13,7 +13,7 @@ namespace Torch.Collections
|
|||||||
/// Multithread safe, observable list
|
/// Multithread safe, observable list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Value type</typeparam>
|
/// <typeparam name="T">Value type</typeparam>
|
||||||
public class MtObservableList<T> : MtObservableCollection<IList<T>, T>, IList<T>, IList
|
public class MtObservableList<T> : MtObservableCollection<List<T>, T>, IList<T>, IList
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the MtObservableList class that is empty and has the default initial capacity.
|
/// Initializes a new instance of the MtObservableList class that is empty and has the default initial capacity.
|
||||||
@@ -114,16 +114,34 @@ namespace Torch.Collections
|
|||||||
using (Lock.WriteUsing())
|
using (Lock.WriteUsing())
|
||||||
{
|
{
|
||||||
comparer = comparer ?? Comparer<TKey>.Default;
|
comparer = comparer ?? Comparer<TKey>.Default;
|
||||||
if (Backing is List<T> lst)
|
Backing.Sort(new TransformComparer<T, TKey>(selector, comparer));
|
||||||
lst.Sort(new TransformComparer<T, TKey>(selector, comparer));
|
|
||||||
else
|
|
||||||
{
|
|
||||||
List<T> sortedItems = Backing.OrderBy(selector, comparer).ToList();
|
|
||||||
Backing.Clear();
|
|
||||||
foreach (T v in sortedItems)
|
|
||||||
Backing.Add(v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sorts the list using the given comparer./>
|
||||||
|
/// </summary>
|
||||||
|
public void Sort(IComparer<T> comparer)
|
||||||
|
{
|
||||||
|
using (DeferredUpdate())
|
||||||
|
using (Lock.WriteUsing())
|
||||||
|
{
|
||||||
|
Backing.Sort(comparer);
|
||||||
|
}
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Searches the entire list for an element using the specified comparer and returns the zero-based index of the element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
/// <param name="comparer"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int BinarySearch(T item, IComparer<T> comparer = null)
|
||||||
|
{
|
||||||
|
using(Lock.ReadUsing())
|
||||||
|
return Backing.BinarySearch(item, comparer ?? Comparer<T>.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
123
Torch/Collections/SortedView.cs
Normal file
123
Torch/Collections/SortedView.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Collections
|
||||||
|
{
|
||||||
|
public class SortedView<T>: IReadOnlyCollection<T>, INotifyCollectionChanged
|
||||||
|
{
|
||||||
|
private readonly MtObservableCollectionBase<T> _backing;
|
||||||
|
private IComparer<T> _comparer;
|
||||||
|
private readonly List<T> _store;
|
||||||
|
|
||||||
|
public SortedView(MtObservableCollectionBase<T> backing, IComparer<T> comparer)
|
||||||
|
{
|
||||||
|
_comparer = comparer;
|
||||||
|
_backing = backing;
|
||||||
|
_store = new List<T>(_backing.Count);
|
||||||
|
_store.AddRange(_backing);
|
||||||
|
_backing.CollectionChanged += backing_CollectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void backing_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case NotifyCollectionChangedAction.Add:
|
||||||
|
InsertSorted(e.NewItems);
|
||||||
|
CollectionChanged?.Invoke(this, e);
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
_store.RemoveAll(r => e.OldItems.Contains(r));
|
||||||
|
CollectionChanged?.Invoke(this, e);
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Move:
|
||||||
|
case NotifyCollectionChangedAction.Replace:
|
||||||
|
case NotifyCollectionChangedAction.Reset:
|
||||||
|
Refresh();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _store.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => _backing.Count;
|
||||||
|
|
||||||
|
private void InsertSorted(IEnumerable items)
|
||||||
|
{
|
||||||
|
foreach (var t in items)
|
||||||
|
InsertSorted((T)t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int InsertSorted(T item, IComparer<T> comparer = null)
|
||||||
|
{
|
||||||
|
if (comparer == null)
|
||||||
|
comparer = _comparer;
|
||||||
|
|
||||||
|
if (_store.Count == 0 || comparer == null)
|
||||||
|
{
|
||||||
|
_store.Add(item);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(comparer.Compare(_store[_store.Count - 1], item) <= 0)
|
||||||
|
{
|
||||||
|
_store.Add(item);
|
||||||
|
return _store.Count - 1;
|
||||||
|
}
|
||||||
|
if(comparer.Compare(_store[0], item) >= 0)
|
||||||
|
{
|
||||||
|
_store.Insert(0, item);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int index = _store.BinarySearch(item);
|
||||||
|
if (index < 0)
|
||||||
|
index = ~index;
|
||||||
|
_store.Insert(index, item);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sort(IComparer<T> comparer = null)
|
||||||
|
{
|
||||||
|
if (comparer == null)
|
||||||
|
comparer = _comparer;
|
||||||
|
|
||||||
|
if (comparer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_store.Sort(comparer);
|
||||||
|
|
||||||
|
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
_store.Clear();
|
||||||
|
_store.AddRange(_backing);
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetComparer(IComparer<T> comparer, bool resort = true)
|
||||||
|
{
|
||||||
|
_comparer = comparer;
|
||||||
|
if(resort)
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||||
|
}
|
||||||
|
}
|
@@ -290,5 +290,11 @@ namespace Torch.Commands
|
|||||||
}
|
}
|
||||||
}, this, TaskContinuationOptions.RunContinuationsAsynchronously);
|
}, this, TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Command("uptime", "Check how long the server has been online.")]
|
||||||
|
public void Uptime()
|
||||||
|
{
|
||||||
|
Context.Respond(((ITorchServer)Context.Torch).ElapsedPlayTime.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,7 @@ using VRageMath;
|
|||||||
|
|
||||||
namespace Torch.Managers
|
namespace Torch.Managers
|
||||||
{
|
{
|
||||||
public class NetworkManager : Manager, INetworkManager
|
public partial class NetworkManager : Manager, INetworkManager
|
||||||
{
|
{
|
||||||
private static Logger _log = LogManager.GetCurrentClassLogger();
|
private static Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
@@ -26,18 +26,8 @@ namespace Torch.Managers
|
|||||||
private readonly HashSet<INetworkHandler> _networkHandlers = new HashSet<INetworkHandler>();
|
private readonly HashSet<INetworkHandler> _networkHandlers = new HashSet<INetworkHandler>();
|
||||||
private bool _init;
|
private bool _init;
|
||||||
|
|
||||||
private const int MAX_ARGUMENT = 6;
|
|
||||||
private const int GENERIC_PARAMETERS = 8;
|
|
||||||
private const int DISPATCH_PARAMETERS = 10;
|
|
||||||
private static readonly DBNull DbNull = DBNull.Value;
|
|
||||||
private static MethodInfo _dispatchInfo;
|
|
||||||
|
|
||||||
private static MethodInfo DispatchEventInfo => _dispatchInfo ?? (_dispatchInfo = typeof(MyReplicationLayerBase).GetMethod("DispatchEvent", BindingFlags.NonPublic | BindingFlags.Instance));
|
|
||||||
|
|
||||||
[ReflectedGetter(Name = "m_typeTable")]
|
[ReflectedGetter(Name = "m_typeTable")]
|
||||||
private static Func<MyReplicationLayerBase, MyTypeTable> _typeTableGetter;
|
private static Func<MyReplicationLayerBase, MyTypeTable> _typeTableGetter;
|
||||||
[ReflectedGetter(Name = "m_methodInfoLookup")]
|
|
||||||
private static Func<MyEventTable, Dictionary<MethodInfo, CallSite>> _methodInfoLookupGetter;
|
|
||||||
[ReflectedMethod(Type = typeof(MyReplicationLayer), Name = "GetObjectByNetworkId")]
|
[ReflectedMethod(Type = typeof(MyReplicationLayer), Name = "GetObjectByNetworkId")]
|
||||||
private static Func<MyReplicationLayer, NetworkId, IMyNetObject> _getObjectByNetworkId;
|
private static Func<MyReplicationLayer, NetworkId, IMyNetObject> _getObjectByNetworkId;
|
||||||
|
|
||||||
@@ -45,7 +35,7 @@ namespace Torch.Managers
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ReflectionUnitTest(bool suppress = false)
|
private static bool ReflectionUnitTest(bool suppress = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -116,281 +106,120 @@ namespace Torch.Managers
|
|||||||
// TODO reverse what was done in Attach
|
// TODO reverse what was done in Attach
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Network Intercept
|
|
||||||
|
|
||||||
//TODO: Change this to a method patch so I don't have to try to keep up with Keen.
|
|
||||||
/// <summary>
|
|
||||||
/// This is the main body of the network intercept system. When messages come in from clients, they are processed here
|
|
||||||
/// before being passed on to the game server.
|
|
||||||
///
|
|
||||||
/// DO NOT modify this method unless you're absolutely sure of what you're doing. This can very easily destabilize the game!
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="packet"></param>
|
|
||||||
private void OnEvent(MyPacket packet)
|
|
||||||
{
|
|
||||||
if (_networkHandlers.Count == 0)
|
|
||||||
{
|
|
||||||
//pass the message back to the game server
|
|
||||||
try
|
|
||||||
{
|
|
||||||
((MyReplicationLayer)MyMultiplayer.ReplicationLayer).OnEvent(packet);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_log.Error(ex);
|
|
||||||
//crash after logging, bad things could happen if we continue on with bad data
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stream = new BitStream();
|
|
||||||
stream.ResetRead(packet.BitStream);
|
|
||||||
|
|
||||||
var networkId = stream.ReadNetworkId();
|
|
||||||
//this value is unused, but removing this line corrupts the rest of the stream
|
|
||||||
var blockedNetworkId = stream.ReadNetworkId();
|
|
||||||
var eventId = (uint)stream.ReadUInt16();
|
|
||||||
bool flag = stream.ReadBool();
|
|
||||||
Vector3D? position = new Vector3D?();
|
|
||||||
if (flag)
|
|
||||||
position = new Vector3D?(stream.ReadVector3D());
|
|
||||||
|
|
||||||
CallSite site;
|
|
||||||
object obj;
|
|
||||||
if (networkId.IsInvalid) // Static event
|
|
||||||
{
|
|
||||||
site = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).StaticEventTable.Get(eventId);
|
|
||||||
obj = null;
|
|
||||||
}
|
|
||||||
else // Instance event
|
|
||||||
{
|
|
||||||
//var sendAs = ((MyReplicationLayer)MyMultiplayer.ReplicationLayer).GetObjectByNetworkId(networkId);
|
|
||||||
var sendAs = _getObjectByNetworkId((MyReplicationLayer)MyMultiplayer.ReplicationLayer, networkId);
|
|
||||||
if (sendAs == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(sendAs.GetType());
|
|
||||||
var eventCount = typeInfo.EventTable.Count;
|
|
||||||
if (eventId < eventCount) // Directly
|
|
||||||
{
|
|
||||||
obj = sendAs;
|
|
||||||
site = typeInfo.EventTable.Get(eventId);
|
|
||||||
}
|
|
||||||
else // Through proxy
|
|
||||||
{
|
|
||||||
obj = ((IMyProxyTarget)sendAs).Target;
|
|
||||||
typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(obj.GetType());
|
|
||||||
site = typeInfo.EventTable.Get(eventId - (uint)eventCount); // Subtract max id of Proxy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//we're handling the network live in the game thread, this needs to go as fast as possible
|
|
||||||
var discard = false;
|
|
||||||
foreach (var handler in _networkHandlers)
|
|
||||||
//Parallel.ForEach(_networkHandlers, handler =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (handler.CanHandle(site))
|
|
||||||
discard |= handler.Handle(packet.Sender.Id.Value, site, stream, obj, packet);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
//ApplicationLog.Error(ex.ToString());
|
|
||||||
_log.Error(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//one of the handlers wants us to discard this packet
|
|
||||||
if (discard)
|
|
||||||
return;
|
|
||||||
|
|
||||||
//pass the message back to the game server
|
|
||||||
try
|
|
||||||
{
|
|
||||||
((MyReplicationLayer)MyMultiplayer.ReplicationLayer).OnEvent(packet);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_log.Error(ex, "Error processing network event!");
|
|
||||||
_log.Error(ex);
|
|
||||||
//crash after logging, bad things could happen if we continue on with bad data
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void RegisterNetworkHandler(INetworkHandler handler)
|
|
||||||
{
|
|
||||||
var handlerType = handler.GetType().FullName;
|
|
||||||
var toRemove = new List<INetworkHandler>();
|
|
||||||
foreach (var item in _networkHandlers)
|
|
||||||
{
|
|
||||||
if (item.GetType().FullName == handlerType)
|
|
||||||
{
|
|
||||||
//if (ExtenderOptions.IsDebugging)
|
|
||||||
_log.Error("Network handler already registered! " + handlerType);
|
|
||||||
toRemove.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var oldHandler in toRemove)
|
|
||||||
_networkHandlers.Remove(oldHandler);
|
|
||||||
|
|
||||||
_networkHandlers.Add(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool UnregisterNetworkHandler(INetworkHandler handler)
|
|
||||||
{
|
|
||||||
return _networkHandlers.Remove(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterNetworkHandlers(params INetworkHandler[] handlers)
|
|
||||||
{
|
|
||||||
foreach (var handler in handlers)
|
|
||||||
RegisterNetworkHandler(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Network Injection
|
#region Network Injection
|
||||||
|
|
||||||
|
private static Dictionary<MethodInfo, Delegate> _delegateCache = new Dictionary<MethodInfo, Delegate>();
|
||||||
|
|
||||||
/// <summary>
|
private static Func<T, TA> GetDelegate<T, TA>(MethodInfo method) where TA : class
|
||||||
/// Broadcasts an event to all connected clients
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="obj"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseEvent(MethodInfo method, object obj, params object[] args)
|
|
||||||
{
|
{
|
||||||
//default(EndpointId) tells the network to broadcast the message
|
if (!_delegateCache.TryGetValue(method, out var del))
|
||||||
RaiseEvent(method, obj, default(EndpointId), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends an event to one client by SteamId
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="obj"></param>
|
|
||||||
/// <param name="steamId"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseEvent(MethodInfo method, object obj, ulong steamId, params object[] args)
|
|
||||||
{
|
|
||||||
RaiseEvent(method, obj, new EndpointId(steamId), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends an event to one client
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="obj"></param>
|
|
||||||
/// <param name="endpoint"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseEvent(MethodInfo method, object obj, EndpointId endpoint, params object[] args)
|
|
||||||
{
|
|
||||||
if (method == null)
|
|
||||||
throw new ArgumentNullException(nameof(method), "MethodInfo cannot be null!");
|
|
||||||
|
|
||||||
if (args.Length > MAX_ARGUMENT)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(args), $"Cannot pass more than {MAX_ARGUMENT} arguments!");
|
|
||||||
|
|
||||||
var owner = obj as IMyEventOwner;
|
|
||||||
if (obj != null && owner == null)
|
|
||||||
throw new InvalidCastException("Provided event target is not of type IMyEventOwner!");
|
|
||||||
|
|
||||||
if (!method.HasAttribute<EventAttribute>())
|
|
||||||
throw new CustomAttributeFormatException("Provided event target does not have the Event attribute! Replication will not succeed!");
|
|
||||||
|
|
||||||
//array to hold arguments to pass into DispatchEvent
|
|
||||||
object[] arguments = new object[DISPATCH_PARAMETERS];
|
|
||||||
|
|
||||||
|
|
||||||
arguments[0] = obj == null ? TryGetStaticCallSite(method) : TryGetCallSite(method, obj);
|
|
||||||
arguments[1] = endpoint;
|
|
||||||
arguments[2] = owner;
|
|
||||||
|
|
||||||
//copy supplied arguments into the reflection arguments
|
|
||||||
for (var i = 0; i < args.Length; i++)
|
|
||||||
arguments[i + 3] = args[i];
|
|
||||||
|
|
||||||
//pad the array out with DBNull, skip last element
|
|
||||||
//last element should stay null (this is for blocking events -- not used?)
|
|
||||||
for (var j = args.Length + 3; j < arguments.Length - 1; j++)
|
|
||||||
arguments[j] = DbNull;
|
|
||||||
|
|
||||||
//create an array of Types so we can create a generic method
|
|
||||||
var argTypes = new Type[GENERIC_PARAMETERS];
|
|
||||||
|
|
||||||
//any null arguments (not DBNull) must be of type IMyEventOwner
|
|
||||||
for (var k = 2; k < arguments.Length; k++)
|
|
||||||
argTypes[k - 2] = arguments[k]?.GetType() ?? typeof(IMyEventOwner);
|
|
||||||
|
|
||||||
var parameters = method.GetParameters();
|
|
||||||
for (var i = 0; i < parameters.Length; i++)
|
|
||||||
{
|
{
|
||||||
if (argTypes[i + 1] != parameters[i].ParameterType)
|
del = (Func<T, TA>)(x => Delegate.CreateDelegate(typeof(TA), x, method) as TA);
|
||||||
throw new TypeLoadException($"Type mismatch on method parameters. Expected {string.Join(", ", parameters.Select(p => p.ParameterType.ToString()))} got {string.Join(", ", argTypes.Select(t => t.ToString()))}");
|
_delegateCache[method] = del;
|
||||||
}
|
}
|
||||||
|
|
||||||
//create a generic method of DispatchEvent and invoke to inject our data into the network
|
return (Func<T, TA>)del;
|
||||||
var dispatch = DispatchEventInfo.MakeGenericMethod(argTypes);
|
|
||||||
dispatch.Invoke(MyMultiplayer.ReplicationLayer, arguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public static void RaiseEvent<T1>(T1 instance, MethodInfo method, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
/// Broadcasts a static event to all connected clients
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseStaticEvent(MethodInfo method, params object[] args)
|
|
||||||
{
|
{
|
||||||
//default(EndpointId) tells the network to broadcast the message
|
var del = GetDelegate<T1, Action>(method);
|
||||||
RaiseStaticEvent(method, default(EndpointId), args);
|
|
||||||
|
MyMultiplayer.RaiseEvent(instance, del, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public static void RaiseEvent<T1, T2>(T1 instance, MethodInfo method, T2 arg1, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
/// Sends a static event to one client by SteamId
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="steamId"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseStaticEvent(MethodInfo method, ulong steamId, params object[] args)
|
|
||||||
{
|
{
|
||||||
RaiseEvent(method, null, new EndpointId(steamId), args);
|
var del = GetDelegate<T1, Action<T2>> (method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseEvent(instance, del, arg1, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public static void RaiseEvent<T1, T2, T3>(T1 instance, MethodInfo method, T2 arg1, T3 arg2, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
/// Sends a static event to one client
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method"></param>
|
|
||||||
/// <param name="endpoint"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
public void RaiseStaticEvent(MethodInfo method, EndpointId endpoint, params object[] args)
|
|
||||||
{
|
{
|
||||||
RaiseEvent(method, null, endpoint, args);
|
var del = GetDelegate<T1, Action<T2, T3>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseEvent(instance, del, arg1, arg2, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CallSite TryGetStaticCallSite(MethodInfo method)
|
public static void RaiseEvent<T1, T2, T3, T4>(T1 instance, MethodInfo method, T2 arg1, T3 arg2, T4 arg3, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
{
|
{
|
||||||
MyTypeTable typeTable = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer);
|
var del = GetDelegate<T1, Action<T2, T3, T4>>(method);
|
||||||
if (!_methodInfoLookupGetter.Invoke(typeTable.StaticEventTable).TryGetValue(method, out CallSite result))
|
|
||||||
throw new MissingMemberException("Provided event target not found!");
|
MyMultiplayer.RaiseEvent(instance, del, arg1, arg2, arg3, target);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CallSite TryGetCallSite(MethodInfo method, object arg)
|
public static void RaiseEvent<T1, T2, T3, T4, T5>(T1 instance, MethodInfo method, T2 arg1, T3 arg2, T4 arg3, T5 arg4, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
{
|
{
|
||||||
MySynchronizedTypeInfo typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(arg.GetType());
|
var del = GetDelegate<T1, Action<T2, T3, T4, T5>>(method);
|
||||||
if (!_methodInfoLookupGetter.Invoke(typeInfo.EventTable).TryGetValue(method, out CallSite result))
|
|
||||||
throw new MissingMemberException("Provided event target not found!");
|
MyMultiplayer.RaiseEvent(instance, del, arg1, arg2, arg3, arg4, target);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void RaiseEvent<T1, T2, T3, T4, T5, T6>(T1 instance, MethodInfo method, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
|
{
|
||||||
|
var del = GetDelegate<T1, Action<T2, T3, T4, T5, T6>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseEvent(instance, del, arg1, arg2, arg3, arg4, arg5, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseEvent<T1, T2, T3, T4, T5, T6, T7>(T1 instance, MethodInfo method, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, EndpointId target = default(EndpointId)) where T1 : IMyEventOwner
|
||||||
|
{
|
||||||
|
var del = GetDelegate<T1, Action<T2, T3, T4, T5, T6, T7>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseEvent(instance, del, arg1, arg2, arg3, arg4, arg5, arg6, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent(MethodInfo method, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1>(MethodInfo method, T1 arg1, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1, T2>(MethodInfo method, T1 arg1, T2 arg2, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1, T2>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, arg2, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1, T2, T3>(MethodInfo method, T1 arg1, T2 arg2, T3 arg3, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1, T2, T3>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, arg2, arg3, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1, T2, T3, T4>(MethodInfo method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1, T2, T3, T4>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, arg2, arg3, arg4, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1, T2, T3, T4, T5>(MethodInfo method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1, T2, T3, T4, T5>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, arg2, arg3, arg4, arg5, target, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RaiseStaticEvent<T1, T2, T3, T4, T5, T6>(MethodInfo method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, EndpointId target = default(EndpointId), Vector3D? position = null)
|
||||||
|
{
|
||||||
|
var del = GetDelegate<IMyEventOwner, Action<T1, T2, T3, T4, T5, T6>>(method);
|
||||||
|
|
||||||
|
MyMultiplayer.RaiseStaticEvent(del, arg1, arg2, arg3, arg4, arg5, arg6, target, position);
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
318
Torch/Managers/NetworkManager/NetworkManager_Deprecated.cs
Normal file
318
Torch/Managers/NetworkManager/NetworkManager_Deprecated.cs
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Utils;
|
||||||
|
using VRage;
|
||||||
|
using VRage.Library.Collections;
|
||||||
|
using VRage.Network;
|
||||||
|
using VRageMath;
|
||||||
|
|
||||||
|
namespace Torch.Managers
|
||||||
|
{
|
||||||
|
//Everything in this file is deprecated and should be deleted Eventually(tm)
|
||||||
|
public partial class NetworkManager
|
||||||
|
{
|
||||||
|
[ReflectedGetter(Name = "m_methodInfoLookup")]
|
||||||
|
private static Func<MyEventTable, Dictionary<MethodInfo, CallSite>> _methodInfoLookupGetter;
|
||||||
|
private const int MAX_ARGUMENT = 6;
|
||||||
|
private const int GENERIC_PARAMETERS = 8;
|
||||||
|
private const int DISPATCH_PARAMETERS = 11;
|
||||||
|
private static readonly DBNull DbNull = DBNull.Value;
|
||||||
|
private static MethodInfo _dispatchInfo;
|
||||||
|
|
||||||
|
private static MethodInfo DispatchEventInfo => _dispatchInfo ?? (_dispatchInfo = typeof(MyReplicationLayerBase).GetMethod("DispatchEvent", BindingFlags.NonPublic | BindingFlags.Instance));
|
||||||
|
|
||||||
|
#region Network Injection
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcasts an event to all connected clients
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method"></param>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
[Obsolete("Old injection system deprecated. Use the generic methods instead.")]
|
||||||
|
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
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method"></param>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="steamId"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
[Obsolete("Old injection system deprecated. Use the generic methods instead.")]
|
||||||
|
public void RaiseEvent(MethodInfo method, object obj, ulong steamId, params object[] args)
|
||||||
|
{
|
||||||
|
RaiseEvent(method, obj, new EndpointId(steamId), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends an event to one client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method"></param>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="endpoint"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
[Obsolete("Old injection system deprecated. Use the generic methods instead.")]
|
||||||
|
public void RaiseEvent(MethodInfo method, object obj, EndpointId endpoint, params object[] args)
|
||||||
|
{
|
||||||
|
if (method == null)
|
||||||
|
throw new ArgumentNullException(nameof(method), "MethodInfo cannot be null!");
|
||||||
|
|
||||||
|
if (args.Length > MAX_ARGUMENT)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(args), $"Cannot pass more than {MAX_ARGUMENT} arguments!");
|
||||||
|
|
||||||
|
var owner = obj as IMyEventOwner;
|
||||||
|
if (obj != null && owner == null)
|
||||||
|
throw new InvalidCastException("Provided event target is not of type IMyEventOwner!");
|
||||||
|
|
||||||
|
if (!method.HasAttribute<EventAttribute>())
|
||||||
|
throw new CustomAttributeFormatException("Provided event target does not have the Event attribute! Replication will not succeed!");
|
||||||
|
|
||||||
|
//array to hold arguments to pass into DispatchEvent
|
||||||
|
object[] arguments = new object[DISPATCH_PARAMETERS];
|
||||||
|
|
||||||
|
|
||||||
|
arguments[0] = obj == null ? TryGetStaticCallSite(method) : TryGetCallSite(method, obj);
|
||||||
|
arguments[1] = endpoint;
|
||||||
|
arguments[2] = new Vector3D?();
|
||||||
|
arguments[3] = owner;
|
||||||
|
|
||||||
|
//copy supplied arguments into the reflection arguments
|
||||||
|
for (var i = 0; i < args.Length; i++)
|
||||||
|
arguments[i + 4] = args[i];
|
||||||
|
|
||||||
|
//pad the array out with DBNull, skip last element
|
||||||
|
//last element should stay null (this is for blocking events -- not used?)
|
||||||
|
for (var j = args.Length + 4; j < arguments.Length - 1; j++)
|
||||||
|
arguments[j] = DbNull;
|
||||||
|
|
||||||
|
//create an array of Types so we can create a generic method
|
||||||
|
var argTypes = new Type[GENERIC_PARAMETERS];
|
||||||
|
|
||||||
|
//any null arguments (not DBNull) must be of type IMyEventOwner
|
||||||
|
for (var k = 2; k < arguments.Length; k++)
|
||||||
|
argTypes[k - 2] = arguments[k]?.GetType() ?? typeof(IMyEventOwner);
|
||||||
|
|
||||||
|
var parameters = method.GetParameters();
|
||||||
|
for (var i = 0; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
if (argTypes[i + 1] != parameters[i].ParameterType)
|
||||||
|
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()))}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//create a generic method of DispatchEvent and invoke to inject our data into the network
|
||||||
|
var dispatch = DispatchEventInfo.MakeGenericMethod(argTypes);
|
||||||
|
dispatch.Invoke(MyMultiplayer.ReplicationLayer, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcasts a static event to all connected clients
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
[Obsolete("Old injection system deprecated. Use the generic methods instead.")]
|
||||||
|
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
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method"></param>
|
||||||
|
/// <param name="steamId"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
[Obsolete("Old injection system deprecated. Use the generic methods instead.")]
|
||||||
|
public void RaiseStaticEvent(MethodInfo method, ulong steamId, params object[] args)
|
||||||
|
{
|
||||||
|
RaiseEvent(method, (object)null, new EndpointId(steamId), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a static event to one client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method"></param>
|
||||||
|
/// <param name="endpoint"></param>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
[Obsolete("Old injection system deprecated. Use the generic methods instead.")]
|
||||||
|
public void RaiseStaticEvent(MethodInfo method, EndpointId endpoint, params object[] args)
|
||||||
|
{
|
||||||
|
RaiseEvent(method, (object)null, endpoint, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CallSite TryGetStaticCallSite(MethodInfo method)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Network Intercept
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
|
//TODO: Change this to a method patch so I don't have to try to keep up with Keen.
|
||||||
|
/// <summary>
|
||||||
|
/// This is the main body of the network intercept system. When messages come in from clients, they are processed here
|
||||||
|
/// before being passed on to the game server.
|
||||||
|
///
|
||||||
|
/// DO NOT modify this method unless you're absolutely sure of what you're doing. This can very easily destabilize the game!
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="packet"></param>
|
||||||
|
private void OnEvent(MyPacket packet)
|
||||||
|
{
|
||||||
|
if (_networkHandlers.Count == 0)
|
||||||
|
{
|
||||||
|
//pass the message back to the game server
|
||||||
|
try
|
||||||
|
{
|
||||||
|
((MyReplicationLayer)MyMultiplayer.ReplicationLayer).OnEvent(packet);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Error(ex);
|
||||||
|
//crash after logging, bad things could happen if we continue on with bad data
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = new BitStream();
|
||||||
|
stream.ResetRead(packet.BitStream);
|
||||||
|
|
||||||
|
var networkId = stream.ReadNetworkId();
|
||||||
|
//this value is unused, but removing this line corrupts the rest of the stream
|
||||||
|
var blockedNetworkId = stream.ReadNetworkId();
|
||||||
|
var eventId = (uint)stream.ReadUInt16();
|
||||||
|
bool flag = stream.ReadBool();
|
||||||
|
Vector3D? position = new Vector3D?();
|
||||||
|
if (flag)
|
||||||
|
position = new Vector3D?(stream.ReadVector3D());
|
||||||
|
|
||||||
|
CallSite site;
|
||||||
|
object obj;
|
||||||
|
if (networkId.IsInvalid) // Static event
|
||||||
|
{
|
||||||
|
site = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).StaticEventTable.Get(eventId);
|
||||||
|
obj = null;
|
||||||
|
}
|
||||||
|
else // Instance event
|
||||||
|
{
|
||||||
|
//var sendAs = ((MyReplicationLayer)MyMultiplayer.ReplicationLayer).GetObjectByNetworkId(networkId);
|
||||||
|
var sendAs = _getObjectByNetworkId((MyReplicationLayer)MyMultiplayer.ReplicationLayer, networkId);
|
||||||
|
if (sendAs == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(sendAs.GetType());
|
||||||
|
var eventCount = typeInfo.EventTable.Count;
|
||||||
|
if (eventId < eventCount) // Directly
|
||||||
|
{
|
||||||
|
obj = sendAs;
|
||||||
|
site = typeInfo.EventTable.Get(eventId);
|
||||||
|
}
|
||||||
|
else // Through proxy
|
||||||
|
{
|
||||||
|
obj = ((IMyProxyTarget)sendAs).Target;
|
||||||
|
typeInfo = _typeTableGetter.Invoke(MyMultiplayer.ReplicationLayer).Get(obj.GetType());
|
||||||
|
site = typeInfo.EventTable.Get(eventId - (uint)eventCount); // Subtract max id of Proxy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//we're handling the network live in the game thread, this needs to go as fast as possible
|
||||||
|
var discard = false;
|
||||||
|
foreach (var handler in _networkHandlers)
|
||||||
|
//Parallel.ForEach(_networkHandlers, handler =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (handler.CanHandle(site))
|
||||||
|
discard |= handler.Handle(packet.Sender.Id.Value, site, stream, obj, packet);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
//ApplicationLog.Error(ex.ToString());
|
||||||
|
_log.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//one of the handlers wants us to discard this packet
|
||||||
|
if (discard)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//pass the message back to the game server
|
||||||
|
try
|
||||||
|
{
|
||||||
|
((MyReplicationLayer)MyMultiplayer.ReplicationLayer).OnEvent(packet);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.Error(ex, "Error processing network event!");
|
||||||
|
_log.Error(ex);
|
||||||
|
//crash after logging, bad things could happen if we continue on with bad data
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Deprecated. Use a method patch instead.")]
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void RegisterNetworkHandler(INetworkHandler handler)
|
||||||
|
{
|
||||||
|
var handlerType = handler.GetType().FullName;
|
||||||
|
var toRemove = new List<INetworkHandler>();
|
||||||
|
foreach (var item in _networkHandlers)
|
||||||
|
{
|
||||||
|
if (item.GetType().FullName == handlerType)
|
||||||
|
{
|
||||||
|
//if (ExtenderOptions.IsDebugging)
|
||||||
|
_log.Error("Network handler already registered! " + handlerType);
|
||||||
|
toRemove.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var oldHandler in toRemove)
|
||||||
|
_networkHandlers.Remove(oldHandler);
|
||||||
|
|
||||||
|
_networkHandlers.Add(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Deprecated. Use a method patch instead.")]
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool UnregisterNetworkHandler(INetworkHandler handler)
|
||||||
|
{
|
||||||
|
return _networkHandlers.Remove(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Deprecated. Use a method patch instead.")]
|
||||||
|
public void RegisterNetworkHandlers(params INetworkHandler[] handlers)
|
||||||
|
{
|
||||||
|
foreach (var handler in handlers)
|
||||||
|
RegisterNetworkHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,8 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
|
using Torch.API.Session;
|
||||||
|
using Torch.API.Managers;
|
||||||
using Torch.Managers.PatchManager;
|
using Torch.Managers.PatchManager;
|
||||||
using Torch.Mod;
|
using Torch.Mod;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
@@ -16,6 +18,10 @@ namespace Torch.Patches
|
|||||||
[PatchShim]
|
[PatchShim]
|
||||||
internal static class SessionDownloadPatch
|
internal static class SessionDownloadPatch
|
||||||
{
|
{
|
||||||
|
private static ITorchSessionManager _sessionManager;
|
||||||
|
private static ITorchSessionManager SessionManager => _sessionManager ?? (_sessionManager = TorchBase.Instance.Managers.GetManager<ITorchSessionManager>());
|
||||||
|
|
||||||
|
|
||||||
internal static void Patch(PatchContext context)
|
internal static void Patch(PatchContext context)
|
||||||
{
|
{
|
||||||
context.GetPattern(typeof(MySession).GetMethod(nameof(MySession.GetWorld))).Suffixes.Add(typeof(SessionDownloadPatch).GetMethod(nameof(SuffixGetWorld), BindingFlags.Static | BindingFlags.NonPublic));
|
context.GetPattern(typeof(MySession).GetMethod(nameof(MySession.GetWorld))).Suffixes.Add(typeof(SessionDownloadPatch).GetMethod(nameof(SuffixGetWorld), BindingFlags.Static | BindingFlags.NonPublic));
|
||||||
@@ -27,7 +33,7 @@ namespace Torch.Patches
|
|||||||
//copy this list so mods added here don't propagate up to the real session
|
//copy this list so mods added here don't propagate up to the real session
|
||||||
__result.Checkpoint.Mods = __result.Checkpoint.Mods.ToList();
|
__result.Checkpoint.Mods = __result.Checkpoint.Mods.ToList();
|
||||||
|
|
||||||
__result.Checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(TorchModCore.MOD_ID));
|
__result.Checkpoint.Mods.AddRange(SessionManager.OverrideMods);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -9,7 +10,9 @@ using Torch.API;
|
|||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
using Torch.API.Session;
|
using Torch.API.Session;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
|
using Torch.Mod;
|
||||||
using Torch.Session;
|
using Torch.Session;
|
||||||
|
using VRage.Game;
|
||||||
|
|
||||||
namespace Torch.Session
|
namespace Torch.Session
|
||||||
{
|
{
|
||||||
@@ -21,6 +24,15 @@ namespace Torch.Session
|
|||||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
private TorchSession _currentSession;
|
private TorchSession _currentSession;
|
||||||
|
|
||||||
|
private readonly Dictionary<ulong, MyObjectBuilder_Checkpoint.ModItem> _overrideMods;
|
||||||
|
|
||||||
|
public event Action<CollectionChangeEventArgs> OverrideModsChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of mods that will be injected into client world downloads.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<MyObjectBuilder_Checkpoint.ModItem> OverrideMods => _overrideMods.Values;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event TorchSessionStateChangedDel SessionStateChanged;
|
public event TorchSessionStateChangedDel SessionStateChanged;
|
||||||
|
|
||||||
@@ -31,6 +43,8 @@ namespace Torch.Session
|
|||||||
|
|
||||||
public TorchSessionManager(ITorchBase torchInstance) : base(torchInstance)
|
public TorchSessionManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
{
|
{
|
||||||
|
_overrideMods = new Dictionary<ulong, MyObjectBuilder_Checkpoint.ModItem>();
|
||||||
|
_overrideMods.Add(TorchModCore.MOD_ID, new MyObjectBuilder_Checkpoint.ModItem(TorchModCore.MOD_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -49,6 +63,27 @@ namespace Torch.Session
|
|||||||
return _factories.Remove(factory);
|
return _factories.Remove(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool AddOverrideMod(ulong modId)
|
||||||
|
{
|
||||||
|
if (_overrideMods.ContainsKey(modId))
|
||||||
|
return false;
|
||||||
|
var item = new MyObjectBuilder_Checkpoint.ModItem(modId);
|
||||||
|
_overrideMods.Add(modId, item);
|
||||||
|
|
||||||
|
OverrideModsChanged?.Invoke(new CollectionChangeEventArgs(CollectionChangeAction.Add, item));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool RemoveOverrideMod(ulong modId)
|
||||||
|
{
|
||||||
|
if(_overrideMods.TryGetValue(modId, out var item))
|
||||||
|
OverrideModsChanged?.Invoke(new CollectionChangeEventArgs(CollectionChangeAction.Remove, item));
|
||||||
|
|
||||||
|
return _overrideMods.Remove(modId);
|
||||||
|
}
|
||||||
|
|
||||||
#region Session events
|
#region Session events
|
||||||
|
|
||||||
private void SetState(TorchSessionState state)
|
private void SetState(TorchSessionState state)
|
||||||
|
@@ -35,6 +35,8 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<!-- I don't know why this needs to exist -->
|
||||||
|
<ItemGroup> <Reference Include="netstandard" /> </ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="ControlzEx, Version=3.0.2.4, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="ControlzEx, Version=3.0.2.4, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\ControlzEx.3.0.2.4\lib\net45\ControlzEx.dll</HintPath>
|
<HintPath>..\packages\ControlzEx.3.0.2.4\lib\net45\ControlzEx.dll</HintPath>
|
||||||
@@ -48,6 +50,9 @@
|
|||||||
<Reference Include="MahApps.Metro, Version=1.6.1.4, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="MahApps.Metro, Version=1.6.1.4, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\MahApps.Metro.1.6.1\lib\net45\MahApps.Metro.dll</HintPath>
|
<HintPath>..\packages\MahApps.Metro.1.6.1\lib\net45\MahApps.Metro.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Win32.Registry, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Microsoft.Win32.Registry.4.4.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\GameBinaries\Newtonsoft.Json.dll</HintPath>
|
<HintPath>..\GameBinaries\Newtonsoft.Json.dll</HintPath>
|
||||||
@@ -85,6 +90,9 @@
|
|||||||
<HintPath>..\GameBinaries\SpaceEngineers.ObjectBuilders.XmlSerializers.dll</HintPath>
|
<HintPath>..\GameBinaries\SpaceEngineers.ObjectBuilders.XmlSerializers.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="SteamKit2, Version=2.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\SteamKit2.2.1.0\lib\netstandard2.0\SteamKit2.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Steamworks.NET">
|
<Reference Include="Steamworks.NET">
|
||||||
<HintPath>..\GameBinaries\Steamworks.NET.dll</HintPath>
|
<HintPath>..\GameBinaries\Steamworks.NET.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
@@ -93,6 +101,12 @@
|
|||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.IO.Compression" />
|
<Reference Include="System.IO.Compression" />
|
||||||
<Reference Include="System.IO.Compression.FileSystem" />
|
<Reference Include="System.IO.Compression.FileSystem" />
|
||||||
|
<Reference Include="System.Security.AccessControl, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\System.Security.AccessControl.4.4.0\lib\net461\System.Security.AccessControl.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Security.Principal.Windows, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\System.Security.Principal.Windows.4.4.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\ControlzEx.3.0.2.4\lib\net45\System.Windows.Interactivity.dll</HintPath>
|
<HintPath>..\packages\ControlzEx.3.0.2.4\lib\net45\System.Windows.Interactivity.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
@@ -171,6 +185,7 @@
|
|||||||
<Compile Include="Collections\MtObservableSortedDictionary.cs" />
|
<Compile Include="Collections\MtObservableSortedDictionary.cs" />
|
||||||
<Compile Include="Collections\MtObservableEvent.cs" />
|
<Compile Include="Collections\MtObservableEvent.cs" />
|
||||||
<Compile Include="Collections\MtObservableList.cs" />
|
<Compile Include="Collections\MtObservableList.cs" />
|
||||||
|
<Compile Include="Collections\SortedView.cs" />
|
||||||
<Compile Include="Collections\TransformComparer.cs" />
|
<Compile Include="Collections\TransformComparer.cs" />
|
||||||
<Compile Include="Collections\TransformEnumerator.cs" />
|
<Compile Include="Collections\TransformEnumerator.cs" />
|
||||||
<Compile Include="Event\EventShimAttribute.cs" />
|
<Compile Include="Event\EventShimAttribute.cs" />
|
||||||
@@ -183,6 +198,7 @@
|
|||||||
<Compile Include="Event\EventManager.cs" />
|
<Compile Include="Event\EventManager.cs" />
|
||||||
<Compile Include="Event\IEventList.cs" />
|
<Compile Include="Event\IEventList.cs" />
|
||||||
<Compile Include="Managers\KeenLogPatch.cs" />
|
<Compile Include="Managers\KeenLogPatch.cs" />
|
||||||
|
<Compile Include="Managers\NetworkManager\NetworkManager_Deprecated.cs" />
|
||||||
<Compile Include="Managers\PatchManager\AssemblyMemory.cs" />
|
<Compile Include="Managers\PatchManager\AssemblyMemory.cs" />
|
||||||
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
|
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
|
||||||
<Compile Include="Managers\PatchManager\EmitExtensions.cs" />
|
<Compile Include="Managers\PatchManager\EmitExtensions.cs" />
|
||||||
@@ -234,6 +250,7 @@
|
|||||||
<Compile Include="Managers\UpdateManager.cs" />
|
<Compile Include="Managers\UpdateManager.cs" />
|
||||||
<Compile Include="Persistent.cs" />
|
<Compile Include="Persistent.cs" />
|
||||||
<Compile Include="Plugins\PluginManifest.cs" />
|
<Compile Include="Plugins\PluginManifest.cs" />
|
||||||
|
<Compile Include="Utils\SteamWorkshopTools\KeyValueExtensions.cs" />
|
||||||
<Compile Include="Utils\MiscExtensions.cs" />
|
<Compile Include="Utils\MiscExtensions.cs" />
|
||||||
<Compile Include="Utils\Reflected\ReflectedEventReplaceAttribute.cs" />
|
<Compile Include="Utils\Reflected\ReflectedEventReplaceAttribute.cs" />
|
||||||
<Compile Include="Utils\Reflected\ReflectedEventReplacer.cs" />
|
<Compile Include="Utils\Reflected\ReflectedEventReplacer.cs" />
|
||||||
@@ -248,6 +265,7 @@
|
|||||||
<Compile Include="Utils\Reflected\ReflectedStaticMethodAttribute.cs" />
|
<Compile Include="Utils\Reflected\ReflectedStaticMethodAttribute.cs" />
|
||||||
<Compile Include="Utils\Reflection.cs" />
|
<Compile Include="Utils\Reflection.cs" />
|
||||||
<Compile Include="Managers\ScriptingManager.cs" />
|
<Compile Include="Managers\ScriptingManager.cs" />
|
||||||
|
<Compile Include="Utils\SteamWorkshopTools\WebAPI.cs" />
|
||||||
<Compile Include="Utils\StringUtils.cs" />
|
<Compile Include="Utils\StringUtils.cs" />
|
||||||
<Compile Include="Utils\SynchronizationExtensions.cs" />
|
<Compile Include="Utils\SynchronizationExtensions.cs" />
|
||||||
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
||||||
@@ -257,6 +275,7 @@
|
|||||||
<Compile Include="TorchPluginBase.cs" />
|
<Compile Include="TorchPluginBase.cs" />
|
||||||
<Compile Include="Session\TorchSession.cs" />
|
<Compile Include="Session\TorchSession.cs" />
|
||||||
<Compile Include="Utils\TorchLauncher.cs" />
|
<Compile Include="Utils\TorchLauncher.cs" />
|
||||||
|
<Compile Include="Utils\SteamWorkshopTools\PublishedItemDetails.cs" />
|
||||||
<Compile Include="ViewModels\ModViewModel.cs" />
|
<Compile Include="ViewModels\ModViewModel.cs" />
|
||||||
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
||||||
<Compile Include="Extensions\StringExtensions.cs" />
|
<Compile Include="Extensions\StringExtensions.cs" />
|
||||||
|
@@ -6,7 +6,12 @@ using System.Net;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Game.Entities;
|
||||||
|
using Sandbox.Game.World;
|
||||||
using Steamworks;
|
using Steamworks;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
namespace Torch.Utils
|
namespace Torch.Utils
|
||||||
{
|
{
|
||||||
@@ -57,5 +62,12 @@ namespace Torch.Utils
|
|||||||
// What is endianness anyway?
|
// What is endianness anyway?
|
||||||
return new IPAddress(BitConverter.GetBytes(state.m_nRemoteIP).Reverse().ToArray());
|
return new IPAddress(BitConverter.GetBytes(state.m_nRemoteIP).Reverse().ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetGridOwnerName(this MyCubeGrid grid)
|
||||||
|
{
|
||||||
|
if (grid.BigOwners.Count == 0 || grid.BigOwners[0] == 0)
|
||||||
|
return "nobody";
|
||||||
|
return MyMultiplayer.Static.GetMemberName(MySession.Static.Players.TryGetSteamId(grid.BigOwners[0]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
Torch/Utils/SteamWorkshopTools/KeyValueExtensions.cs
Normal file
48
Torch/Utils/SteamWorkshopTools/KeyValueExtensions.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using SteamKit2;
|
||||||
|
|
||||||
|
namespace Torch.Utils.SteamWorkshopTools
|
||||||
|
{
|
||||||
|
public static class KeyValueExtensions
|
||||||
|
{
|
||||||
|
private static Logger Log = LogManager.GetLogger("SteamWorkshopService");
|
||||||
|
|
||||||
|
public static T GetValueOrDefault<T>(this KeyValue kv, string key)
|
||||||
|
{
|
||||||
|
kv.TryGetValueOrDefault<T>(key, out T result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public static bool TryGetValueOrDefault<T>(this KeyValue kv, string key, out T typedValue)
|
||||||
|
{
|
||||||
|
var match = kv.Children?.Find((KeyValue item) => item.Name == key);
|
||||||
|
object result = default(T);
|
||||||
|
if (match == null)
|
||||||
|
{
|
||||||
|
typedValue = (T) result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = match.Value ?? "";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var converter = TypeDescriptor.GetConverter(typeof(T));
|
||||||
|
result = converter.ConvertFromString(value);
|
||||||
|
typedValue = (T)result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (NotSupportedException)
|
||||||
|
{
|
||||||
|
throw new Exception($"Unexpected Type '{typeof(T)}'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
Torch/Utils/SteamWorkshopTools/PublishedItemDetails.cs
Normal file
26
Torch/Utils/SteamWorkshopTools/PublishedItemDetails.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Utils.SteamWorkshopTools
|
||||||
|
{
|
||||||
|
public class PublishedItemDetails
|
||||||
|
{
|
||||||
|
public ulong PublishedFileId;
|
||||||
|
public uint Views;
|
||||||
|
public uint Subscriptions;
|
||||||
|
public DateTime TimeUpdated;
|
||||||
|
public DateTime TimeCreated;
|
||||||
|
public string Description;
|
||||||
|
public string Title;
|
||||||
|
public string FileUrl;
|
||||||
|
public long FileSize;
|
||||||
|
public string FileName;
|
||||||
|
public ulong ConsumerAppId;
|
||||||
|
public ulong CreatorAppId;
|
||||||
|
public ulong Creator;
|
||||||
|
public string[] Tags;
|
||||||
|
}
|
||||||
|
}
|
291
Torch/Utils/SteamWorkshopTools/WebAPI.cs
Normal file
291
Torch/Utils/SteamWorkshopTools/WebAPI.cs
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using NLog;
|
||||||
|
using SteamKit2;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Torch.Utils.SteamWorkshopTools
|
||||||
|
{
|
||||||
|
public class WebAPI
|
||||||
|
{
|
||||||
|
private static Logger Log = LogManager.GetLogger("SteamWorkshopService");
|
||||||
|
public const uint AppID = 244850U;
|
||||||
|
public string Username { get; private set; }
|
||||||
|
private string password;
|
||||||
|
public bool IsReady { get; private set; }
|
||||||
|
public bool IsRunning { get; private set; }
|
||||||
|
private TaskCompletionSource<bool> logonTaskCompletionSource;
|
||||||
|
|
||||||
|
private SteamClient steamClient;
|
||||||
|
private CallbackManager cbManager;
|
||||||
|
private SteamUser steamUser;
|
||||||
|
|
||||||
|
private static WebAPI _instance;
|
||||||
|
public static WebAPI Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _instance ?? (_instance = new WebAPI());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebAPI()
|
||||||
|
{
|
||||||
|
steamClient = new SteamClient();
|
||||||
|
cbManager = new CallbackManager(steamClient);
|
||||||
|
|
||||||
|
IsRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Logon(string user = "anonymous", string pw = "")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(user))
|
||||||
|
throw new ArgumentNullException("User can't be null!");
|
||||||
|
if (!user.Equals("anonymous") && !pw.Equals(""))
|
||||||
|
throw new ArgumentNullException("Password can't be null if user is not anonymous!");
|
||||||
|
|
||||||
|
Username = user;
|
||||||
|
password = pw;
|
||||||
|
|
||||||
|
logonTaskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
|
steamUser = steamClient.GetHandler<SteamUser>();
|
||||||
|
cbManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
|
||||||
|
cbManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);
|
||||||
|
cbManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
|
||||||
|
cbManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
|
||||||
|
|
||||||
|
Log.Info("Connecting to Steam...");
|
||||||
|
|
||||||
|
steamClient.Connect();
|
||||||
|
|
||||||
|
await logonTaskCompletionSource.Task;
|
||||||
|
return logonTaskCompletionSource.Task.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelLogon()
|
||||||
|
{
|
||||||
|
logonTaskCompletionSource?.SetCanceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Dictionary<ulong, PublishedItemDetails>> GetPublishedFileDetails(IEnumerable<ulong> workshopIds)
|
||||||
|
{
|
||||||
|
//if (!IsReady)
|
||||||
|
// throw new Exception("SteamWorkshopService not initialized!");
|
||||||
|
|
||||||
|
using (dynamic remoteStorage = SteamKit2.WebAPI.GetInterface("ISteamRemoteStorage"))
|
||||||
|
{
|
||||||
|
KeyValue allFilesDetails = null ;
|
||||||
|
remoteStorage.Timeout = TimeSpan.FromSeconds(30);
|
||||||
|
allFilesDetails = await Task.Run(delegate {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return remoteStorage.GetPublishedFileDetails1(
|
||||||
|
itemcount: workshopIds.Count(),
|
||||||
|
publishedfileids: workshopIds,
|
||||||
|
method: HttpMethod.Post);
|
||||||
|
// var ifaceArgs = new Dictionary<string, string>();
|
||||||
|
// ifaceArgs["itemcount"] = workshopIds.Count().ToString();
|
||||||
|
// no idea if that formatting is correct - in fact I get a 404 response
|
||||||
|
// ifaceArgs["publishedfileids"] = string.Join(",", workshopIds);
|
||||||
|
// return remoteStorage.Call(HttpMethod.Post, "GetPublishedFileDetails", args: ifaceArgs);
|
||||||
|
}
|
||||||
|
catch (HttpRequestException e)
|
||||||
|
{
|
||||||
|
Log.Error($"Fetching File Details failed: {e.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (allFilesDetails == null)
|
||||||
|
return null;
|
||||||
|
//fileDetails = remoteStorage.Call(HttpMethod.Post, "GetPublishedFileDetails", 1, new Dictionary<string, string>() { { "itemcount", workshopIds.Count().ToString() }, { "publishedfileids", workshopIds.ToString() } });
|
||||||
|
var detailsList = allFilesDetails?.Children.Find((KeyValue kv) => kv.Name == "publishedfiledetails")?.Children;
|
||||||
|
var resultCount = allFilesDetails?.GetValueOrDefault<int>("resultcount");
|
||||||
|
if( detailsList == null || resultCount == null)
|
||||||
|
{
|
||||||
|
Log.Error("Received invalid data: ");
|
||||||
|
#if DEBUG
|
||||||
|
if(allFilesDetails != null)
|
||||||
|
PrintKeyValue(allFilesDetails);
|
||||||
|
return null;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if ( detailsList.Count != workshopIds.Count() || resultCount != workshopIds.Count())
|
||||||
|
{
|
||||||
|
Log.Error($"Received unexpected number of fileDetails. Expected: {workshopIds.Count()}, Received: {resultCount}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new Dictionary<ulong, PublishedItemDetails>();
|
||||||
|
for( int i = 0; i < resultCount; i++ )
|
||||||
|
{
|
||||||
|
var fileDetails = detailsList[i];
|
||||||
|
|
||||||
|
var tagContainer = fileDetails.Children.Find(item => item.Name == "tags");
|
||||||
|
List<string> tags = new List<string>();
|
||||||
|
if (tagContainer != null)
|
||||||
|
foreach (var tagKv in tagContainer.Children)
|
||||||
|
{
|
||||||
|
var tag = tagKv.Children.Find(item => item.Name == "tag")?.Value;
|
||||||
|
if( tag != null)
|
||||||
|
tags.Add(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
var publishedFileId = fileDetails.GetValueOrDefault<ulong>("publishedfileid");
|
||||||
|
result[publishedFileId] = new PublishedItemDetails()
|
||||||
|
{
|
||||||
|
PublishedFileId = publishedFileId,
|
||||||
|
Views = fileDetails.GetValueOrDefault<uint>("views"),
|
||||||
|
Subscriptions = fileDetails.GetValueOrDefault<uint>("subscriptions"),
|
||||||
|
TimeUpdated = DateTimeOffset.FromUnixTimeSeconds(fileDetails.GetValueOrDefault<long>("time_updated")).DateTime,
|
||||||
|
TimeCreated = DateTimeOffset.FromUnixTimeSeconds(fileDetails.GetValueOrDefault<long>("time_created")).DateTime,
|
||||||
|
Description = fileDetails.GetValueOrDefault<string>("description"),
|
||||||
|
Title = fileDetails.GetValueOrDefault<string>("title"),
|
||||||
|
FileUrl = fileDetails.GetValueOrDefault<string>("file_url"),
|
||||||
|
FileSize = fileDetails.GetValueOrDefault<long>("file_size"),
|
||||||
|
FileName = fileDetails.GetValueOrDefault<string>("filename"),
|
||||||
|
ConsumerAppId = fileDetails.GetValueOrDefault<ulong>("consumer_app_id"),
|
||||||
|
CreatorAppId = fileDetails.GetValueOrDefault<ulong>("creator_app_id"),
|
||||||
|
Creator = fileDetails.GetValueOrDefault<ulong>("creator"),
|
||||||
|
Tags = tags.ToArray()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Space Engineers has transitioned to Steam's UGC api, therefore this method might not always work!")]
|
||||||
|
public async Task DownloadPublishedFile(PublishedItemDetails fileDetails, string dir, string name = null)
|
||||||
|
{
|
||||||
|
var fullPath = Path.Combine(dir, name);
|
||||||
|
if (name == null)
|
||||||
|
name = fileDetails.FileName;
|
||||||
|
var expectedSize = (fileDetails.FileSize == 0) ? -1 : fileDetails.FileSize;
|
||||||
|
|
||||||
|
using (var client = new WebClient())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var downloadTask = client.DownloadFileTaskAsync(fileDetails.FileUrl, Path.Combine(dir, name));
|
||||||
|
DateTime start = DateTime.Now;
|
||||||
|
for (int i = 0; i < 30; i++)
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
if (downloadTask.IsCompleted)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ( !downloadTask.IsCompleted )
|
||||||
|
{
|
||||||
|
client.CancelAsync();
|
||||||
|
throw new Exception("Timeout while attempting to downloading published workshop item!");
|
||||||
|
}
|
||||||
|
//var text = await client.DownloadStringTaskAsync(url);
|
||||||
|
//File.WriteAllText(fullPath, text);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to download workshop item! /n" +
|
||||||
|
$"{e.Message} - url: {fileDetails.FileUrl}, path: {Path.Combine(dir, name)}");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Printable
|
||||||
|
{
|
||||||
|
public KeyValue Data;
|
||||||
|
public int Offset;
|
||||||
|
|
||||||
|
public void Print()
|
||||||
|
{
|
||||||
|
Log.Info($"{new string(' ', Offset)}{Data.Name}: {Data.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrintKeyValue(KeyValue data)
|
||||||
|
{
|
||||||
|
|
||||||
|
var dataSet = new Stack<Printable>();
|
||||||
|
dataSet.Push(new Printable()
|
||||||
|
{
|
||||||
|
Data = data,
|
||||||
|
Offset = 0
|
||||||
|
});
|
||||||
|
while (dataSet.Count != 0)
|
||||||
|
{
|
||||||
|
var printable = dataSet.Pop();
|
||||||
|
foreach (var child in printable.Data.Children)
|
||||||
|
dataSet.Push(new Printable()
|
||||||
|
{
|
||||||
|
Data = child,
|
||||||
|
Offset = printable.Offset + 2
|
||||||
|
});
|
||||||
|
printable.Print();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region CALLBACKS
|
||||||
|
private void OnConnected( SteamClient.ConnectedCallback callback)
|
||||||
|
{
|
||||||
|
Log.Info("Connected to Steam! Logging in '{0}'...", Username);
|
||||||
|
if( Username == "anonymous" )
|
||||||
|
steamUser.LogOnAnonymous();
|
||||||
|
else
|
||||||
|
steamUser.LogOn(new SteamUser.LogOnDetails
|
||||||
|
{
|
||||||
|
Username = Username,
|
||||||
|
Password = password
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisconnected( SteamClient.DisconnectedCallback callback )
|
||||||
|
{
|
||||||
|
Log.Info("Disconnected from Steam");
|
||||||
|
IsReady = false;
|
||||||
|
IsRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoggedOn( SteamUser.LoggedOnCallback callback )
|
||||||
|
{
|
||||||
|
if( callback.Result != EResult.OK )
|
||||||
|
{
|
||||||
|
string msg;
|
||||||
|
if( callback.Result == EResult.AccountLogonDenied )
|
||||||
|
{
|
||||||
|
msg = "Unable to logon to Steam: This account is Steamguard protected.";
|
||||||
|
Log.Warn(msg);
|
||||||
|
logonTaskCompletionSource.SetException(new Exception(msg));
|
||||||
|
IsRunning = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = $"Unable to logon to Steam: {callback.Result} / {callback.ExtendedResult}";
|
||||||
|
Log.Warn(msg);
|
||||||
|
logonTaskCompletionSource.SetException(new Exception(msg));
|
||||||
|
IsRunning = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IsReady = true;
|
||||||
|
Log.Info("Successfully logged on!");
|
||||||
|
logonTaskCompletionSource.SetResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoggedOff( SteamUser.LoggedOffCallback callback )
|
||||||
|
{
|
||||||
|
IsReady = false;
|
||||||
|
Log.Info($"Logged off of Steam: {callback.Result}");
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -273,8 +273,7 @@ namespace Torch
|
|||||||
MySessionLoader.LoadSingleplayerSession(sessionPath);
|
MySessionLoader.LoadSingleplayerSession(sessionPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ulong checkpointSize;
|
MyObjectBuilder_Checkpoint checkpoint = MyLocalCache.LoadCheckpoint(sessionPath, out ulong checkpointSize);
|
||||||
MyObjectBuilder_Checkpoint checkpoint = MyLocalCache.LoadCheckpoint(sessionPath, out checkpointSize);
|
|
||||||
if (MySession.IsCompatibleVersion(checkpoint))
|
if (MySession.IsCompatibleVersion(checkpoint))
|
||||||
{
|
{
|
||||||
if (MyWorkshop.DownloadWorldModsBlocking(checkpoint.Mods, null).Success)
|
if (MyWorkshop.DownloadWorldModsBlocking(checkpoint.Mods, null).Success)
|
||||||
|
@@ -2,7 +2,12 @@
|
|||||||
<packages>
|
<packages>
|
||||||
<package id="ControlzEx" version="3.0.2.4" targetFramework="net461" />
|
<package id="ControlzEx" version="3.0.2.4" targetFramework="net461" />
|
||||||
<package id="MahApps.Metro" version="1.6.1" targetFramework="net461" />
|
<package id="MahApps.Metro" version="1.6.1" targetFramework="net461" />
|
||||||
|
<package id="Microsoft.Win32.Registry" version="4.4.0" targetFramework="net461" />
|
||||||
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
|
||||||
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
<package id="NLog" version="4.4.12" targetFramework="net461" />
|
||||||
<package id="Octokit" version="0.24.0" targetFramework="net461" />
|
<package id="Octokit" version="0.24.0" targetFramework="net461" />
|
||||||
|
<package id="protobuf-net" version="2.1.0" targetFramework="net461" />
|
||||||
|
<package id="SteamKit2" version="2.1.0" targetFramework="net461" />
|
||||||
|
<package id="System.Security.AccessControl" version="4.4.0" targetFramework="net461" />
|
||||||
|
<package id="System.Security.Principal.Windows" version="4.4.0" targetFramework="net461" />
|
||||||
</packages>
|
</packages>
|
Reference in New Issue
Block a user