Optimize UI, add easily accessible restart code, fix bug in network manager RaiseEvent
This commit is contained in:
@@ -75,11 +75,16 @@ namespace Torch.API
|
||||
/// </summary>
|
||||
void Stop();
|
||||
|
||||
/// <summary>
|
||||
/// Restart the Torch instance.
|
||||
/// </summary>
|
||||
void Restart();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a save of the game.
|
||||
/// </summary>
|
||||
/// <param name="callerId">Id of the player who initiated the save.</param>
|
||||
void Save(long callerId);
|
||||
Task Save(long callerId);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the Torch instance.
|
||||
|
@@ -14,12 +14,12 @@ namespace Torch.API.Managers
|
||||
/// <summary>
|
||||
/// Fired when plugins are loaded.
|
||||
/// </summary>
|
||||
event Action<List<ITorchPlugin>> PluginsLoaded;
|
||||
event Action<IList<ITorchPlugin>> PluginsLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of loaded plugins.
|
||||
/// </summary>
|
||||
ObservableCollection<ITorchPlugin> Plugins { get; }
|
||||
IList<ITorchPlugin> Plugins { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates all loaded plugins.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.186.642")]
|
||||
[assembly: AssemblyFileVersion("1.0.186.642")]
|
||||
[assembly: AssemblyVersion("1.0.195.514")]
|
||||
[assembly: AssemblyFileVersion("1.0.195.514")]
|
@@ -100,6 +100,8 @@ namespace Torch.Server
|
||||
var waitProc = Process.GetProcessById(pid);
|
||||
_log.Warn($"Waiting for process {pid} to exit.");
|
||||
waitProc.WaitForExit();
|
||||
_log.Info("Continuing in 5 seconds.");
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -245,8 +247,8 @@ quit";
|
||||
}*/
|
||||
|
||||
_server = new TorchServer(config);
|
||||
_server.Init();
|
||||
|
||||
_server.Init();
|
||||
if (config.NoGui || config.Autostart)
|
||||
{
|
||||
new Thread(() => _server.Start()).Start();
|
||||
@@ -255,7 +257,6 @@ quit";
|
||||
if (!config.NoGui)
|
||||
{
|
||||
var ui = new TorchUI((TorchServer)_server);
|
||||
ui.LoadConfig(config);
|
||||
ui.ShowDialog();
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.186.642")]
|
||||
[assembly: AssemblyFileVersion("1.0.186.642")]
|
||||
[assembly: AssemblyVersion("1.0.195.514")]
|
||||
[assembly: AssemblyFileVersion("1.0.195.514")]
|
@@ -24,7 +24,7 @@ namespace Torch.Server
|
||||
public bool Update { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[XmlIgnore, Arg("autostart", "Start the server immediately.")]
|
||||
[Arg("autostart", "Start the server immediately.")]
|
||||
public bool Autostart { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -32,7 +32,7 @@ namespace Torch.Server
|
||||
public bool RestartOnCrash { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[XmlIgnore, Arg("nogui", "Do not show the Torch UI.")]
|
||||
[Arg("nogui", "Do not show the Torch UI.")]
|
||||
public bool NoGui { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@@ -10,6 +10,7 @@ using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Xml.Serialization.GeneratedAssembly;
|
||||
using Sandbox.Engine.Analytics;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
@@ -20,6 +21,7 @@ using Torch.Managers;
|
||||
using VRage.Dedicated;
|
||||
using VRage.FileSystem;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
using VRage.Game.ObjectBuilder;
|
||||
using VRage.Game.SessionComponents;
|
||||
using VRage.Library;
|
||||
@@ -49,6 +51,7 @@ namespace Torch.Server
|
||||
private float _simRatio;
|
||||
private readonly AutoResetEvent _stopHandle = new AutoResetEvent(false);
|
||||
private Timer _watchdog;
|
||||
private Stopwatch _uptime;
|
||||
|
||||
public TorchServer(TorchConfig config = null)
|
||||
{
|
||||
@@ -133,6 +136,7 @@ namespace Torch.Server
|
||||
if (State != ServerState.Stopped)
|
||||
return;
|
||||
|
||||
_uptime = Stopwatch.StartNew();
|
||||
IsRunning = true;
|
||||
GameThread = Thread.CurrentThread;
|
||||
Config.Save();
|
||||
@@ -166,7 +170,8 @@ namespace Torch.Server
|
||||
{
|
||||
base.Update();
|
||||
SimulationRatio = Sync.ServerSimulationRatio;
|
||||
ElapsedPlayTime = MySession.Static?.ElapsedPlayTime ?? default(TimeSpan);
|
||||
var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds));
|
||||
ElapsedPlayTime = elapsed;
|
||||
|
||||
if (_watchdog == null && Instance.Config.TickTimeout > 0)
|
||||
{
|
||||
@@ -215,15 +220,21 @@ namespace Torch.Server
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
/// <summary>
|
||||
/// Restart the program. DOES NOT SAVE!
|
||||
/// </summary>
|
||||
public override void Restart()
|
||||
{
|
||||
|
||||
var exe = Assembly.GetExecutingAssembly().Location;
|
||||
((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
||||
Process.Start(exe, Config.ToString());
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Save(long callerId)
|
||||
public override Task Save(long callerId)
|
||||
{
|
||||
base.SaveGameAsync((statusCode) => SaveCompleted(statusCode, callerId));
|
||||
return SaveGameAsync(statusCode => SaveCompleted(statusCode, callerId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -231,7 +242,7 @@ namespace Torch.Server
|
||||
/// </summary>
|
||||
/// <param name="statusCode">Return code of the save operation</param>
|
||||
/// <param name="callerId">Caller of the save operation</param>
|
||||
private void SaveCompleted(SaveGameStatus statusCode, long callerId)
|
||||
private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0)
|
||||
{
|
||||
switch (statusCode)
|
||||
{
|
||||
|
@@ -56,7 +56,7 @@ namespace Torch.Server.ViewModels
|
||||
|
||||
public SessionSettingsViewModel SessionSettings { get; }
|
||||
|
||||
public ObservableCollection<string> WorldPaths { get; } = new ObservableCollection<string>();
|
||||
public ObservableList<string> WorldPaths { get; } = new ObservableList<string>();
|
||||
private string _administrators;
|
||||
public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } }
|
||||
private string _banned;
|
||||
|
@@ -15,7 +15,7 @@ namespace Torch.Server.ViewModels.Blocks
|
||||
public class BlockViewModel : EntityViewModel
|
||||
{
|
||||
public IMyTerminalBlock Block { get; }
|
||||
public MTObservableCollection<PropertyViewModel> Properties { get; } = new MTObservableCollection<PropertyViewModel>();
|
||||
public ObservableList<PropertyViewModel> Properties { get; } = new ObservableList<PropertyViewModel>();
|
||||
|
||||
public string FullName => $"{Block.CubeGrid.CustomName} - {Block.CustomName}";
|
||||
|
||||
@@ -24,8 +24,11 @@ namespace Torch.Server.ViewModels.Blocks
|
||||
get => Block?.CustomName ?? "null";
|
||||
set
|
||||
{
|
||||
TorchBase.Instance.InvokeBlocking(() => Block.CustomName = value);
|
||||
TorchBase.Instance.Invoke(() =>
|
||||
{
|
||||
Block.CustomName = value;
|
||||
OnPropertyChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +40,11 @@ namespace Torch.Server.ViewModels.Blocks
|
||||
get => ((MySlimBlock)Block.SlimBlock).BuiltBy;
|
||||
set
|
||||
{
|
||||
TorchBase.Instance.InvokeBlocking(() => ((MySlimBlock)Block.SlimBlock).TransferAuthorship(value));
|
||||
TorchBase.Instance.Invoke(() =>
|
||||
{
|
||||
((MySlimBlock)Block.SlimBlock).TransferAuthorship(value);
|
||||
OnPropertyChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -16,17 +16,15 @@ namespace Torch.Server.ViewModels.Blocks
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
var val = default(T);
|
||||
TorchBase.Instance.InvokeBlocking(() => val = _prop.GetValue(Block.Block));
|
||||
return val;
|
||||
}
|
||||
get => _prop.GetValue(Block.Block);
|
||||
set
|
||||
{
|
||||
TorchBase.Instance.InvokeBlocking(() => _prop.SetValue(Block.Block, value));
|
||||
TorchBase.Instance.Invoke(() =>
|
||||
{
|
||||
_prop.SetValue(Block.Block, value);
|
||||
OnPropertyChanged();
|
||||
Block.RefreshModel();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -40,6 +40,7 @@ namespace Torch.Server.ViewModels.Entities
|
||||
public EntityViewModel(IMyEntity entity, EntityTreeViewModel tree)
|
||||
{
|
||||
Entity = entity;
|
||||
Tree = tree;
|
||||
}
|
||||
|
||||
public EntityViewModel()
|
||||
|
@@ -1,6 +1,9 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using Sandbox.Game.Entities;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
using Sandbox.Game.World;
|
||||
using Sandbox.ModAPI;
|
||||
using Torch.Server.ViewModels.Blocks;
|
||||
|
||||
@@ -9,17 +12,16 @@ namespace Torch.Server.ViewModels.Entities
|
||||
public class GridViewModel : EntityViewModel, ILazyLoad
|
||||
{
|
||||
private MyCubeGrid Grid => (MyCubeGrid)Entity;
|
||||
public MTObservableCollection<BlockViewModel> Blocks { get; } = new MTObservableCollection<BlockViewModel>();
|
||||
private static readonly Logger Log = LogManager.GetLogger(nameof(GridViewModel));
|
||||
public ObservableList<BlockViewModel> Blocks { get; } = new ObservableList<BlockViewModel>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DescriptiveName => $"{Name} ({Grid.BlocksCount} blocks)";
|
||||
public string DescriptiveName { get; }
|
||||
|
||||
public GridViewModel() { }
|
||||
|
||||
public GridViewModel(MyCubeGrid grid, EntityTreeViewModel tree) : base(grid, tree)
|
||||
{
|
||||
Log.Debug($"Creating model {Grid.DisplayName}");
|
||||
DescriptiveName = $"{grid.DisplayName} ({grid.BlocksCount} blocks)";
|
||||
Blocks.Add(new BlockViewModel(null, Tree));
|
||||
}
|
||||
|
||||
@@ -28,7 +30,6 @@ namespace Torch.Server.ViewModels.Entities
|
||||
if (obj.FatBlock != null)
|
||||
Blocks.RemoveWhere(b => b.Block.EntityId == obj.FatBlock?.EntityId);
|
||||
|
||||
Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName);
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
|
||||
@@ -36,9 +37,8 @@ namespace Torch.Server.ViewModels.Entities
|
||||
{
|
||||
var block = obj.FatBlock as IMyTerminalBlock;
|
||||
if (block != null)
|
||||
Blocks.Add(new BlockViewModel(block, Tree));
|
||||
Blocks.Insert(new BlockViewModel(block, Tree), b => b.Name);
|
||||
|
||||
Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName);
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
|
||||
@@ -48,20 +48,23 @@ namespace Torch.Server.ViewModels.Entities
|
||||
if (_load)
|
||||
return;
|
||||
|
||||
Log.Debug($"Loading model {Grid.DisplayName}");
|
||||
_load = true;
|
||||
Blocks.Clear();
|
||||
TorchBase.Instance.InvokeBlocking(() =>
|
||||
TorchBase.Instance.Invoke(() =>
|
||||
{
|
||||
foreach (var block in Grid.GetFatBlocks().Where(b => b is IMyTerminalBlock))
|
||||
{
|
||||
Blocks.Add(new BlockViewModel((IMyTerminalBlock)block, Tree));
|
||||
}
|
||||
});
|
||||
Blocks.Sort(b => b.Block.GetType().AssemblyQualifiedName);
|
||||
|
||||
Grid.OnBlockAdded += Grid_OnBlockAdded;
|
||||
Grid.OnBlockRemoved += Grid_OnBlockRemoved;
|
||||
|
||||
Tree.ControlDispatcher.BeginInvoke(() =>
|
||||
{
|
||||
Blocks.Sort(b => b.Block.CustomName);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ namespace Torch.Server.ViewModels.Entities
|
||||
|
||||
public override bool CanStop => false;
|
||||
|
||||
public MTObservableCollection<GridViewModel> AttachedGrids { get; } = new MTObservableCollection<GridViewModel>();
|
||||
public ObservableList<GridViewModel> AttachedGrids { get; } = new ObservableList<GridViewModel>();
|
||||
|
||||
public async Task UpdateAttachedGrids()
|
||||
{
|
||||
|
@@ -3,22 +3,28 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using Sandbox.Game.Entities;
|
||||
using Sandbox.Game.Entities.Character;
|
||||
using Torch.Server.ViewModels.Entities;
|
||||
using VRage.Game.ModAPI;
|
||||
using VRage.ModAPI;
|
||||
using System.Windows.Threading;
|
||||
using NLog;
|
||||
|
||||
namespace Torch.Server.ViewModels
|
||||
{
|
||||
public class EntityTreeViewModel : ViewModel
|
||||
{
|
||||
public MTObservableCollection<GridViewModel> Grids { get; set; } = new MTObservableCollection<GridViewModel>();
|
||||
public MTObservableCollection<CharacterViewModel> Characters { get; set; } = new MTObservableCollection<CharacterViewModel>();
|
||||
public MTObservableCollection<EntityViewModel> FloatingObjects { get; set; } = new MTObservableCollection<EntityViewModel>();
|
||||
public MTObservableCollection<VoxelMapViewModel> VoxelMaps { get; set; } = new MTObservableCollection<VoxelMapViewModel>();
|
||||
//TODO: these should be sorted sets for speed
|
||||
public ObservableList<GridViewModel> Grids { get; set; } = new ObservableList<GridViewModel>();
|
||||
public ObservableList<CharacterViewModel> Characters { get; set; } = new ObservableList<CharacterViewModel>();
|
||||
public ObservableList<EntityViewModel> FloatingObjects { get; set; } = new ObservableList<EntityViewModel>();
|
||||
public ObservableList<VoxelMapViewModel> VoxelMaps { get; set; } = new ObservableList<VoxelMapViewModel>();
|
||||
public Dispatcher ControlDispatcher => _control.Dispatcher;
|
||||
|
||||
private EntityViewModel _currentEntity;
|
||||
private UserControl _control;
|
||||
|
||||
public EntityViewModel CurrentEntity
|
||||
{
|
||||
@@ -26,7 +32,12 @@ namespace Torch.Server.ViewModels
|
||||
set { _currentEntity = value; OnPropertyChanged(); }
|
||||
}
|
||||
|
||||
public EntityTreeViewModel()
|
||||
public EntityTreeViewModel(UserControl control)
|
||||
{
|
||||
_control = control;
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
MyEntities.OnEntityAdd += MyEntities_OnEntityAdd;
|
||||
MyEntities.OnEntityRemove += MyEntities_OnEntityRemove;
|
||||
@@ -56,20 +67,16 @@ namespace Torch.Server.ViewModels
|
||||
switch (obj)
|
||||
{
|
||||
case MyCubeGrid grid:
|
||||
if (Grids.All(g => g.Entity.EntityId != obj.EntityId))
|
||||
Grids.Add(new GridViewModel(grid, this));
|
||||
Grids.Insert(new GridViewModel(grid, this), g => g.Name);
|
||||
break;
|
||||
case MyCharacter character:
|
||||
if (Characters.All(g => g.Entity.EntityId != obj.EntityId))
|
||||
Characters.Add(new CharacterViewModel(character, this));
|
||||
Characters.Insert(new CharacterViewModel(character, this), c => c.Name);
|
||||
break;
|
||||
case MyFloatingObject floating:
|
||||
if (FloatingObjects.All(g => g.Entity.EntityId != obj.EntityId))
|
||||
FloatingObjects.Add(new FloatingObjectViewModel(floating, this));
|
||||
FloatingObjects.Insert(new FloatingObjectViewModel(floating, this), f => f.Name);
|
||||
break;
|
||||
case MyVoxelBase voxel:
|
||||
if (VoxelMaps.All(g => g.Entity.EntityId != obj.EntityId))
|
||||
VoxelMaps.Add(new VoxelMapViewModel(voxel, this));
|
||||
VoxelMaps.Insert(new VoxelMapViewModel(voxel, this), v => v.Name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ namespace Torch.Server.ViewModels
|
||||
{
|
||||
public class PluginManagerViewModel : ViewModel
|
||||
{
|
||||
public MTObservableCollection<PluginViewModel> Plugins { get; } = new MTObservableCollection<PluginViewModel>();
|
||||
public ObservableList<PluginViewModel> Plugins { get; } = new ObservableList<PluginViewModel>();
|
||||
|
||||
private PluginViewModel _selectedPlugin;
|
||||
public PluginViewModel SelectedPlugin
|
||||
@@ -24,10 +24,12 @@ namespace Torch.Server.ViewModels
|
||||
|
||||
public PluginManagerViewModel(IPluginManager pluginManager)
|
||||
{
|
||||
foreach (var plugin in pluginManager)
|
||||
Plugins.Add(new PluginViewModel(plugin));
|
||||
pluginManager.PluginsLoaded += PluginManager_PluginsLoaded;
|
||||
}
|
||||
|
||||
private void PluginManager_PluginsLoaded(List<ITorchPlugin> obj)
|
||||
private void PluginManager_PluginsLoaded(IList<ITorchPlugin> obj)
|
||||
{
|
||||
Plugins.Clear();
|
||||
foreach (var plugin in obj)
|
||||
|
@@ -35,7 +35,7 @@ namespace Torch.Server.ViewModels
|
||||
BlockLimits.Add(new BlockLimitViewModel(this, limit.Key, limit.Value));
|
||||
}
|
||||
|
||||
public MTObservableCollection<BlockLimitViewModel> BlockLimits { get; } = new MTObservableCollection<BlockLimitViewModel>();
|
||||
public ObservableList<BlockLimitViewModel> BlockLimits { get; } = new ObservableList<BlockLimitViewModel>();
|
||||
|
||||
#region Multipliers
|
||||
|
||||
|
@@ -11,6 +11,7 @@
|
||||
<TextBox x:Name="Message" DockPanel.Dock="Left" Margin="5,5,5,5" KeyDown="Message_OnKeyDown"></TextBox>
|
||||
</DockPanel>
|
||||
<ListView x:Name="ChatItems" ItemsSource="{Binding ChatHistory}" Margin="5,5,5,5">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled"/>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<WrapPanel>
|
||||
|
@@ -41,8 +41,10 @@ namespace Torch.Server
|
||||
{
|
||||
_server = (TorchBase)server;
|
||||
_multiplayer = (MultiplayerManager)server.Multiplayer;
|
||||
ChatItems.Items.Clear();
|
||||
DataContext = _multiplayer;
|
||||
_multiplayer.ChatHistory.CollectionChanged += ChatHistory_CollectionChanged;
|
||||
if (_multiplayer.ChatHistory is INotifyCollectionChanged ncc)
|
||||
ncc.CollectionChanged += ChatHistory_CollectionChanged;
|
||||
}
|
||||
|
||||
private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
@@ -74,22 +76,26 @@ namespace Torch.Server
|
||||
return;
|
||||
|
||||
var commands = _server.Commands;
|
||||
string response = null;
|
||||
if (commands.IsCommand(text))
|
||||
{
|
||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text));
|
||||
_server.InvokeBlocking(() =>
|
||||
_server.Invoke(() =>
|
||||
{
|
||||
response = commands.HandleCommandFromServer(text);
|
||||
var response = commands.HandleCommandFromServer(text);
|
||||
Dispatcher.BeginInvoke(() => OnMessageEntered_Callback(response));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_server.Multiplayer.SendMessage(text);
|
||||
}
|
||||
Message.Text = "";
|
||||
}
|
||||
|
||||
private void OnMessageEntered_Callback(string response)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(response))
|
||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
|
||||
Message.Text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -80,7 +80,7 @@
|
||||
</DockPanel>
|
||||
<Button Content="Add" Margin="3" Click="AddLimit_OnClick" />
|
||||
<ListView ItemsSource="{Binding BlockLimits}" Margin="3">
|
||||
<ListBox.ItemTemplate>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding BlockType}" Width="150" Margin="3" />
|
||||
@@ -88,7 +88,7 @@
|
||||
<Button Content=" X " Margin="3" Click="RemoveLimit_OnClick" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
|
@@ -53,7 +53,6 @@ namespace Torch.Server.Views
|
||||
Log.Info("Saved DS config.");
|
||||
try
|
||||
{
|
||||
//var checkpoint = MyLocalCache.LoadCheckpoint(Config.LoadWorld, out _);
|
||||
MyObjectBuilderSerializer.DeserializeXML(Path.Combine(Config.LoadWorld, "Sandbox.sbc"), out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
||||
if (checkpoint == null)
|
||||
{
|
||||
|
@@ -8,9 +8,6 @@
|
||||
xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities"
|
||||
xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.DataContext>
|
||||
<viewModels:EntityTreeViewModel />
|
||||
</UserControl.DataContext>
|
||||
<DockPanel>
|
||||
<DockPanel DockPanel.Dock="Left">
|
||||
<StackPanel DockPanel.Dock="Bottom">
|
||||
|
@@ -27,12 +27,14 @@ namespace Torch.Server.Views
|
||||
/// </summary>
|
||||
public partial class EntitiesControl : UserControl
|
||||
{
|
||||
public EntityTreeViewModel Entities { get; set; } = new EntityTreeViewModel();
|
||||
public EntityTreeViewModel Entities { get; set; }
|
||||
|
||||
public EntitiesControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
Entities = new EntityTreeViewModel(this);
|
||||
DataContext = Entities;
|
||||
Entities.Init();
|
||||
}
|
||||
|
||||
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
|
@@ -47,6 +47,7 @@ namespace Torch.Server
|
||||
Chat.BindServer(server);
|
||||
PlayerList.BindServer(server);
|
||||
Plugins.BindServer(server);
|
||||
LoadConfig((TorchConfig)server.Config);
|
||||
}
|
||||
|
||||
public void LoadConfig(TorchConfig config)
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@@ -10,15 +9,18 @@ using System.Windows.Threading;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
[Obsolete("Use ObservableList<T>.")]
|
||||
public class MTObservableCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
public override event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
|
||||
if (collectionChanged != null)
|
||||
foreach (NotifyCollectionChangedEventHandler nh in collectionChanged.GetInvocationList())
|
||||
foreach (var del in collectionChanged.GetInvocationList())
|
||||
{
|
||||
var nh = (NotifyCollectionChangedEventHandler)del;
|
||||
var dispObj = nh.Target as DispatcherObject;
|
||||
|
||||
var dispatcher = dispObj?.Dispatcher;
|
||||
|
186
Torch/Collections/ObservableList.cs
Normal file
186
Torch/Collections/ObservableList.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
/// <summary>
|
||||
/// An observable version of <see cref="List{T}"/>.
|
||||
/// </summary>
|
||||
public class ObservableList<T> : IList<T>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||
{
|
||||
private List<T> _internalList = new List<T>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
_internalList.Clear();
|
||||
OnPropertyChanged(nameof(Count));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return _internalList.Contains(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
_internalList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(T item)
|
||||
{
|
||||
var oldIndex = _internalList.IndexOf(item);
|
||||
if (!_internalList.Remove(item))
|
||||
return false;
|
||||
|
||||
OnPropertyChanged(nameof(Count));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, oldIndex));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _internalList.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(T item)
|
||||
{
|
||||
_internalList.Add(item);
|
||||
OnPropertyChanged(nameof(Count));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, Count - 1));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int IndexOf(T item) => _internalList.IndexOf(item);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
_internalList.Insert(index, item);
|
||||
OnPropertyChanged(nameof(Count));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an item in order based on the provided selector and comparer. This will only work properly on a pre-sorted list.
|
||||
/// </summary>
|
||||
public void Insert<TKey>(T item, Func<T, TKey> selector, IComparer<TKey> comparer = null)
|
||||
{
|
||||
comparer = comparer ?? Comparer<TKey>.Default;
|
||||
var key1 = selector(item);
|
||||
for (var i = 0; i < _internalList.Count; i++)
|
||||
{
|
||||
var key2 = selector(_internalList[i]);
|
||||
if (comparer.Compare(key1, key2) < 1)
|
||||
{
|
||||
Insert(i, item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Add(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
var old = this[index];
|
||||
_internalList.RemoveAt(index);
|
||||
OnPropertyChanged(nameof(Count));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old, index));
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => _internalList[index];
|
||||
set
|
||||
{
|
||||
var old = _internalList[index];
|
||||
if (old.Equals(value))
|
||||
return;
|
||||
|
||||
_internalList[index] = value;
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, old, index));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the list using the given selector and comparer./>
|
||||
/// </summary>
|
||||
public void Sort<TKey>(Func<T, TKey> selector, IComparer<TKey> comparer = null)
|
||||
{
|
||||
comparer = comparer ?? Comparer<TKey>.Default;
|
||||
var sortedItems = _internalList.OrderBy(selector, comparer).ToList();
|
||||
|
||||
_internalList = sortedItems;
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items that satisfy the given condition.
|
||||
/// </summary>
|
||||
public void RemoveWhere(Func<T, bool> condition)
|
||||
{
|
||||
for (var i = Count - 1; i > 0; i--)
|
||||
{
|
||||
if (condition?.Invoke(this[i]) ?? false)
|
||||
RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
var collectionChanged = CollectionChanged;
|
||||
if (collectionChanged != null)
|
||||
foreach (var del in collectionChanged.GetInvocationList())
|
||||
{
|
||||
var nh = (NotifyCollectionChangedEventHandler)del;
|
||||
var dispObj = nh.Target as DispatcherObject;
|
||||
|
||||
var dispatcher = dispObj?.Dispatcher;
|
||||
if (dispatcher != null && !dispatcher.CheckAccess())
|
||||
{
|
||||
dispatcher.BeginInvoke(() => nh.Invoke(this, e), DispatcherPriority.DataBind);
|
||||
continue;
|
||||
}
|
||||
|
||||
nh.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return _internalList.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)_internalList).GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,7 +6,10 @@ using System.Text;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
public class CommandLine
|
||||
/// <summary>
|
||||
/// Base class that adds tools for setting type properties through the command line.
|
||||
/// </summary>
|
||||
public abstract class CommandLine
|
||||
{
|
||||
private readonly string _argPrefix;
|
||||
private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>();
|
||||
|
@@ -21,7 +21,7 @@ namespace Torch.Commands
|
||||
public CommandTree Commands { get; set; } = new CommandTree();
|
||||
private Logger _log = LogManager.GetLogger(nameof(CommandManager));
|
||||
|
||||
public CommandManager(ITorchBase torch, char prefix = '/') : base(torch)
|
||||
public CommandManager(ITorchBase torch, char prefix = '!') : base(torch)
|
||||
{
|
||||
Prefix = prefix;
|
||||
}
|
||||
|
@@ -45,6 +45,42 @@ namespace Torch.Commands
|
||||
}
|
||||
}
|
||||
|
||||
[Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")]
|
||||
public void LongHelp()
|
||||
{
|
||||
var commandManager = Context.Torch.GetManager<CommandManager>();
|
||||
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
|
||||
|
||||
if (node != null)
|
||||
{
|
||||
var command = node.Command;
|
||||
var children = node.Subcommands.Select(x => x.Key);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (command != null)
|
||||
{
|
||||
sb.AppendLine($"Syntax: {command.SyntaxHelp}");
|
||||
sb.Append(command.HelpText);
|
||||
}
|
||||
|
||||
if (node.Subcommands.Count() != 0)
|
||||
sb.Append($"\nSubcommands: {string.Join(", ", children)}");
|
||||
|
||||
Context.Respond(sb.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
var sb = new StringBuilder("Available commands:\n");
|
||||
foreach (var command in commandManager.Commands.WalkTree())
|
||||
{
|
||||
if (command.IsCommand)
|
||||
sb.AppendLine($"{command.Command.SyntaxHelp}\n {command.Command.HelpText}");
|
||||
}
|
||||
Context.Respond(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Command("ver", "Shows the running Torch version.")]
|
||||
[Permission(MyPromoteLevel.None)]
|
||||
public void Version()
|
||||
@@ -62,12 +98,23 @@ namespace Torch.Commands
|
||||
}
|
||||
|
||||
[Command("stop", "Stops the server.")]
|
||||
public void Stop()
|
||||
public void Stop(bool save = true)
|
||||
{
|
||||
Context.Respond("Stopping server.");
|
||||
if (save)
|
||||
Context.Torch.Save(Context.Player?.IdentityId ?? 0).Wait();
|
||||
Context.Torch.Stop();
|
||||
}
|
||||
|
||||
[Command("restart", "Restarts the server.")]
|
||||
public void Restart(bool save = true)
|
||||
{
|
||||
Context.Respond("Restarting server.");
|
||||
if (save)
|
||||
Context.Torch.Save(Context.Player?.IdentityId ?? 0).Wait();
|
||||
Context.Torch.Restart();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a save of the game.
|
||||
/// Caller id defaults to 0 in the case of triggering the chat command from server.
|
||||
|
17
Torch/DispatcherExtensions.cs
Normal file
17
Torch/DispatcherExtensions.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Torch
|
||||
{
|
||||
public static class DispatcherExtensions
|
||||
{
|
||||
public static DispatcherOperation BeginInvoke(this Dispatcher dispatcher, Action action, DispatcherPriority priority = DispatcherPriority.Normal)
|
||||
{
|
||||
return dispatcher.BeginInvoke(priority, action);
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,6 +16,7 @@ using NLog;
|
||||
using Torch;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Game.Entities.Character;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
using Sandbox.Game.World;
|
||||
using Sandbox.ModAPI;
|
||||
@@ -43,7 +44,7 @@ namespace Torch.Managers
|
||||
/// <inheritdoc />
|
||||
public event MessageReceivedDel MessageReceived;
|
||||
|
||||
public MTObservableCollection<IChatMessage> ChatHistory { get; } = new MTObservableCollection<IChatMessage>();
|
||||
public IList<IChatMessage> ChatHistory { get; } = new ObservableList<IChatMessage>();
|
||||
public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>();
|
||||
public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer;
|
||||
private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager));
|
||||
@@ -99,6 +100,17 @@ namespace Torch.Managers
|
||||
return p;
|
||||
}
|
||||
|
||||
public ulong GetSteamId(long identityId)
|
||||
{
|
||||
foreach (var kv in _onlinePlayers)
|
||||
{
|
||||
if (kv.Value.Identity.IdentityId == identityId)
|
||||
return kv.Key.SteamId;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetSteamUsername(ulong steamId)
|
||||
{
|
||||
@@ -108,17 +120,24 @@ namespace Torch.Managers
|
||||
/// <inheritdoc />
|
||||
public void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Red)
|
||||
{
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", message));
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, message));
|
||||
var commands = Torch.GetManager<CommandManager>();
|
||||
if (commands.IsCommand(message))
|
||||
{
|
||||
var response = commands.HandleCommandFromServer(message);
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, response));
|
||||
}
|
||||
else
|
||||
{
|
||||
var msg = new ScriptedChatMsg { Author = author, Font = font, Target = playerId, Text = message };
|
||||
MyMultiplayerBase.SendScriptedChatMessage(ref msg);
|
||||
var character = MySession.Static.Players.TryGetIdentity(playerId)?.Character;
|
||||
var steamId = GetSteamId(playerId);
|
||||
if (character == null)
|
||||
return;
|
||||
|
||||
var addToGlobalHistoryMethod = typeof(MyCharacter).GetMethod("OnGlobalMessageSuccess", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Torch.GetManager<NetworkManager>().RaiseEvent(addToGlobalHistoryMethod, character, steamId, steamId, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -311,7 +311,7 @@ namespace Torch.Managers
|
||||
var parameters = method.GetParameters();
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
if (argTypes[i] != parameters[i].ParameterType)
|
||||
if (argTypes[i + 1] != parameters[i].ParameterType)
|
||||
throw new TypeLoadException($"Type mismatch on method parameters. Expected {string.Join(", ", parameters.Select(p => p.ParameterType.ToString()))} got {string.Join(", ", argTypes.Select(t => t.ToString()))}");
|
||||
}
|
||||
|
||||
|
@@ -23,9 +23,9 @@ namespace Torch.Managers
|
||||
private UpdateManager _updateManager;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ObservableCollection<ITorchPlugin> Plugins { get; } = new ObservableCollection<ITorchPlugin>();
|
||||
public IList<ITorchPlugin> Plugins { get; } = new ObservableList<ITorchPlugin>();
|
||||
|
||||
public event Action<List<ITorchPlugin>> PluginsLoaded;
|
||||
public event Action<IList<ITorchPlugin>> PluginsLoaded;
|
||||
|
||||
public PluginManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
|
@@ -5,9 +5,24 @@
|
||||
/// </summary>
|
||||
public enum SaveGameStatus : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The game was saved.
|
||||
/// </summary>
|
||||
Success = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A save operation is already in progress.
|
||||
/// </summary>
|
||||
SaveInProgress = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The game is not in a save-able state.
|
||||
/// </summary>
|
||||
GameNotReady = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The save operation timed out.
|
||||
/// </summary>
|
||||
TimedOut = 3
|
||||
};
|
||||
}
|
||||
|
@@ -146,6 +146,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ChatMessage.cs" />
|
||||
<Compile Include="Collections\ObservableList.cs" />
|
||||
<Compile Include="DispatcherExtensions.cs" />
|
||||
<Compile Include="SaveGameStatus.cs" />
|
||||
<Compile Include="Collections\KeyTree.cs" />
|
||||
<Compile Include="Collections\ObservableDictionary.cs" />
|
||||
|
@@ -128,7 +128,7 @@ namespace Torch
|
||||
return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId;
|
||||
}
|
||||
|
||||
public async Task SaveGameAsync(Action<SaveGameStatus> callback)
|
||||
public Task SaveGameAsync(Action<SaveGameStatus> callback)
|
||||
{
|
||||
Log.Info("Saving game");
|
||||
|
||||
@@ -142,26 +142,17 @@ namespace Torch
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var e = new AutoResetEvent(false))
|
||||
var e = new AutoResetEvent(false);
|
||||
MyAsyncSaving.Start(() => e.Set());
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
MyAsyncSaving.Start(() =>
|
||||
{
|
||||
MySector.ResetEyeAdaptation = true;
|
||||
e.Set();
|
||||
callback?.Invoke(e.WaitOne(5000) ? SaveGameStatus.Success : SaveGameStatus.TimedOut);
|
||||
e.Dispose();
|
||||
});
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (e.WaitOne(60000))
|
||||
{
|
||||
callback?.Invoke(SaveGameStatus.Success);
|
||||
return;
|
||||
}
|
||||
|
||||
callback?.Invoke(SaveGameStatus.TimedOut);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#region Game Actions
|
||||
@@ -306,19 +297,28 @@ namespace Torch
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void Save(long callerId)
|
||||
public virtual Task Save(long callerId)
|
||||
{
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// <inheritdoc/>
|
||||
public virtual void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Stop() { }
|
||||
public virtual void Stop()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Restart()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Dispose()
|
||||
|
Reference in New Issue
Block a user