Merge branch 'Patron' of https://github.com/TorchAPI/Torch into survival

This commit is contained in:
Brant Martin
2019-02-25 15:27:34 -05:00
39 changed files with 2012 additions and 337 deletions

2
Jenkinsfile vendored
View File

@@ -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"

View File

@@ -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>

View File

@@ -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;
} }
} }

View File

@@ -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

View File

@@ -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);

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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); }

View File

@@ -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);
}
}
} }
} }

View File

@@ -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>());
} }

View File

@@ -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;
} }
} }

View 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;
}
}
}
}

View File

@@ -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"

View 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;
}
}
}

View 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!");
}
}
}

View File

@@ -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" />

View File

@@ -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);
}
} }
} }

View 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>

View 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();
});
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();

View File

@@ -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.");

View File

@@ -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>

View File

@@ -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/>

View 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;
}
}

View File

@@ -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());
}
} }
} }

View File

@@ -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
} }
} }

View 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
}
}

View File

@@ -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);
} }
} }
} }

View File

@@ -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)

View File

@@ -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" />

View File

@@ -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]));
}
} }
} }

View 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)}'!");
}
}
}
}

View 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;
}
}

View 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
}
}

View File

@@ -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)

View File

@@ -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>