Merge branch 'master' into experiment
This commit is contained in:
4
Jenkinsfile
vendored
4
Jenkinsfile
vendored
@@ -50,7 +50,7 @@ node {
|
||||
|
||||
packageAndArchive(buildMode, "torch-server", "Torch.Client*")
|
||||
|
||||
packageAndArchive(buildMode, "torch-client", "Torch.Server*")
|
||||
/*packageAndArchive(buildMode, "torch-client", "Torch.Server*")*/
|
||||
}
|
||||
|
||||
/* Disabled because they fail builds more often than they detect actual problems
|
||||
@@ -73,4 +73,4 @@ node {
|
||||
])
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
72
Torch.Mod/Messages/DialogMessage.cs
Normal file
72
Torch.Mod/Messages/DialogMessage.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ProtoBuf;
|
||||
using Sandbox.ModAPI;
|
||||
|
||||
namespace Torch.Mod.Messages
|
||||
{
|
||||
/// Dialogs are structured as follows
|
||||
///
|
||||
/// _____________________________________
|
||||
/// | Title |
|
||||
/// --------------------------------------
|
||||
/// | Prefix Subtitle |
|
||||
/// --------------------------------------
|
||||
/// | ________________________________ |
|
||||
/// | | Content | |
|
||||
/// | --------------------------------- |
|
||||
/// | ____________ |
|
||||
/// | | ButtonText | |
|
||||
/// | -------------- |
|
||||
/// --------------------------------------
|
||||
///
|
||||
/// Button has a callback on click option,
|
||||
/// but can't serialize that, so ¯\_(ツ)_/¯
|
||||
[ProtoContract]
|
||||
public class DialogMessage : MessageBase
|
||||
{
|
||||
[ProtoMember(201)]
|
||||
public string Title;
|
||||
[ProtoMember(202)]
|
||||
public string Subtitle;
|
||||
[ProtoMember(203)]
|
||||
public string Prefix;
|
||||
[ProtoMember(204)]
|
||||
public string Content;
|
||||
[ProtoMember(205)]
|
||||
public string ButtonText;
|
||||
|
||||
public DialogMessage()
|
||||
{ }
|
||||
|
||||
public DialogMessage(string title, string subtitle, string content)
|
||||
{
|
||||
Title = title;
|
||||
Subtitle = subtitle;
|
||||
Content = content;
|
||||
Prefix = String.Empty;
|
||||
}
|
||||
|
||||
public DialogMessage(string title = null, string prefix = null, string subtitle = null, string content = null, string buttonText = null)
|
||||
{
|
||||
Title = title;
|
||||
Subtitle = subtitle;
|
||||
Prefix = prefix ?? String.Empty;
|
||||
Content = content;
|
||||
ButtonText = buttonText;
|
||||
}
|
||||
|
||||
public override void ProcessClient()
|
||||
{
|
||||
MyAPIGateway.Utilities.ShowMissionScreen(Title, Prefix, Subtitle, Content, null, ButtonText);
|
||||
}
|
||||
|
||||
public override void ProcessServer()
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
}
|
51
Torch.Mod/Messages/MessageBase.cs
Normal file
51
Torch.Mod/Messages/MessageBase.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ProtoBuf;
|
||||
|
||||
namespace Torch.Mod.Messages
|
||||
{
|
||||
#region Includes
|
||||
[ProtoInclude(1, typeof(DialogMessage))]
|
||||
[ProtoInclude(2, typeof(NotificationMessage))]
|
||||
[ProtoInclude(3, typeof(VoxelResetMessage))]
|
||||
#endregion
|
||||
|
||||
[ProtoContract]
|
||||
public abstract class MessageBase
|
||||
{
|
||||
[ProtoMember(101)]
|
||||
public ulong SenderId;
|
||||
|
||||
public abstract void ProcessClient();
|
||||
public abstract void ProcessServer();
|
||||
|
||||
//members below not serialized, they're just metadata about the intended target(s) of this message
|
||||
internal MessageTarget TargetType;
|
||||
internal ulong Target;
|
||||
internal ulong[] Ignore;
|
||||
internal byte[] CompressedData;
|
||||
}
|
||||
|
||||
public enum MessageTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// Send to Target
|
||||
/// </summary>
|
||||
Single,
|
||||
/// <summary>
|
||||
/// Send to Server
|
||||
/// </summary>
|
||||
Server,
|
||||
/// <summary>
|
||||
/// Send to all Clients (only valid from server)
|
||||
/// </summary>
|
||||
AllClients,
|
||||
/// <summary>
|
||||
/// Send to all except those steam ID listed in Ignore
|
||||
/// </summary>
|
||||
AllExcept,
|
||||
}
|
||||
}
|
39
Torch.Mod/Messages/NotificationMessage.cs
Normal file
39
Torch.Mod/Messages/NotificationMessage.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using ProtoBuf;
|
||||
using Sandbox.ModAPI;
|
||||
|
||||
namespace Torch.Mod.Messages
|
||||
{
|
||||
[ProtoContract]
|
||||
public class NotificationMessage : MessageBase
|
||||
{
|
||||
[ProtoMember(201)]
|
||||
public string Message;
|
||||
[ProtoMember(202)]
|
||||
public string Font;
|
||||
[ProtoMember(203)]
|
||||
public int DisappearTimeMs;
|
||||
|
||||
public NotificationMessage()
|
||||
{ }
|
||||
|
||||
public NotificationMessage(string message, int disappearTimeMs, string font)
|
||||
{
|
||||
Message = message;
|
||||
DisappearTimeMs = disappearTimeMs;
|
||||
Font = font;
|
||||
}
|
||||
|
||||
public override void ProcessClient()
|
||||
{
|
||||
MyAPIGateway.Utilities.ShowNotification(Message, DisappearTimeMs, Font);
|
||||
}
|
||||
|
||||
public override void ProcessServer()
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
}
|
46
Torch.Mod/Messages/VoxelResetMessage.cs
Normal file
46
Torch.Mod/Messages/VoxelResetMessage.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using ProtoBuf;
|
||||
using Sandbox.ModAPI;
|
||||
using VRage.ModAPI;
|
||||
using VRage.Voxels;
|
||||
|
||||
namespace Torch.Mod.Messages
|
||||
{
|
||||
[ProtoContract]
|
||||
public class VoxelResetMessage : MessageBase
|
||||
{
|
||||
[ProtoMember(201)]
|
||||
public long[] EntityId;
|
||||
|
||||
public VoxelResetMessage()
|
||||
{ }
|
||||
|
||||
public VoxelResetMessage(long[] entityId)
|
||||
{
|
||||
EntityId = entityId;
|
||||
}
|
||||
|
||||
public override void ProcessClient()
|
||||
{
|
||||
MyAPIGateway.Parallel.ForEach(EntityId, id =>
|
||||
{
|
||||
IMyEntity e;
|
||||
if (!MyAPIGateway.Entities.TryGetEntityById(id, out e))
|
||||
return;
|
||||
|
||||
var v = e as IMyVoxelBase;
|
||||
if (v == null)
|
||||
return;
|
||||
|
||||
v.Storage.Reset(MyStorageDataTypeFlags.All);
|
||||
});
|
||||
}
|
||||
|
||||
public override void ProcessServer()
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
}
|
213
Torch.Mod/ModCommunication.cs
Normal file
213
Torch.Mod/ModCommunication.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Sandbox.ModAPI;
|
||||
using Torch.Mod.Messages;
|
||||
using VRage;
|
||||
using VRage.Game.ModAPI;
|
||||
using VRage.Utils;
|
||||
using Task = ParallelTasks.Task;
|
||||
|
||||
namespace Torch.Mod
|
||||
{
|
||||
public static class ModCommunication
|
||||
{
|
||||
public const ushort NET_ID = 4352;
|
||||
private static bool _closing;
|
||||
private static ConcurrentQueue<MessageBase> _outgoing;
|
||||
private static ConcurrentQueue<byte[]> _incoming;
|
||||
private static List<IMyPlayer> _playerCache;
|
||||
private static FastResourceLock _lock;
|
||||
private static Task _task;
|
||||
|
||||
public static void Register()
|
||||
{
|
||||
MyLog.Default.WriteLineAndConsole("TORCH MOD: Registering mod communication.");
|
||||
_outgoing = new ConcurrentQueue<MessageBase>();
|
||||
_incoming = new ConcurrentQueue<byte[]>();
|
||||
_playerCache = new List<IMyPlayer>();
|
||||
_lock = new FastResourceLock();
|
||||
|
||||
|
||||
MyAPIGateway.Multiplayer.RegisterMessageHandler(NET_ID, MessageHandler);
|
||||
//background thread to handle de/compression and processing
|
||||
_task = MyAPIGateway.Parallel.StartBackground(DoProcessing);
|
||||
MyLog.Default.WriteLineAndConsole("TORCH MOD: Mod communication registered successfully.");
|
||||
}
|
||||
|
||||
public static void Unregister()
|
||||
{
|
||||
MyLog.Default.WriteLineAndConsole("TORCH MOD: Unregistering mod communication.");
|
||||
MyAPIGateway.Multiplayer?.UnregisterMessageHandler(NET_ID, MessageHandler);
|
||||
ReleaseLock();
|
||||
_closing = true;
|
||||
//_task.Wait();
|
||||
}
|
||||
|
||||
private static void MessageHandler(byte[] bytes)
|
||||
{
|
||||
_incoming.Enqueue(bytes);
|
||||
ReleaseLock();
|
||||
}
|
||||
|
||||
public static void DoProcessing()
|
||||
{
|
||||
while (!_closing)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] incoming;
|
||||
while (_incoming.TryDequeue(out incoming))
|
||||
{
|
||||
MessageBase m;
|
||||
try
|
||||
{
|
||||
var o = MyCompression.Decompress(incoming);
|
||||
m = MyAPIGateway.Utilities.SerializeFromBinary<MessageBase>(o);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MyLog.Default.WriteLineAndConsole($"TORCH MOD: Failed to deserialize message! {ex}");
|
||||
continue;
|
||||
}
|
||||
if (MyAPIGateway.Multiplayer.IsServer)
|
||||
m.ProcessServer();
|
||||
else
|
||||
m.ProcessClient();
|
||||
}
|
||||
|
||||
if (!_outgoing.IsEmpty)
|
||||
{
|
||||
List<MessageBase> tosend = new List<MessageBase>(_outgoing.Count);
|
||||
MessageBase outMessage;
|
||||
while (_outgoing.TryDequeue(out outMessage))
|
||||
{
|
||||
var b = MyAPIGateway.Utilities.SerializeToBinary(outMessage);
|
||||
outMessage.CompressedData = MyCompression.Compress(b);
|
||||
tosend.Add(outMessage);
|
||||
}
|
||||
|
||||
MyAPIGateway.Utilities.InvokeOnGameThread(() =>
|
||||
{
|
||||
MyAPIGateway.Players.GetPlayers(_playerCache);
|
||||
foreach (var outgoing in tosend)
|
||||
{
|
||||
switch (outgoing.TargetType)
|
||||
{
|
||||
case MessageTarget.Single:
|
||||
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, outgoing.CompressedData, outgoing.Target);
|
||||
break;
|
||||
case MessageTarget.Server:
|
||||
MyAPIGateway.Multiplayer.SendMessageToServer(NET_ID, outgoing.CompressedData);
|
||||
break;
|
||||
case MessageTarget.AllClients:
|
||||
foreach (var p in _playerCache)
|
||||
{
|
||||
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId)
|
||||
continue;
|
||||
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, outgoing.CompressedData, p.SteamUserId);
|
||||
}
|
||||
break;
|
||||
case MessageTarget.AllExcept:
|
||||
foreach (var p in _playerCache)
|
||||
{
|
||||
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId || outgoing.Ignore.Contains(p.SteamUserId))
|
||||
continue;
|
||||
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, outgoing.CompressedData, p.SteamUserId);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
_playerCache.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
AcquireLock();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MyLog.Default.WriteLineAndConsole($"TORCH MOD: Exception occurred in communication thread! {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
MyLog.Default.WriteLineAndConsole("TORCH MOD: COMMUNICATION THREAD: EXIT SIGNAL RECIEVED!");
|
||||
//exit signal received. Clean everything and GTFO
|
||||
_outgoing = null;
|
||||
_incoming = null;
|
||||
_playerCache = null;
|
||||
_lock = null;
|
||||
}
|
||||
|
||||
public static void SendMessageTo(MessageBase message, ulong target)
|
||||
{
|
||||
if (!MyAPIGateway.Multiplayer.IsServer)
|
||||
throw new Exception("Only server can send targeted messages");
|
||||
|
||||
if (_closing)
|
||||
return;
|
||||
|
||||
message.Target = target;
|
||||
message.TargetType = MessageTarget.Single;
|
||||
MyLog.Default.WriteLineAndConsole($"Sending message of type {message.GetType().FullName}");
|
||||
_outgoing.Enqueue(message);
|
||||
ReleaseLock();
|
||||
}
|
||||
|
||||
public static void SendMessageToClients(MessageBase message)
|
||||
{
|
||||
if (!MyAPIGateway.Multiplayer.IsServer)
|
||||
throw new Exception("Only server can send targeted messages");
|
||||
|
||||
if (_closing)
|
||||
return;
|
||||
|
||||
message.TargetType = MessageTarget.AllClients;
|
||||
_outgoing.Enqueue(message);
|
||||
ReleaseLock();
|
||||
}
|
||||
|
||||
public static void SendMessageExcept(MessageBase message, params ulong[] ignoredUsers)
|
||||
{
|
||||
if (!MyAPIGateway.Multiplayer.IsServer)
|
||||
throw new Exception("Only server can send targeted messages");
|
||||
|
||||
if (_closing)
|
||||
return;
|
||||
|
||||
message.TargetType = MessageTarget.AllExcept;
|
||||
message.Ignore = ignoredUsers;
|
||||
_outgoing.Enqueue(message);
|
||||
ReleaseLock();
|
||||
}
|
||||
|
||||
public static void SendMessageToServer(MessageBase message)
|
||||
{
|
||||
if (_closing)
|
||||
return;
|
||||
|
||||
message.TargetType = MessageTarget.Server;
|
||||
_outgoing.Enqueue(message);
|
||||
ReleaseLock();
|
||||
}
|
||||
|
||||
private static void ReleaseLock()
|
||||
{
|
||||
while(_lock?.TryAcquireExclusive() == false)
|
||||
_lock?.ReleaseExclusive();
|
||||
_lock?.ReleaseExclusive();
|
||||
}
|
||||
|
||||
private static void AcquireLock()
|
||||
{
|
||||
ReleaseLock();
|
||||
_lock?.AcquireExclusive();
|
||||
_lock?.AcquireExclusive();
|
||||
}
|
||||
}
|
||||
}
|
19
Torch.Mod/Torch.Mod.projitems
Normal file
19
Torch.Mod/Torch.Mod.projitems
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<HasSharedItems>true</HasSharedItems>
|
||||
<SharedGUID>3ce4d2e9-b461-4f19-8233-f87e0dfddd74</SharedGUID>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Import_RootNamespace>Torch.Mod</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Messages\NotificationMessage.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Messages\DialogMessage.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Messages\MessageBase.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Messages\VoxelResetMessage.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ModCommunication.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)TorchModCore.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
13
Torch.Mod/Torch.Mod.shproj
Normal file
13
Torch.Mod/Torch.Mod.shproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>3ce4d2e9-b461-4f19-8233-f87e0dfddd74</ProjectGuid>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||
<PropertyGroup />
|
||||
<Import Project="Torch.Mod.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||
</Project>
|
37
Torch.Mod/TorchModCore.cs
Normal file
37
Torch.Mod/TorchModCore.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VRage.Game.Components;
|
||||
|
||||
namespace Torch.Mod
|
||||
{
|
||||
[MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)]
|
||||
public class TorchModCore : MySessionComponentBase
|
||||
{
|
||||
public const long MOD_ID = 1406994352;
|
||||
private static bool _init;
|
||||
|
||||
public override void UpdateAfterSimulation()
|
||||
{
|
||||
if (_init)
|
||||
return;
|
||||
|
||||
_init = true;
|
||||
ModCommunication.Register();
|
||||
}
|
||||
|
||||
protected override void UnloadData()
|
||||
{
|
||||
try
|
||||
{
|
||||
ModCommunication.Unregister();
|
||||
}
|
||||
catch
|
||||
{
|
||||
//session unloading, don't care
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -18,6 +18,7 @@ using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.Session;
|
||||
using Torch.Commands;
|
||||
using Torch.Mod;
|
||||
using Torch.Server.Commands;
|
||||
using Torch.Server.Managers;
|
||||
using Torch.Utils;
|
||||
@@ -45,7 +46,7 @@ namespace Torch.Server
|
||||
private Timer _watchdog;
|
||||
|
||||
/// <inheritdoc />
|
||||
public TorchServer(TorchConfig config = null)
|
||||
public TorchServer(TorchConfig config = null)
|
||||
{
|
||||
DedicatedInstance = new InstanceManager(this);
|
||||
AddManager(DedicatedInstance);
|
||||
@@ -174,10 +175,14 @@ namespace Torch.Server
|
||||
{
|
||||
_watchdog?.Dispose();
|
||||
_watchdog = null;
|
||||
ModCommunication.Unregister();
|
||||
}
|
||||
|
||||
if (newState == TorchSessionState.Loaded)
|
||||
{
|
||||
CurrentSession.Managers.GetManager<CommandManager>().RegisterCommandModule(typeof(WhitelistCommands));
|
||||
ModCommunication.Register();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@@ -26,8 +26,8 @@
|
||||
</Grid.RowDefinitions>
|
||||
<DockPanel Grid.Row="0">
|
||||
<Label Content="World:" DockPanel.Dock="Left" />
|
||||
<Button Content="Import World Config" Margin="3" DockPanel.Dock="Right" Click="ImportConfig_OnClick" ToolTip="Override the DS config with the one from the selected world."/>
|
||||
<ComboBox ItemsSource="{Binding Worlds}" SelectedItem="{Binding SelectedWorld}" Margin="3"
|
||||
<Button Content="Import World Config" Margin="3" DockPanel.Dock="Right" Click="ImportConfig_OnClick" ToolTip="Override the DS config with the one from the selected world." IsEnabled="{Binding ElementName=WorldList, Path=Items.Count, Mode=OneWay}"/>
|
||||
<ComboBox x:Name="WorldList" ItemsSource="{Binding Worlds}" SelectedItem="{Binding SelectedWorld}" Margin="3"
|
||||
SelectionChanged="Selector_OnSelectionChanged">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="managers:WorldViewModel">
|
||||
|
@@ -28,7 +28,7 @@
|
||||
</ListView>
|
||||
<Button Grid.Row="1" Content="Open Folder" Margin="3" DockPanel.Dock="Bottom" Click="OpenFolder_OnClick"/>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Column="1" VerticalScrollBarVisibility="Auto" Margin="3">
|
||||
<ScrollViewer Name="PScroll" Grid.Column="1" Margin="3">
|
||||
<Frame NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -19,6 +20,7 @@ using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Managers;
|
||||
using Torch.Server.ViewModels;
|
||||
using Torch.Views;
|
||||
|
||||
namespace Torch.Server.Views
|
||||
{
|
||||
@@ -35,6 +37,17 @@ namespace Torch.Server.Views
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void PluginManagerOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
|
||||
{
|
||||
if (propertyChangedEventArgs.PropertyName == nameof(PluginManagerViewModel.SelectedPlugin))
|
||||
{
|
||||
if (((PluginManagerViewModel)DataContext).SelectedPlugin.Control is PropertyGrid)
|
||||
PScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
|
||||
else
|
||||
PScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
|
||||
}
|
||||
}
|
||||
|
||||
public void BindServer(ITorchServer server)
|
||||
{
|
||||
_server = server;
|
||||
@@ -48,6 +61,7 @@ namespace Torch.Server.Views
|
||||
_plugins = _server.Managers.GetManager<PluginManager>();
|
||||
var pluginManager = new PluginManagerViewModel(_plugins);
|
||||
DataContext = pluginManager;
|
||||
pluginManager.PropertyChanged += PluginManagerOnPropertyChanged;
|
||||
});
|
||||
|
||||
}
|
||||
|
28
Torch.sln
28
Torch.sln
@@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27004.2010
|
||||
VisualStudioVersion = 15.0.26430.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch", "Torch\Torch.csproj", "{7E01635C-3B67-472E-BCD6-C5539564F214}"
|
||||
EndProject
|
||||
@@ -27,41 +27,60 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Versioning", "Versioning",
|
||||
Versioning\AssemblyVersion.cs = Versioning\AssemblyVersion.cs
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Torch.Mod", "Torch.Mod\Torch.Mod.shproj", "{3CE4D2E9-B461-4F19-8233-F87E0DFDDD74}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(Performance) = preSolution
|
||||
HasPerformanceSessions = true
|
||||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||
Torch.Mod\Torch.Mod.projitems*{3ce4d2e9-b461-4f19-8233-f87e0dfddd74}*SharedItemsImports = 13
|
||||
Torch.Mod\Torch.Mod.projitems*{7e01635c-3b67-472e-bcd6-c5539564f214}*SharedItemsImports = 4
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{7E01635C-3B67-472E-BCD6-C5539564F214}.Debug|x64.Build.0 = Debug|x64
|
||||
{7E01635C-3B67-472E-BCD6-C5539564F214}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{7E01635C-3B67-472E-BCD6-C5539564F214}.Release|x64.ActiveCfg = Release|x64
|
||||
{7E01635C-3B67-472E-BCD6-C5539564F214}.Release|x64.Build.0 = Release|x64
|
||||
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Debug|x64.Build.0 = Debug|x64
|
||||
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|x64.ActiveCfg = Release|x64
|
||||
{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}.Release|x64.Build.0 = Release|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Debug|x64.Build.0 = Debug|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.ActiveCfg = Release|x64
|
||||
{E36DF745-260B-4956-A2E8-09F08B2E7161}.Release|x64.Build.0 = Release|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Debug|x64.Build.0 = Debug|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.ActiveCfg = Release|x64
|
||||
{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}.Release|x64.Build.0 = Release|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Debug|x64.Build.0 = Debug|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|x64.ActiveCfg = Release|x64
|
||||
{C3C8B671-6AD1-44AA-A8DA-E0C0DC0FEDF5}.Release|x64.Build.0 = Release|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Debug|x64.Build.0 = Debug|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.ActiveCfg = Release|x64
|
||||
{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}.Release|x64.Build.0 = Release|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Debug|x64.Build.0 = Debug|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.ActiveCfg = Release|x64
|
||||
{632E78C0-0DAC-4B71-B411-2F1B333CC310}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
@@ -74,4 +93,7 @@ Global
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BB51D91F-958D-4B63-A897-3C40642ACD3E}
|
||||
EndGlobalSection
|
||||
GlobalSection(Performance) = preSolution
|
||||
HasPerformanceSessions = true
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@@ -17,6 +17,9 @@ using Torch.API.Managers;
|
||||
using Torch.API.Session;
|
||||
using Torch.Commands.Permissions;
|
||||
using Torch.Managers;
|
||||
using Torch.Mod;
|
||||
using Torch.Mod.Messages;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
|
||||
namespace Torch.Commands
|
||||
@@ -75,7 +78,7 @@ namespace Torch.Commands
|
||||
}
|
||||
}
|
||||
|
||||
[Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")]
|
||||
[Command("longhelp", "Get verbose help. Will send a long message in a dialog window.")]
|
||||
[Permission(MyPromoteLevel.None)]
|
||||
public void LongHelp()
|
||||
{
|
||||
@@ -107,13 +110,20 @@ namespace Torch.Commands
|
||||
}
|
||||
else
|
||||
{
|
||||
var sb = new StringBuilder("Available commands:\n");
|
||||
var sb = new StringBuilder();
|
||||
foreach (var command in commandManager.Commands.WalkTree())
|
||||
{
|
||||
if (command.IsCommand)
|
||||
sb.AppendLine($"{command.Command.SyntaxHelp}\n {command.Command.HelpText}");
|
||||
}
|
||||
Context.Respond(sb.ToString());
|
||||
|
||||
if (!Context.SentBySelf)
|
||||
{
|
||||
var m = new DialogMessage("Torch Help", subtitle: "Available commands:", content: sb.ToString());
|
||||
ModCommunication.SendMessageTo(m, Context.Player.SteamUserId);
|
||||
}
|
||||
else
|
||||
Context.Respond($"Available commands: {sb}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +179,13 @@ namespace Torch.Commands
|
||||
});
|
||||
}
|
||||
|
||||
[Command("notify", "Shows a message as a notification in the middle of all players' screens.")]
|
||||
[Permission(MyPromoteLevel.Admin)]
|
||||
public void Notify(string message, int disappearTimeMs = 2000, string font = "White")
|
||||
{
|
||||
ModCommunication.SendMessageToClients(new NotificationMessage(message, disappearTimeMs, font));
|
||||
}
|
||||
|
||||
[Command("restart cancel", "Cancel a pending restart.")]
|
||||
public void CancelRestart()
|
||||
{
|
||||
|
29
Torch/Patches/SessionDownloadPatch.cs
Normal file
29
Torch/Patches/SessionDownloadPatch.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Sandbox.Game.World;
|
||||
using Torch.Managers.PatchManager;
|
||||
using Torch.Mod;
|
||||
using VRage.Game;
|
||||
|
||||
namespace Torch.Patches
|
||||
{
|
||||
[PatchShim]
|
||||
internal static class SessionDownloadPatch
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static void SuffixGetWorld(ref MyObjectBuilder_World __result)
|
||||
{
|
||||
if (!__result.Checkpoint.Mods.Any(m => m.PublishedFileId == TorchModCore.MOD_ID))
|
||||
__result.Checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(TorchModCore.MOD_ID));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
@@ -18,7 +18,19 @@ namespace Torch
|
||||
public sealed class Persistent<T> : IDisposable where T : new()
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public T Data { get; private set; }
|
||||
private T _data;
|
||||
public T Data
|
||||
{
|
||||
get => _data;
|
||||
private set
|
||||
{
|
||||
if (_data is INotifyPropertyChanged npc1)
|
||||
npc1.PropertyChanged -= OnPropertyChanged;
|
||||
_data = value;
|
||||
if (_data is INotifyPropertyChanged npc2)
|
||||
npc2.PropertyChanged += OnPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
~Persistent()
|
||||
{
|
||||
@@ -29,13 +41,23 @@ namespace Torch
|
||||
{
|
||||
Path = path;
|
||||
Data = data;
|
||||
if (Data is INotifyPropertyChanged npc)
|
||||
npc.PropertyChanged += OnPropertyChanged;
|
||||
}
|
||||
|
||||
private Timer _saveConfigTimer;
|
||||
|
||||
private void SaveAsync()
|
||||
{
|
||||
if (_saveConfigTimer == null)
|
||||
{
|
||||
_saveConfigTimer = new Timer((x) => Save());
|
||||
}
|
||||
|
||||
_saveConfigTimer.Change(1000, -1);
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
Save();
|
||||
SaveAsync();
|
||||
}
|
||||
|
||||
public void Save(string path = null)
|
||||
@@ -52,20 +74,20 @@ namespace Torch
|
||||
|
||||
public static Persistent<T> Load(string path, bool saveIfNew = true)
|
||||
{
|
||||
var config = new Persistent<T>(path, new T());
|
||||
Persistent<T> config = null;
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var ser = new XmlSerializer(typeof(T));
|
||||
using (var f = File.OpenText(path))
|
||||
{
|
||||
config.Data = (T)ser.Deserialize(f);
|
||||
config = new Persistent<T>(path, (T)ser.Deserialize(f));
|
||||
}
|
||||
}
|
||||
else if (saveIfNew)
|
||||
{
|
||||
config.Save(path);
|
||||
}
|
||||
if (config == null)
|
||||
config = new Persistent<T>(path, new T());
|
||||
if (!File.Exists(path) && saveIfNew)
|
||||
config.Save();
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -76,6 +98,7 @@ namespace Torch
|
||||
{
|
||||
if (Data is INotifyPropertyChanged npc)
|
||||
npc.PropertyChanged -= OnPropertyChanged;
|
||||
_saveConfigTimer?.Dispose();
|
||||
Save();
|
||||
}
|
||||
catch
|
||||
|
@@ -377,6 +377,7 @@ namespace Torch.Managers
|
||||
private void InstantiatePlugin(PluginManifest manifest, IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
Type pluginType = null;
|
||||
bool mult = false;
|
||||
foreach (var asm in assemblies)
|
||||
{
|
||||
foreach (var type in asm.GetExportedTypes())
|
||||
@@ -384,16 +385,26 @@ namespace Torch.Managers
|
||||
if (!type.GetInterfaces().Contains(typeof(ITorchPlugin)))
|
||||
continue;
|
||||
|
||||
_log.Info($"Loading plugin at {type.FullName}");
|
||||
|
||||
if (pluginType != null)
|
||||
{
|
||||
_log.Error($"The plugin '{manifest.Name}' has multiple implementations of {nameof(ITorchPlugin)}, not loading.");
|
||||
return;
|
||||
//_log.Error($"The plugin '{manifest.Name}' has multiple implementations of {nameof(ITorchPlugin)}, not loading.");
|
||||
//return;
|
||||
mult = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
pluginType = type;
|
||||
}
|
||||
}
|
||||
|
||||
if (mult)
|
||||
{
|
||||
_log.Error($"The plugin '{manifest.Name}' has multiple implementations of {nameof(ITorchPlugin)}, not loading.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pluginType == null)
|
||||
{
|
||||
_log.Error($"The plugin '{manifest.Name}' does not have an implementation of {nameof(ITorchPlugin)}, not loading.");
|
||||
|
@@ -208,6 +208,7 @@
|
||||
<Compile Include="Patches\GameAnalyticsPatch.cs" />
|
||||
<Compile Include="Patches\GameStatePatchShim.cs" />
|
||||
<Compile Include="Patches\ObjectFactoryInitPatch.cs" />
|
||||
<Compile Include="Patches\SessionDownloadPatch.cs" />
|
||||
<Compile Include="Patches\TorchAsyncSaving.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Collections\KeyTree.cs" />
|
||||
@@ -269,6 +270,9 @@
|
||||
<DependentUpon>DictionaryEditor.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\DisplayAttribute.cs" />
|
||||
<Compile Include="Views\EmbeddedCollectionEditor.xaml.cs">
|
||||
<DependentUpon>EmbeddedCollectionEditor.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\ObjectCollectionEditor.xaml.cs">
|
||||
<DependentUpon>ObjectCollectionEditor.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -299,6 +303,10 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Views\EmbeddedCollectionEditor.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\ObjectCollectionEditor.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -315,6 +323,7 @@
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\Torch.Mod\Torch.Mod.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
</Project>
|
@@ -16,6 +16,7 @@ namespace Torch.Views
|
||||
public bool Enabled = true;
|
||||
public bool Visible = true;
|
||||
public bool ReadOnly = false;
|
||||
public Type EditorType = null;
|
||||
|
||||
public DisplayAttribute()
|
||||
{ }
|
||||
|
32
Torch/Views/EmbeddedCollectionEditor.xaml
Normal file
32
Torch/Views/EmbeddedCollectionEditor.xaml
Normal file
@@ -0,0 +1,32 @@
|
||||
<UserControl x:Class="Torch.Views.EmbeddedCollectionEditor"
|
||||
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:local="clr-namespace:Torch.Views"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="300">
|
||||
<Grid Width="Auto" Height="Auto">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="25" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox Grid.Row="0" Grid.Column="0" x:Name="ElementList"
|
||||
HorizontalContentAlignment="Stretch" Margin="0" VerticalContentAlignment="Stretch" />
|
||||
<GridSplitter Grid.Column="1" Grid.Row="0" Width="2" HorizontalAlignment="Left" VerticalAlignment="Stretch"
|
||||
Background="Gray" ShowsPreview="True" VerticalContentAlignment="Stretch" />
|
||||
|
||||
<local:PropertyGrid Grid.Row="0" Grid.Column="1" x:Name="PGrid" Margin="4,0,0,0" />
|
||||
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" x:Name="AddButton" Content="Add"
|
||||
HorizontalAlignment="Left" Margin="0" VerticalAlignment="Top"
|
||||
Width="90" />
|
||||
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" x:Name="RemoveButton" Content="Remove"
|
||||
HorizontalAlignment="Left" Margin="100,0,0,0"
|
||||
VerticalAlignment="Top" Width="90" />
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
126
Torch/Views/EmbeddedCollectionEditor.xaml.cs
Normal file
126
Torch/Views/EmbeddedCollectionEditor.xaml.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using NLog;
|
||||
using NLog.Fluent;
|
||||
|
||||
namespace Torch.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for EmbeddedCollectionEditor.xaml
|
||||
/// </summary>
|
||||
public partial class EmbeddedCollectionEditor : UserControl
|
||||
{
|
||||
public EmbeddedCollectionEditor()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
}
|
||||
|
||||
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
|
||||
{
|
||||
var c = dependencyPropertyChangedEventArgs.NewValue as ICollection;
|
||||
//var c = DataContext as ICollection;
|
||||
if (c != null)
|
||||
Edit(c);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Type, MethodInfo> MethodCache = new Dictionary<Type, MethodInfo>();
|
||||
private static readonly MethodInfo EditMethod;
|
||||
|
||||
|
||||
static EmbeddedCollectionEditor()
|
||||
{
|
||||
var m = typeof(EmbeddedCollectionEditor).GetMethods();
|
||||
EditMethod = m.First(mt => mt.Name == "Edit" && mt.GetGenericArguments().Length == 1);
|
||||
}
|
||||
|
||||
public void Edit(ICollection collection)
|
||||
{
|
||||
if (collection == null)
|
||||
{
|
||||
MessageBox.Show("Cannot load null collection.", "Edit Error");
|
||||
return;
|
||||
}
|
||||
|
||||
var gt = collection.GetType().GenericTypeArguments[0];
|
||||
|
||||
//substitute for 'where T : new()'
|
||||
if (gt.GetConstructor(Type.EmptyTypes) == null)
|
||||
{
|
||||
MessageBox.Show("Unsupported collection type. Type must have paramaterless ctor.", "Edit Error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MethodCache.TryGetValue(gt, out MethodInfo gm))
|
||||
{
|
||||
gm = EditMethod.MakeGenericMethod(gt);
|
||||
MethodCache.Add(gt, gm);
|
||||
}
|
||||
|
||||
gm.Invoke(this, new object[] {collection});
|
||||
}
|
||||
|
||||
public void Edit<T>(ICollection<T> collection) where T : new()
|
||||
{
|
||||
var oc = collection as ObservableCollection<T> ?? new ObservableCollection<T>(collection);
|
||||
|
||||
AddButton.Click += (sender, args) =>
|
||||
{
|
||||
var t = new T();
|
||||
oc.Add(t);
|
||||
ElementList.SelectedItem = t;
|
||||
};
|
||||
|
||||
RemoveButton.Click += RemoveButton_OnClick<T>;
|
||||
ElementList.SelectionChanged += ElementsList_OnSelected;
|
||||
|
||||
ElementList.ItemsSource = oc;
|
||||
oc.CollectionChanged += (sender, args) => RefreshList();
|
||||
|
||||
if (!(collection is ObservableCollection<T>))
|
||||
{
|
||||
collection.Clear();
|
||||
foreach (var o in oc)
|
||||
collection.Add(o);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveButton_OnClick<T>(object sender, RoutedEventArgs e)
|
||||
{
|
||||
//this is kinda shitty, but item count is normally small, and it prevents CollectionModifiedExceptions
|
||||
var l = (ObservableCollection<T>)ElementList.ItemsSource;
|
||||
var r = new List<T>(ElementList.SelectedItems.Cast<T>());
|
||||
foreach (var item in r)
|
||||
l.Remove(item);
|
||||
if (l.Any())
|
||||
ElementList.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private void ElementsList_OnSelected(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var item = (sender as ListBox)?.SelectedItem;
|
||||
PGrid.DataContext = item;
|
||||
}
|
||||
|
||||
private void RefreshList()
|
||||
{
|
||||
ElementList.Items.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,24 +6,5 @@
|
||||
xmlns:local="clr-namespace:Torch.Views"
|
||||
mc:Ignorable="d"
|
||||
Height="370" Width="400" Title="Edit Collection">
|
||||
<Grid Width="Auto" Height="Auto">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="25"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox Grid.Row="0" Grid.Column="0" x:Name="ElementList"
|
||||
HorizontalContentAlignment="Stretch" Margin="0" VerticalContentAlignment="Stretch" />
|
||||
<GridSplitter Grid.Column="1" Grid.Row="0" Width="2" HorizontalAlignment="Left" VerticalAlignment="Stretch" Background="Gray" ShowsPreview="True" VerticalContentAlignment="Stretch"/>
|
||||
|
||||
<local:PropertyGrid Grid.Row="0" Grid.Column="1" x:Name="PGrid" Margin="4,0,0,0" />
|
||||
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" x:Name="AddButton" Content="Add" HorizontalAlignment="Left" Margin="0" VerticalAlignment="Top"
|
||||
Width="90" />
|
||||
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" x:Name="RemoveButton" Content="Remove" HorizontalAlignment="Left" Margin="100,0,0,0"
|
||||
VerticalAlignment="Top" Width="90" />
|
||||
|
||||
</Grid>
|
||||
<local:EmbeddedCollectionEditor x:Name="Editor"/>
|
||||
</Window>
|
||||
|
@@ -25,90 +25,18 @@ namespace Torch.Views
|
||||
/// </summary>
|
||||
public partial class ObjectCollectionEditor : Window
|
||||
{
|
||||
private static readonly Dictionary<Type, MethodInfo> MethodCache = new Dictionary<Type, MethodInfo>();
|
||||
private static readonly MethodInfo EditMethod;
|
||||
|
||||
public ObjectCollectionEditor()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
static ObjectCollectionEditor()
|
||||
{
|
||||
var m = typeof(ObjectCollectionEditor).GetMethods();
|
||||
EditMethod = m.First(mt => mt.Name == "Edit" && mt.GetGenericArguments().Length == 1);
|
||||
}
|
||||
|
||||
public void Edit(ICollection collection, string title)
|
||||
{
|
||||
if (collection == null)
|
||||
{
|
||||
MessageBox.Show("Cannot load null collection.", "Edit Error");
|
||||
return;
|
||||
}
|
||||
|
||||
var gt = collection.GetType().GenericTypeArguments[0];
|
||||
|
||||
//substitute for 'where T : new()'
|
||||
if (gt.GetConstructor(Type.EmptyTypes) == null)
|
||||
{
|
||||
MessageBox.Show("Unsupported collection type. Type must have paramaterless ctor.", "Edit Error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MethodCache.TryGetValue(gt, out MethodInfo gm))
|
||||
{
|
||||
gm = EditMethod.MakeGenericMethod(gt);
|
||||
MethodCache.Add(gt, gm);
|
||||
}
|
||||
|
||||
gm.Invoke(this, new object[] {collection, title});
|
||||
}
|
||||
|
||||
public void Edit<T>(ICollection<T> collection, string title) where T : new()
|
||||
{
|
||||
var oc = collection as ObservableCollection<T> ?? new ObservableCollection<T>(collection);
|
||||
|
||||
AddButton.Click += (sender, args) =>
|
||||
{
|
||||
var t = new T();
|
||||
oc.Add(t);
|
||||
ElementList.SelectedItem = t;
|
||||
};
|
||||
|
||||
RemoveButton.Click += RemoveButton_OnClick<T>;
|
||||
ElementList.SelectionChanged += ElementsList_OnSelected;
|
||||
|
||||
ElementList.ItemsSource = oc;
|
||||
|
||||
Editor.Edit(collection);
|
||||
Title = title;
|
||||
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
ShowDialog();
|
||||
|
||||
if (!(collection is ObservableCollection<T>))
|
||||
{
|
||||
collection.Clear();
|
||||
foreach (var o in oc)
|
||||
collection.Add(o);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveButton_OnClick<T>(object sender, RoutedEventArgs e)
|
||||
{
|
||||
//this is kinda shitty, but item count is normally small, and it prevents CollectionModifiedExceptions
|
||||
var l = (ObservableCollection<T>)ElementList.ItemsSource;
|
||||
var r = new List<T>(ElementList.SelectedItems.Cast<T>());
|
||||
foreach (var item in r)
|
||||
l.Remove(item);
|
||||
if (l.Any())
|
||||
ElementList.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private void ElementsList_OnSelected(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var item = (sender as ListBox)?.SelectedItem;
|
||||
PGrid.DataContext = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,6 @@
|
||||
<TextBox Name="TbFilter" Grid.Column="1" Margin="3" TextChanged="UpdateFilter" IsEnabled="False"/>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="1" x:Name="ScrollViewer"/>
|
||||
<TextBlock x:Name="TbDescription" Grid.Row="2" MinHeight="18"/>
|
||||
<TextBlock x:Name="TbDescription" Grid.Row="2" MinHeight="18" TextWrapping="Wrap"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
@@ -67,8 +67,8 @@ namespace Torch.Views
|
||||
var properties = t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
var grid = new Grid();
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(2, GridUnitType.Star) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
var categories = new Dictionary<string, List<PropertyInfo>>();
|
||||
var descriptors = new Dictionary<PropertyInfo, DisplayAttribute>(properties.Length);
|
||||
@@ -145,7 +145,12 @@ namespace Torch.Views
|
||||
grid.Children.Add(text);
|
||||
|
||||
FrameworkElement valueControl;
|
||||
if (property.GetSetMethod() == null || descriptor?.ReadOnly == true)
|
||||
if (descriptor?.EditorType != null)
|
||||
{
|
||||
valueControl = (FrameworkElement)Activator.CreateInstance(descriptor.EditorType);
|
||||
valueControl.SetBinding(FrameworkElement.DataContextProperty, property.Name);
|
||||
}
|
||||
else if (property.GetSetMethod() == null && !(propertyType.IsGenericType && typeof(ICollection).IsAssignableFrom(propertyType.GetGenericTypeDefinition()))|| descriptor?.ReadOnly == true)
|
||||
{
|
||||
valueControl = new TextBlock();
|
||||
var binding = new Binding(property.Name)
|
||||
@@ -211,11 +216,21 @@ namespace Torch.Views
|
||||
|
||||
valueControl = button;
|
||||
}
|
||||
else if (propertyType.IsPrimitive || propertyType == typeof(string))
|
||||
else if (propertyType.IsPrimitive)
|
||||
{
|
||||
valueControl = new TextBox();
|
||||
valueControl.SetBinding(TextBox.TextProperty, property.Name);
|
||||
}
|
||||
else if (propertyType == typeof(string))
|
||||
{
|
||||
var tb = new TextBox();
|
||||
tb.TextWrapping = TextWrapping.Wrap;
|
||||
tb.AcceptsReturn = true;
|
||||
tb.AcceptsTab = true;
|
||||
tb.SpellCheck.IsEnabled = true;
|
||||
tb.SetBinding(TextBox.TextProperty, property.Name);
|
||||
valueControl = tb;
|
||||
}
|
||||
else
|
||||
{
|
||||
var button = new Button
|
||||
|
Reference in New Issue
Block a user