First test build

This commit is contained in:
John Gross
2017-03-30 15:24:39 -07:00
parent 650ab05d74
commit 5c1a9a5e35
47 changed files with 1610 additions and 498 deletions

View File

@@ -6,10 +6,11 @@ using System.Threading.Tasks;
namespace Torch.API
{
public interface IChatItem
public interface IChatMessage
{
IPlayer Player { get; }
DateTime Timestamp { get; }
ulong SteamId { get; }
string Name { get; }
string Message { get; }
DateTime Time { get; }
}
}

View File

@@ -6,17 +6,18 @@ using VRage.Game.ModAPI;
namespace Torch.API
{
public delegate void MessageReceivedDel(IChatMessage message, ref bool sendToOthers);
public interface IMultiplayer
{
event Action<IPlayer> PlayerJoined;
event Action<IPlayer> PlayerLeft;
event Action<IChatItem> MessageReceived;
Dictionary<ulong, IPlayer> Players { get; }
List<IChatItem> Chat { get; }
event Action<ulong> PlayerJoined;
event Action<ulong, ConnectionState> PlayerLeft;
event MessageReceivedDel MessageReceived;
void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Blue);
void KickPlayer(ulong id);
void BanPlayer(ulong id, bool banned = true);
void KickPlayer(ulong steamId);
void BanPlayer(ulong steamId, bool banned = true);
IMyPlayer GetPlayerBySteamId(ulong id);
IMyPlayer GetPlayerByName(string name);
MTObservableCollection<IChatMessage> ChatHistory { get; }
}
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Torch.API
{
public interface IPlayer
{
ulong SteamId { get; }
List<ulong> IdentityIds { get; }
string Name { get; }
ConnectionState State { get; }
DateTime LastConnected { get; }
void SetConnectionState(ConnectionState state);
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.Game.ModAPI;
namespace Torch.API
{
@@ -19,6 +20,7 @@ namespace Torch.API
void InvokeBlocking(Action action);
Task InvokeAsync(Action action);
string[] RunArgs { get; set; }
bool IsOnGameThread();
void Start();
void Stop();
void Init();
@@ -28,6 +30,7 @@ namespace Torch.API
{
bool IsRunning { get; }
string InstancePath { get; }
void Start(IMyConfigDedicated config);
}
public interface ITorchClient : ITorchBase

View File

@@ -69,6 +69,9 @@
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="Torch">
<HintPath>..\Torch.Server\bin\x64\Release\Torch.dll</HintPath>
</Reference>
<Reference Include="VRage">
<HintPath>C:\Program Files (x86)\Steam\steamapps\common\SpaceEngineers\Bin64\VRage.dll</HintPath>
<Private>False</Private>
@@ -111,9 +114,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ConnectionState.cs" />
<Compile Include="IChatItem.cs" />
<Compile Include="IChatMessage.cs" />
<Compile Include="IMultiplayer.cs" />
<Compile Include="IPlayer.cs" />
<Compile Include="IPluginManager.cs" />
<Compile Include="ITorchPlugin.cs" />
<Compile Include="IServerControls.cs" />

View File

@@ -12,5 +12,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: AssemblyVersion("1.0.35.456")]
[assembly: AssemblyFileVersion("1.0.35.456")]
[assembly: AssemblyVersion("1.0.89.455")]
[assembly: AssemblyFileVersion("1.0.89.455")]

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Torch.Server
{
public class NativeMethods
{
[DllImport("kernel32")]
public static extern bool AllocConsole();
[DllImport("kernel32")]
public static extern bool FreeConsole();
}
}

View File

@@ -28,6 +28,7 @@ namespace Torch.Server
private static ITorchServer _server;
private static Logger _log = LogManager.GetLogger("Torch");
[STAThread]
public static void Main(string[] args)
{
if (!Environment.UserInteractive)
@@ -41,16 +42,16 @@ namespace Torch.Server
var configName = args.FirstOrDefault() ?? "TorchConfig.xml";
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
ServerConfig options;
TorchConfig options;
if (File.Exists(configName))
{
_log.Info($"Loading config {configPath}");
options = ServerConfig.LoadFrom(configPath);
options = TorchConfig.LoadFrom(configPath);
}
else
{
_log.Info($"Generating default config at {configPath}");
options = new ServerConfig();
options = new TorchConfig();
options.SaveTo(configPath);
}
@@ -117,7 +118,9 @@ namespace Torch.Server
_server = new TorchServer(options);
_server.Init();
_server.Start();
var ui = new TorchUI((TorchServer)_server);
ui.LoadConfig(options);
ui.ShowDialog();
}
}
}

View File

@@ -12,5 +12,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: AssemblyVersion("1.0.35.456")]
[assembly: AssemblyFileVersion("1.0.35.456")]
[assembly: AssemblyVersion("1.0.89.455")]
[assembly: AssemblyFileVersion("1.0.89.455")]

View File

@@ -152,12 +152,13 @@
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<Compile Include="NativeMethods.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AssemblyInfo.tt</DependentUpon>
</Compile>
<Compile Include="ServerConfig.cs" />
<Compile Include="TorchConfig.cs" />
<Compile Include="TorchService.cs">
<SubType>Component</SubType>
</Compile>
@@ -165,12 +166,19 @@
<SubType>Component</SubType>
</Compile>
<Compile Include="ViewModels\ConfigDedicatedViewModel.cs" />
<Compile Include="ViewModels\SessionSettingsViewModel.cs" />
<Compile Include="Views\AddWorkshopItemsDialog.xaml.cs">
<DependentUpon>AddWorkshopItemsDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Views\ChatControl.xaml.cs">
<DependentUpon>ChatControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\CollectionEditor.xaml.cs">
<DependentUpon>CollectionEditor.xaml</DependentUpon>
</Compile>
<Compile Include="Views\ConfigControl.xaml.cs">
<DependentUpon>ConfigControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\ModsControl.xaml.cs">
<DependentUpon>ModsControl.xaml</DependentUpon>
</Compile>
@@ -182,9 +190,6 @@
</Compile>
<Compile Include="Program.cs" />
<Compile Include="TorchServer.cs" />
<Compile Include="Views\PropertyGrid.xaml.cs">
<DependentUpon>PropertyGrid.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\Resources.Designer.cs">
@@ -229,6 +234,14 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\CollectionEditor.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\ConfigControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\ModsControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@@ -241,10 +254,6 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\PropertyGrid.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Resource Include="torchicon.ico" />

View File

@@ -10,7 +10,7 @@ using VRage.Dedicated;
namespace Torch.Server
{
public class ServerConfig
public class TorchConfig
{
private static Logger _log = LogManager.GetLogger("Config");
@@ -18,8 +18,11 @@ namespace Torch.Server
public string InstanceName { get; set; }
public int Autosave { get; set; }
public bool AutoRestart { get; set; }
public bool LogChat { get; set; }
public ServerConfig(string instanceName = "Torch", string instancePath = null, int autosaveInterval = 5, bool autoRestart = false)
public TorchConfig() : this("Torch") { }
public TorchConfig(string instanceName = "Torch", string instancePath = null, int autosaveInterval = 5, bool autoRestart = false)
{
InstanceName = instanceName;
InstancePath = instancePath ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Torch", InstanceName);
@@ -27,15 +30,15 @@ namespace Torch.Server
AutoRestart = autoRestart;
}
public static ServerConfig LoadFrom(string path)
public static TorchConfig LoadFrom(string path)
{
try
{
var serializer = new XmlSerializer(typeof(ServerConfig));
ServerConfig config;
var serializer = new XmlSerializer(typeof(TorchConfig));
TorchConfig config;
using (var f = File.OpenRead(path))
{
config = (ServerConfig)serializer.Deserialize(f);
config = (TorchConfig)serializer.Deserialize(f);
}
return config;
}
@@ -50,7 +53,7 @@ namespace Torch.Server
{
try
{
var serializer = new XmlSerializer(typeof(ServerConfig));
var serializer = new XmlSerializer(typeof(TorchConfig));
using (var f = File.OpenWrite(path))
{
serializer.Serialize(f, this);

View File

@@ -18,9 +18,12 @@ using Sandbox.Game;
using Sandbox.Game.Gui;
using Sandbox.Game.World;
using SpaceEngineers.Game;
using SteamSDK;
using Torch.API;
using VRage.Dedicated;
using VRage.FileSystem;
using VRage.Game;
using VRage.Game.ModAPI;
using VRage.Game.SessionComponents;
using VRage.Profiler;
@@ -30,22 +33,24 @@ namespace Torch.Server
{
public Thread GameThread { get; private set; }
public bool IsRunning { get; private set; }
public string InstancePath { get; }
public string InstanceName { get; }
public string InstancePath { get; private set; }
public string InstanceName { get; private set; }
private readonly AutoResetEvent _stopHandle = new AutoResetEvent(false);
internal TorchServer(ServerConfig options)
public TorchServer(TorchConfig options = null)
{
InstanceName = options.InstanceName;
InstancePath = options.InstancePath;
var opt = options ?? new TorchConfig();
InstanceName = opt.InstanceName;
InstancePath = opt.InstancePath;
}
public override void Init()
{
base.Init();
Log.Info($"Server instance {InstanceName} at path {InstancePath}");
Log.Info($"Init server instance '{InstanceName}' at path '{InstancePath}'");
MyFakes.ENABLE_INFINARIO = false;
MyPerGameSettings.SendLogToKeen = false;
@@ -55,16 +60,30 @@ namespace Torch.Server
MyPerServerSettings.GameDSDescription = "Your place for space engineering, destruction and exploring.";
MySessionComponentExtDebug.ForceDisable = true;
MyPerServerSettings.AppId = 244850u;
ConfigForm<MyObjectBuilder_SessionSettings>.GameAttributes = Game.SpaceEngineers;
ConfigForm<MyObjectBuilder_SessionSettings>.OnReset = delegate
{
SpaceEngineersGame.SetupBasicGameInfo();
SpaceEngineersGame.SetupPerGameSettings();
};
var gameVersion = MyPerGameSettings.BasicGameInfo.GameVersion;
MyFinalBuildConstants.APP_VERSION = gameVersion ?? 0;
//InstancePath = InstanceName != null ? GetInstancePath(true, InstanceName) : GetInstancePath();
//TODO: Allows players to filter servers for Torch in the server browser.
//SteamServerAPI.Instance.GameServer.SetKeyValue("SM", "Torch");
}
public void SetConfig(IMyConfigDedicated config)
{
MySandboxGame.ConfigDedicated = config;
}
public void SetInstance(string path = null, string name = null)
{
if (path != null)
InstancePath = path;
if (name != null)
InstanceName = name;
}
public void Start(IMyConfigDedicated config)
{
SetConfig(config);
Start();
}
/// <summary>
@@ -103,10 +122,7 @@ namespace Torch.Server
if (Thread.CurrentThread.ManagedThreadId != GameThread?.ManagedThreadId && MySandboxGame.Static.IsRunning)
{
Log.Info("Requesting server stop.");
MySandboxGame.Static.Invoke(Stop);
_stopHandle.WaitOne(10000);
Log.Error("Server stop timed out.");
Invoke(Stop);
return;
}

View File

@@ -33,9 +33,9 @@ namespace Torch.Server
base.OnStart(args);
string configName = args.Length > 0 ? args[0] : "TorchConfig.xml";
var options = new ServerConfig("Torch");
var options = new TorchConfig("Torch");
if (File.Exists(configName))
options = ServerConfig.LoadFrom(configName);
options = TorchConfig.LoadFrom(configName);
else
options.SaveTo(configName);

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -11,27 +12,97 @@ namespace Torch.Server.ViewModels
{
public class ConfigDedicatedViewModel : ViewModel
{
public IMyConfigDedicated Config { get; }
private MyConfigDedicated<MyObjectBuilder_SessionSettings> _config;
public MTObservableCollection<string> Administrators { get; } = new MTObservableCollection<string>();
public MTObservableCollection<ulong> BannedPlayers { get; } = new MTObservableCollection<ulong>();
public ConfigDedicatedViewModel()
{
_config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>("");
SessionSettings = new SessionSettingsViewModel(_config.SessionSettings);
Administrators = new ObservableCollection<string>(_config.Administrators);
Banned = new ObservableCollection<ulong>(_config.Banned);
Mods = new ObservableCollection<ulong>(_config.Mods);
}
public ConfigDedicatedViewModel(MyConfigDedicated<MyObjectBuilder_SessionSettings> configDedicated)
{
_config = configDedicated;
SessionSettings = new SessionSettingsViewModel(_config.SessionSettings);
Administrators = new ObservableCollection<string>(_config.Administrators);
Banned = new ObservableCollection<ulong>(_config.Banned);
Mods = new ObservableCollection<ulong>(_config.Mods);
}
public SessionSettingsViewModel SessionSettings { get; }
public ObservableCollection<string> WorldPaths { get; } = new ObservableCollection<string>();
public ObservableCollection<string> Administrators { get; }
public ObservableCollection<ulong> Banned { get; }
public ObservableCollection<ulong> Mods { get; }
public int AsteroidAmount
{
get { return Config.AsteroidAmount; }
set { Config.AsteroidAmount = value; OnPropertyChanged(); }
get { return _config.AsteroidAmount; }
set { _config.AsteroidAmount = value; OnPropertyChanged(); }
}
public ConfigDedicatedViewModel(IMyConfigDedicated config)
public ulong GroupId
{
Config = config;
Config.Administrators.ForEach(x => Administrators.Add(x));
Config.Banned.ForEach(x => BannedPlayers.Add(x));
get { return _config.GroupID; }
set { _config.GroupID = value; OnPropertyChanged(); }
}
public void FlushConfig()
public bool IgnoreLastSession
{
Config.Administrators = Administrators.ToList();
get { return _config.IgnoreLastSession; }
set { _config.IgnoreLastSession = value; OnPropertyChanged(); }
}
public string IP
{
get { return _config.IP; }
set { _config.IP = value; OnPropertyChanged(); }
}
public int Port
{
get { return _config.ServerPort; }
set { _config.ServerPort = value; OnPropertyChanged(); }
}
public string ServerName
{
get { return _config.ServerName; }
set { _config.ServerName = value; OnPropertyChanged(); }
}
public bool PauseGameWhenEmpty
{
get { return _config.PauseGameWhenEmpty; }
set { _config.PauseGameWhenEmpty = value; OnPropertyChanged(); }
}
public string PremadeCheckpointPath
{
get { return _config.PremadeCheckpointPath; }
set { _config.PremadeCheckpointPath = value; OnPropertyChanged(); }
}
public string LoadWorld
{
get { return _config.LoadWorld; }
set { _config.LoadWorld = value; OnPropertyChanged(); }
}
public int SteamPort
{
get { return _config.SteamPort; }
set { _config.SteamPort = value; OnPropertyChanged(); }
}
public string WorldName
{
get { return _config.WorldName; }
set { _config.WorldName = value; OnPropertyChanged(); }
}
}
}

View File

@@ -0,0 +1,335 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.Game;
using VRage.Library.Utils;
namespace Torch.Server.ViewModels
{
public class SessionSettingsViewModel : ViewModel
{
private MyObjectBuilder_SessionSettings _settings;
public SessionSettingsViewModel()
{
_settings = new MyObjectBuilder_SessionSettings();
}
public SessionSettingsViewModel(MyObjectBuilder_SessionSettings settings)
{
_settings = settings;
}
#region Multipliers
public float InventorySizeMultiplier
{
get { return _settings.InventorySizeMultiplier; }
set { _settings.InventorySizeMultiplier = value; OnPropertyChanged(); }
}
public float RefinerySpeedMultiplier
{
get { return _settings.RefinerySpeedMultiplier; }
set { _settings.RefinerySpeedMultiplier = value; OnPropertyChanged(); }
}
public float AssemblerEfficiencyMultiplier
{
get { return _settings.AssemblerEfficiencyMultiplier; }
set { _settings.AssemblerEfficiencyMultiplier = value; OnPropertyChanged(); }
}
public float AssemblerSpeedMultiplier
{
get { return _settings.AssemblerSpeedMultiplier; }
set { _settings.AssemblerSpeedMultiplier = value; OnPropertyChanged(); }
}
public float GrinderSpeedMultiplier
{
get { return _settings.GrinderSpeedMultiplier; }
set { _settings.GrinderSpeedMultiplier = value; OnPropertyChanged(); }
}
public float HackSpeedMultiplier
{
get { return _settings.HackSpeedMultiplier; }
set { _settings.HackSpeedMultiplier = value; OnPropertyChanged(); }
}
#endregion
#region NPCs
public bool EnableDrones
{
get { return _settings.EnableDrones; }
set { _settings.EnableDrones = value; OnPropertyChanged(); }
}
public bool EnableEncounters
{
get { return _settings.EnableEncounters; }
set { _settings.EnableEncounters = value; OnPropertyChanged(); }
}
public bool EnableSpiders
{
get { return _settings.EnableSpiders; }
set { _settings.EnableSpiders = value; OnPropertyChanged(); }
}
public bool EnableWolves
{
get { return _settings.EnableWolfs; }
set { _settings.EnableWolfs = value; OnPropertyChanged(); }
}
public bool EnableCargoShips
{
get { return _settings.CargoShipsEnabled; }
set { _settings.CargoShipsEnabled = value; OnPropertyChanged(); }
}
#endregion
#region Environment
public bool EnableSunRotation
{
get { return _settings.EnableSunRotation; }
set { _settings.EnableSunRotation = value; OnPropertyChanged(); }
}
public bool EnableAirtightness
{
get { return _settings.EnableOxygenPressurization; }
set { _settings.EnableOxygenPressurization = value; OnPropertyChanged(); }
}
public bool EnableOxygen
{
get { return _settings.EnableOxygen; }
set { _settings.EnableOxygen = value; OnPropertyChanged(); }
}
public bool EnableDestructibleBlocks
{
get { return _settings.DestructibleBlocks; }
set { _settings.DestructibleBlocks = value; OnPropertyChanged(); }
}
public bool EnableToolShake
{
get { return _settings.EnableToolShake; }
set { _settings.EnableToolShake = value; OnPropertyChanged(); }
}
public bool EnableVoxelDestruction
{
get { return _settings.EnableVoxelDestruction; }
set { _settings.EnableVoxelDestruction = value; OnPropertyChanged(); }
}
public List<string> EnvironmentHostilityValues => Enum.GetNames(typeof(MyEnvironmentHostilityEnum)).ToList();
public string EnvironmentHostility
{
get { return _settings.EnvironmentHostility.ToString(); }
set { Enum.TryParse(value, true, out _settings.EnvironmentHostility); OnPropertyChanged(); }
}
public bool EnableFlora
{
get { return _settings.EnableFlora; }
set { _settings.EnableFlora = value; OnPropertyChanged(); }
}
#endregion
public List<string> GameModeValues => Enum.GetNames(typeof(MyGameModeEnum)).ToList();
public string GameMode
{
get { return _settings.GameMode.ToString(); }
set { Enum.TryParse(value, true, out _settings.GameMode); OnPropertyChanged(); }
}
public bool EnableAutoHealing
{
get { return _settings.AutoHealing; }
set { _settings.AutoHealing = value; OnPropertyChanged(); }
}
public bool EnableCopyPaste
{
get { return _settings.EnableCopyPaste; }
set { _settings.EnableCopyPaste = value; OnPropertyChanged(); }
}
public bool ShowPlayerNamesOnHud
{
get { return _settings.ShowPlayerNamesOnHud; }
set { _settings.ShowPlayerNamesOnHud = value; OnPropertyChanged(); }
}
public bool EnableThirdPerson
{
get { return _settings.Enable3rdPersonView; }
set { _settings.Enable3rdPersonView = value; OnPropertyChanged(); }
}
public bool EnableSpectator
{
get { return _settings.EnableSpectator; }
set { _settings.EnableSpectator = value; OnPropertyChanged(); }
}
public bool SpawnWithTools
{
get { return _settings.SpawnWithTools; }
set { _settings.SpawnWithTools = value; OnPropertyChanged(); }
}
public bool EnableConvertToStation
{
get { return _settings.EnableConvertToStation; }
set { _settings.EnableConvertToStation = value; OnPropertyChanged(); }
}
public bool EnableJetpack
{
get { return _settings.EnableJetpack; }
set { _settings.EnableJetpack = value; OnPropertyChanged(); }
}
public bool EnableRemoteOwnerRemoval
{
get { return _settings.EnableRemoteBlockRemoval; }
set { _settings.EnableRemoteBlockRemoval = value; OnPropertyChanged(); }
}
public bool EnableRespawnShips
{
get { return _settings.EnableRespawnShips; }
set { _settings.EnableRespawnShips = value; OnPropertyChanged(); }
}
public bool EnableScripterRole
{
get { return _settings.EnableScripterRole; }
set { _settings.EnableScripterRole = value; OnPropertyChanged(); }
}
public bool EnableRealisticSound
{
get { return _settings.RealisticSound; }
set { _settings.RealisticSound = value; OnPropertyChanged(); }
}
public bool ResetOwnership
{
get { return _settings.ResetOwnership; }
set { _settings.ResetOwnership = value; OnPropertyChanged(); }
}
public bool DeleteRespawnShips
{
get { return _settings.RespawnShipDelete; }
set { _settings.RespawnShipDelete = value; OnPropertyChanged(); }
}
public bool EnableThrusterDamage
{
get { return _settings.ThrusterDamage; }
set { _settings.ThrusterDamage = value; OnPropertyChanged(); }
}
public bool EnableWeapons
{
get { return _settings.WeaponsEnabled; }
set { _settings.WeaponsEnabled = value; OnPropertyChanged(); }
}
public bool EnableIngameScripts
{
get { return _settings.EnableIngameScripts; }
set { _settings.EnableIngameScripts = value; OnPropertyChanged(); }
}
public uint AutosaveInterval
{
get { return _settings.AutoSaveInMinutes; }
set { _settings.AutoSaveInMinutes = value; OnPropertyChanged(); }
}
public int FloraDensity
{
get { return _settings.FloraDensity; }
set { _settings.FloraDensity = value; OnPropertyChanged(); }
}
public float FloraDensityMultiplier
{
get { return _settings.FloraDensityMultiplier; }
set { _settings.FloraDensityMultiplier = value; OnPropertyChanged(); }
}
public short MaxBackupSaves
{
get { return _settings.MaxBackupSaves; }
set { _settings.MaxBackupSaves = value; OnPropertyChanged(); }
}
public int MaxBlocksPerPlayer
{
get { return _settings.MaxBlocksPerPlayer; }
set { _settings.MaxBlocksPerPlayer = value; OnPropertyChanged(); }
}
public short MaxFloatingObjects
{
get { return _settings.MaxFloatingObjects; }
set { _settings.MaxFloatingObjects = value; OnPropertyChanged(); }
}
public int MaxGridSize
{
get { return _settings.MaxGridSize; }
set { _settings.MaxGridSize = value; OnPropertyChanged(); }
}
public short MaxPlayers
{
get { return _settings.MaxPlayers; }
set { _settings.MaxPlayers = value; OnPropertyChanged(); }
}
public int PhysicsIterations
{
get { return _settings.PhysicsIterations; }
set { _settings.PhysicsIterations = value; OnPropertyChanged(); }
}
public float SpawnTimeMultiplier
{
get { return _settings.SpawnShipTimeMultiplier; }
set { _settings.SpawnShipTimeMultiplier = value; OnPropertyChanged(); }
}
public float SunRotationInterval
{
get { return _settings.SunRotationIntervalMinutes; }
set { _settings.SunRotationIntervalMinutes = value; OnPropertyChanged(); }
}
public int ViewDistance
{
get { return _settings.ViewDistance; }
set { _settings.ViewDistance = value; OnPropertyChanged(); }
}
public int WorldSize
{
get { return _settings.ViewDistance; }
set { _settings.WorldSizeKm = value; OnPropertyChanged(); }
}
}
}

View File

@@ -36,17 +36,17 @@ namespace Torch.Server
public void BindServer(ITorchServer server)
{
_server = server;
server.Multiplayer.MessageReceived += Refresh;
Refresh();
//ChatItems.ItemsSource = server.Multiplayer.ChatHistory;
//server.Multiplayer.MessageReceived += Refresh;
//Refresh();
}
private void Refresh(IChatItem chatItem = null)
private void Refresh(IChatMessage chatItem = null)
{
Dispatcher.Invoke(() =>
{
ChatItems.ItemsSource = null;
ChatItems.ItemsSource = _server.Multiplayer.Chat;
//ChatItems.ItemsSource = _server.Multiplayer.Chat;
});
}

View File

@@ -0,0 +1,13 @@
<Window x:Class="Torch.Server.Views.CollectionEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Torch.Server.Views"
mc:Ignorable="d"
Title="Edit Collection" Height="300" Width="300">
<DockPanel>
<Button Content="Done" Margin="3" Click="OkButton_OnClick" DockPanel.Dock="Bottom"/>
<TextBox x:Name="ItemList" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" Margin="3"/>
</DockPanel>
</Window>

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
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.Shapes;
namespace Torch.Server.Views
{
/// <summary>
/// Interaction logic for CollectionEditor.xaml
/// </summary>
public partial class CollectionEditor : Window
{
public CollectionEditor()
{
InitializeComponent();
}
public void Edit<T>(ICollection<T> collection, string name)
{
ItemList.Text = string.Join("\r\n", collection.Select(x => x.ToString()));
Title = name;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
ShowDialog();
var parsed = new List<T>();
try
{
var converter = TypeDescriptor.GetConverter(typeof(T));
foreach (var item in ItemList.Text.Split(new[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries))
{
parsed.Add((T)converter.ConvertFromString(item));
}
}
catch (Exception)
{
MessageBox.Show("Error parsing list, check your input.", "Edit Error");
return;
}
collection.Clear();
foreach (var item in parsed)
collection.Add(item);
}
private void OkButton_OnClick(object sender, RoutedEventArgs e)
{
Close();
}
}
}

View File

@@ -0,0 +1,183 @@
<UserControl x:Class="Torch.Server.Views.ConfigControl"
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.Server.Views"
xmlns:viewModels="clr-namespace:Torch.Server.ViewModels"
mc:Ignorable="d"
Background="White">
<UserControl.DataContext>
<viewModels:ConfigDedicatedViewModel/>
</UserControl.DataContext>
<StackPanel>
<DockPanel>
<Label Content="World:" DockPanel.Dock="Left"/>
<ComboBox Text="{Binding LoadWorld}" ItemsSource="{Binding WorldPaths}" IsEditable="True" Margin="3"/>
</DockPanel>
<DockPanel>
<StackPanel Margin="3" DockPanel.Dock="Left">
<Label Content="Server Name"/>
<TextBox Text="{Binding ServerName}" Margin="3,0,3,3" Width="130"/>
<Label Content="World Name"/>
<TextBox Text="{Binding WorldName}" Margin="3,0,3,3" Width="130"/>
<Label Content="Whitelist Group ID"/>
<TextBox Text="{Binding GroupId}" Margin="3,0,3,3" Width="130"/>
<Label Content="Server IP"/>
<StackPanel Orientation="Horizontal" Margin="3,0,3,3">
<TextBox Text="{Binding IP}" Width="100" Height="20"/>
<Label Content=":"></Label>
<TextBox Text="{Binding Port}" Width="50" Height="20"/>
</StackPanel>
<Label Content="Steam Port"/>
<TextBox Text="{Binding SteamPort}" Width="130" Margin="3,0,3,3"/>
<CheckBox IsChecked="{Binding IgnoreLastSession}" Content="Ignore Last Session" Margin="3"/>
<CheckBox IsChecked="{Binding PauseGameWhenEmpty}" Content="Pause When Empty" Margin="3"/>
<Label Content="Banned Players"/>
<ListBox ItemsSource="{Binding Banned}" Width="130" Margin="3,0,3,3" Height="100" MouseDoubleClick="Banned_OnMouseDoubleClick"/>
<Label Content="Administrators"/>
<ListBox ItemsSource="{Binding Administrators}" Width="130" Margin="3,0,3,3" Height="100" MouseDoubleClick="Administrators_OnMouseDoubleClick"/>
<Label Content="Mods"/>
<ListBox ItemsSource="{Binding Mods}" Width="130" Margin="3,0,3,3" Height="100" MouseDoubleClick="Mods_OnMouseDoubleClick"/>
</StackPanel>
<ScrollViewer Margin="3" DockPanel.Dock="Right">
<StackPanel DataContext="{Binding SessionSettings}">
<Expander Header="Multipliers">
<StackPanel Margin="10,0,0,0">
<DockPanel>
<TextBox Text="{Binding InventorySizeMultiplier}" Margin="3" Width="70"/>
<Label Content="Inventory Size"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding RefinerySpeedMultiplier}" Margin="3" Width="70"/>
<Label Content="Refinery Speed"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding AssemblerEfficiencyMultiplier}" Margin="3" Width="70"/>
<Label Content="Assembler Efficiency"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding AssemblerSpeedMultiplier}" Margin="3" Width="70"/>
<Label Content="Assembler Speed"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding GrinderSpeedMultiplier}" Margin="3" Width="70"/>
<Label Content="Grinder Speed"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding HackSpeedMultiplier}" Margin="3" Width="70"/>
<Label Content="Hacking Speed"/>
</DockPanel>
</StackPanel>
</Expander>
<Expander Header="NPCs">
<StackPanel Margin="10,0,0,0">
<CheckBox IsChecked="{Binding EnableDrones}" Content="Enable Drones" Margin="3"/>
<CheckBox IsChecked="{Binding EnableEncounters}" Content="Enable Encounters" Margin="3"/>
<CheckBox IsChecked="{Binding EnableSpiders}" Content="Enable Spiders" Margin="3"/>
<CheckBox IsChecked="{Binding EnableWolves}" Content="Enable Wolves" Margin="3"/>
<CheckBox IsChecked="{Binding EnableCargoShips}" Content="Enable Cargo Ships" Margin="3"/>
</StackPanel>
</Expander>
<Expander Header="Environment">
<StackPanel Margin="10,0,0,0">
<CheckBox IsChecked="{Binding EnableSunRotation}" Content="Enable Sun Rotation" Margin="3"/>
<CheckBox IsChecked="{Binding EnableAirtightness}" Content="Enable Airtightness" Margin="3"/>
<CheckBox IsChecked="{Binding EnableOxygen}" Content="Enable Oxygen" Margin="3"/>
<CheckBox IsChecked="{Binding EnableDestructibleBlocks}" Content="Enable Destructible Blocks" Margin="3"/>
<CheckBox IsChecked="{Binding EnableToolShake}" Content="Enable Tool Shake" Margin="3"/>
<CheckBox IsChecked="{Binding EnableVoxelDestruction}" Content="Enable Voxel Destruction" Margin="3"/>
<CheckBox IsChecked="{Binding EnableFlora}" Content="Enable Flora" Margin="3"/>
<DockPanel>
<ComboBox SelectedItem="{Binding EnvironmentHostility}" ItemsSource="{Binding EnvironmentHostilityValues}" Margin="3" Width="100" DockPanel.Dock="Left"/>
<Label Content="Environment Hostility"></Label>
</DockPanel>
</StackPanel>
</Expander>
<Expander Header="Players">
<StackPanel Margin="10,0,0,0">
<CheckBox IsChecked="{Binding EnableAutoHealing}" Content="Auto Healing" Margin="3"/>
<CheckBox IsChecked="{Binding EnableCopyPaste}" Content="Enable Copy/Paste" Margin="3"/>
<CheckBox IsChecked="{Binding ShowPlayerNamesOnHud}" Content="Show Player Names on HUD" Margin="3"/>
</StackPanel>
</Expander>
<Expander Header="Miscellaneous">
<StackPanel Margin="10,0,0,0">
<DockPanel>
<TextBox Text="{Binding AutosaveInterval}" Margin="3" Width="70"/>
<Label Content="Autosave Interval (minutes)"/>
</DockPanel>
<CheckBox IsChecked="{Binding EnableThirdPerson}" Content="Enable 3rd Person Camera" Margin="3"/>
<CheckBox IsChecked="{Binding EnableConvertToStation}" Content="Enable Convert to Station" Margin="3"/>
<CheckBox IsChecked="{Binding EnableJetpack}" Content="Enable Jetpack" Margin="3"/>
<CheckBox IsChecked="{Binding EnableRemoteOwnerRemoval}" Content="Enable Remote Ownership Removal" Margin="3"/>
<CheckBox IsChecked="{Binding EnableRespawnShips}" Content="Enable Respawn Ships" Margin="3"/>
<CheckBox IsChecked="{Binding EnableScripterRole}" Content="Enable Scripter Role" Margin="3"/>
<CheckBox IsChecked="{Binding EnableSpectator}" Content="Enable Spectator Camera" Margin="3"/>
<CheckBox IsChecked="{Binding EnableRealisticSound}" Content="Enable Realistic Sound" Margin="3"/>
<CheckBox IsChecked="{Binding ResetOwnership}" Content="Reset Ownership" Margin="3"/>
<CheckBox IsChecked="{Binding DeleteRespawnShips}" Content="Remove Respawn Ships on Logoff" Margin="3"/>
<CheckBox IsChecked="{Binding EnableThrusterDamage}" Content="Enable Thruster Damage" Margin="3"/>
<CheckBox IsChecked="{Binding EnableWeapons}" Content="Enable Weapons" Margin="3"/>
<CheckBox IsChecked="{Binding EnableIngameScripts}" Content="Enable Ingame Scripts" Margin="3"/>
<CheckBox IsChecked="{Binding SpawnWithTools}" Content="Spawn With Tools" Margin="3"/>
<DockPanel>
<ComboBox SelectedItem="{Binding GameMode}" ItemsSource="{Binding GameModeValues}" Margin="3" Width="100" DockPanel.Dock="Left"/>
<Label Content="Game Mode"></Label>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding FloraDensity}" Margin="3" Width="70"/>
<Label Content="Flora Density"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding FloraDensityMultiplier}" Margin="3" Width="70"/>
<Label Content="Flora Density Multiplier"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding MaxBackupSaves}" Margin="3" Width="70"/>
<Label Content="Max Backup Saves"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding MaxBlocksPerPlayer}" Margin="3" Width="70"/>
<Label Content="Max Blocks Per Player"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding MaxFloatingObjects}" Margin="3" Width="70"/>
<Label Content="Max Floating Objects"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding MaxGridSize}" Margin="3" Width="70"/>
<Label Content="Max Grid Size"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding MaxPlayers}" Margin="3" Width="70"/>
<Label Content="Max Players"/>
</DockPanel>
<DockPanel ToolTip="Increases physics precision at the cost of performance.">
<TextBox Text="{Binding PhysicsIterations}" Margin="3" Width="70"/>
<Label Content="Physics Iterations"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding SpawnTimeMultiplier}" Margin="3" Width="70"/>
<Label Content="Respawn Time Multiplier"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding SunRotationInterval}" Margin="3" Width="70"/>
<Label Content="Sun Rotation Interval (mins)"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding ViewDistance}" Margin="3" Width="70"/>
<Label Content="View Distance (meters)"/>
</DockPanel>
<DockPanel>
<TextBox Text="{Binding WorldSize}" Margin="3" Width="70"/>
<Label Content="World Size (km)"/>
</DockPanel>
</StackPanel>
</Expander>
</StackPanel>
</ScrollViewer>
</DockPanel>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.IO;
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 Sandbox.Engine.Utils;
using Torch.Server.ViewModels;
using VRage.Game;
using Path = System.IO.Path;
namespace Torch.Server.Views
{
/// <summary>
/// Interaction logic for ConfigControl.xaml
/// </summary>
public partial class ConfigControl : UserControl
{
public MyConfigDedicated<MyObjectBuilder_SessionSettings> Config { get; set; }
private ConfigDedicatedViewModel _viewModel;
private string _configPath;
public ConfigControl()
{
InitializeComponent();
LoadDedicatedConfig(@"C:\ProgramData\Torch\Torch\SpaceEngineers-Dedicated.cfg");
}
public void SaveConfig()
{
Config.Save(_configPath);
}
public void LoadDedicatedConfig(string path)
{
Config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(path);
Config.Load(path);
_viewModel = new ConfigDedicatedViewModel(Config);
DataContext = _viewModel;
}
public void LoadDedicatedConfig(TorchConfig torchConfig)
{
var path = Path.Combine(torchConfig.InstancePath, "SpaceEngineers-Dedicated.cfg");
Config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(path);
Config.Load(path);
_configPath = path;
_viewModel = new ConfigDedicatedViewModel(Config);
var worldFolders = Directory.EnumerateDirectories(Path.Combine(torchConfig.InstancePath, "Saves"));
foreach (var f in worldFolders)
_viewModel.WorldPaths.Add(f);
DataContext = _viewModel;
}
private void Banned_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var editor = new CollectionEditor {Owner = Window.GetWindow(this)};
editor.Edit(_viewModel.Banned, "Banned Players");
}
private void Administrators_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var editor = new CollectionEditor { Owner = Window.GetWindow(this) };
editor.Edit(_viewModel.Administrators, "Administrators");
}
private void Mods_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var editor = new CollectionEditor { Owner = Window.GetWindow(this) };
editor.Edit(_viewModel.Mods, "Mods");
}
}
}

View File

@@ -7,14 +7,18 @@
mc:Ignorable="d">
<StackPanel Margin="0,0,0,0" Orientation="Vertical">
<Label Content="Mods" Margin="5,5,5,5"/>
<ListView x:Name="ModList" HorizontalAlignment="Left" Height="265" VerticalAlignment="Top" Width="300" Margin="5,5,5,5" MouseDoubleClick="modList_OnMouesDoubleClick">
<ListView x:Name="ModList" HorizontalAlignment="Left" Height="265" VerticalAlignment="Top" Width="300" Margin="5,5,5,5" MouseDoubleClick="modList_OnMouseDoubleClick">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FriendlyName}" ToolTip="{Binding Description}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="AddBtn" Content="Add Mods" Click="addBtn_Click" Margin="5,5,5,5"/>
<Button x:Name="RemBtn" Content="Remove Mods" Margin="5,5,5,5" Click="remBtn_Click"/>
<DockPanel>
<Button x:Name="RemBtn" Width="70" Content="Remove" Margin="3" Click="remBtn_Click" DockPanel.Dock="Right"/>
<Button x:Name="AddBtn" Width="70" Content="Add" Click="addBtn_Click" Margin="3" DockPanel.Dock="Right"/>
<Label Content="Mod ID:"/>
<TextBox x:Name="ModIdBox" Margin="3" DockPanel.Dock="Left"/>
</DockPanel>
</StackPanel>
</UserControl>

View File

@@ -31,23 +31,33 @@ namespace Torch.Server
}
private void addBtn_Click(object sender, RoutedEventArgs e)
{
ulong[] mods;
if (!string.IsNullOrEmpty(ModIdBox.Text))
{
mods = new ulong[1];
ulong.TryParse(ModIdBox.Text, out mods[0]);
}
else
{
var dialog = new AddModsDialog();
dialog.ShowDialog();
mods = dialog.Result;
}
foreach (var id in dialog.Result)
foreach (var id in mods)
{
var details = SteamHelper.GetItemDetails(id);
if (details.FileType != WorkshopFileType.Community)
continue;
var item = SteamHelper.GetModItem(details);
var desc = string.Join("\n", details.Description.ReadLines(5, true), "Double click to open the workshop page.");
var desc = details.Description.Length < 500 ? details.Description : details.Description.Substring(0, 500);
ModList.Items.Add(new ModViewModel(item, desc));
}
}
private void modList_OnMouesDoubleClick(object sender, MouseButtonEventArgs e)
private void modList_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var box = (ListView)sender;
if (box.SelectedItem == null)

View File

@@ -19,6 +19,7 @@ using Sandbox.Game.Multiplayer;
using Sandbox.Game.World;
using SteamSDK;
using Torch.API;
using VRage.Game.ModAPI;
namespace Torch.Server
{
@@ -37,35 +38,35 @@ namespace Torch.Server
public void BindServer(ITorchServer server)
{
_server = server;
server.Multiplayer.PlayerJoined += Refresh;
server.Multiplayer.PlayerLeft += Refresh;
//server.Multiplayer.PlayerJoined += Refresh;
//server.Multiplayer.PlayerLeft += Refresh;
Refresh();
}
private void Refresh(IPlayer player = null)
private void Refresh(IMyPlayer player = null)
{
Dispatcher.Invoke(() =>
{
PlayerList.ItemsSource = null;
PlayerList.ItemsSource = _server.Multiplayer.Players;
//PlayerList.ItemsSource = _server.Multiplayer.Players;
});
}
private void KickButton_Click(object sender, RoutedEventArgs e)
{
var player = PlayerList.SelectedItem as IPlayer;
var player = PlayerList.SelectedItem as IMyPlayer;
if (player != null)
{
_server.Multiplayer.KickPlayer(player.SteamId);
_server.Multiplayer.KickPlayer(player.SteamUserId);
}
}
private void BanButton_Click(object sender, RoutedEventArgs e)
{
var player = PlayerList.SelectedItem as IPlayer;
var player = PlayerList.SelectedItem as IMyPlayer;
if (player != null)
{
_server.Multiplayer.BanPlayer(player.SteamId);
_server.Multiplayer.BanPlayer(player.SteamUserId);
}
}
}

View File

@@ -1,15 +0,0 @@
<UserControl x:Class="Torch.Server.Views.PropertyGrid"
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.Server.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<DataGrid x:Name="Grid" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn x:Name="NameCol" Width="1*" Header="Name" Binding="{Binding Name}" IsReadOnly="True"/>
<DataGridTemplateColumn x:Name="ValCol" Width="1*" Header="Value"/>
</DataGrid.Columns>
</DataGrid>
</UserControl>

View File

@@ -1,68 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
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;
namespace Torch.Server.Views
{
/// <summary>
/// Interaction logic for PropertyGrid.xaml
/// </summary>
public partial class PropertyGrid : UserControl
{
public PropertyGrid()
{
InitializeComponent();
}
public void SetObject(object obj)
{
var props = obj.GetType().GetProperties();
foreach (var prop in props)
{
var p = prop.GetValue(obj);
Grid.Items.Add(new PropertyView(p, prop.Name));
}
}
}
public class PropertyView : ViewModel
{
private object _obj;
public string Name { get; }
public string Value { get { return _obj.ToString(); } }
public DataTemplate ValueEditTemplate;
public PropertyView(object obj, string name)
{
Name = name;
_obj = obj;
ValueEditTemplate = new DataTemplate();
}
}
/*
public class PropertyGridDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is IEnumerable)
{
}
}
}*/
}

View File

@@ -6,7 +6,7 @@
xmlns:local="clr-namespace:Torch.Server"
xmlns:views="clr-namespace:Torch.Server.Views"
mc:Ignorable="d"
Title="Piston" Height="600" Width="900">
Title="Torch" Height="900" Width="600">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Margin="5,5,5,5" Orientation="Horizontal">
<Button x:Name="BtnStart" Content="Start" Height="24" Width="75" Margin="5,0,5,0" HorizontalAlignment="Left" Click="BtnStart_Click" IsDefault="True"/>
@@ -17,10 +17,10 @@
</StackPanel>
<TabControl x:Name="TabControl" DockPanel.Dock="Bottom" Margin="5,0,5,5">
<TabItem Header="Configuration">
<DockPanel>
<local:ModsControl/>
<views:PropertyGrid x:Name="PropGrid"/>
</DockPanel>
<StackPanel>
<TextBox x:Name="InstancePathBox" Margin="3" Height="20" TextChanged="InstancePathBox_OnTextChanged"/>
<views:ConfigControl x:Name="ConfigControl" Margin="3"/>
</StackPanel>
</TabItem>
<TabItem Header="Chat/Players">
<DockPanel>

View File

@@ -27,7 +27,8 @@ namespace Torch.Server
/// </summary>
public partial class TorchUI : Window
{
private ITorchServer _server;
private TorchServer _server;
private TorchConfig _config;
private DateTime _startTime;
private readonly Timer _uiUpdate = new Timer
{
@@ -35,7 +36,7 @@ namespace Torch.Server
AutoReset = true,
};
public TorchUI(ITorchServer server)
public TorchUI(TorchServer server)
{
_server = server;
InitializeComponent();
@@ -46,6 +47,19 @@ namespace Torch.Server
PlayerList.BindServer(server);
}
public void LoadConfig(TorchConfig config)
{
if (!Directory.Exists(config.InstancePath))
return;
_config = config;
Dispatcher.Invoke(() =>
{
ConfigControl.LoadDedicatedConfig(config);
InstancePathBox.Text = config.InstancePath;
});
}
private void UiUpdate_Elapsed(object sender, ElapsedEventArgs e)
{
UpdateUptime();
@@ -67,7 +81,8 @@ namespace Torch.Server
((Button) sender).IsEnabled = false;
BtnStop.IsEnabled = true;
_uiUpdate.Start();
new Thread(() => _server.Start()).Start();
ConfigControl.SaveConfig();
new Thread(() => _server.Start(ConfigControl.Config)).Start();
}
private void BtnStop_Click(object sender, RoutedEventArgs e)
@@ -83,12 +98,23 @@ namespace Torch.Server
protected override void OnClosing(CancelEventArgs e)
{
if (_server?.IsRunning ?? false)
_server.Stop();
}
private void BtnRestart_Click(object sender, RoutedEventArgs e)
{
//Program.FullRestart();
}
private void InstancePathBox_OnTextChanged(object sender, TextChangedEventArgs e)
{
var name = (sender as TextBox).Text;
_server.SetInstance(null, name);
_config.InstancePath = name;
LoadConfig(_config);
}
}
}

View File

@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.25928.0
VisualStudioVersion = 15.0.26127.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch", "Torch\Torch.csproj", "{7E01635C-3B67-472E-BCD6-C5539564F214}"
EndProject
@@ -13,8 +13,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Server", "Torch.Serve
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Torch.Launcher", "Torch.Launcher\Torch.Launcher.csproj", "{19292801-5B9C-4EE0-961F-0FA37B3A6C3D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestPlugin", "TestPlugin\TestPlugin.csproj", "{ABD18A6C-F638-44E9-8E55-DEDEA321C600}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7AD02A71-1D4C-48F9-A8C1-789A5512424F}"
ProjectSection(SolutionItems) = preProject
NLog.config = NLog.config
@@ -58,12 +56,6 @@ Global
{19292801-5B9C-4EE0-961F-0FA37B3A6C3D}.Release|Any CPU.ActiveCfg = Release|x64
{19292801-5B9C-4EE0-961F-0FA37B3A6C3D}.Release|x64.ActiveCfg = Release|x64
{19292801-5B9C-4EE0-961F-0FA37B3A6C3D}.Release|x64.Build.0 = Release|x64
{ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Debug|Any CPU.ActiveCfg = Debug|x64
{ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Debug|x64.ActiveCfg = Debug|x64
{ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Debug|x64.Build.0 = Debug|x64
{ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Release|Any CPU.ActiveCfg = Release|x64
{ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Release|x64.ActiveCfg = Release|x64
{ABD18A6C-F638-44E9-8E55-DEDEA321C600}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

37
Torch/ChatMessage.cs Normal file
View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Engine.Multiplayer;
using Sandbox.Engine.Networking;
using Torch.API;
using VRage.Network;
namespace Torch
{
public struct ChatMessage : IChatMessage
{
public DateTime Timestamp { get; }
public ulong SteamId { get; }
public string Name { get; }
public string Message { get; }
public ChatMessage(DateTime timestamp, ulong steamId, string name, string message)
{
Timestamp = timestamp;
SteamId = steamId;
Name = name;
Message = message;
}
public static ChatMessage FromChatMsg(ChatMsg msg, DateTime dt = default(DateTime))
{
return new ChatMessage(
dt == default(DateTime) ? DateTime.Now : dt,
msg.Author,
MyMultiplayer.Static.GetMemberName(msg.Author),
msg.Text);
}
}
}

View File

@@ -9,6 +9,25 @@ namespace Torch.Collections
public class KeyTree<TKey, TValue>
{
private Dictionary<TKey, KeyTreeNode<TKey, TValue>> _nodes = new Dictionary<TKey, KeyTreeNode<TKey, TValue>>();
public KeyTreeNode<TKey, TValue> this[TKey key] => _nodes[key];
public void AddNode(TKey key, TValue value)
{
_nodes.Add(key, new KeyTreeNode<TKey, TValue>(key, value));
}
public bool RemoveNode(TKey key)
{
return _nodes.Remove(key);
}
public IEnumerable<KeyTreeNode<TKey, TValue>> Traverse()
{
foreach (var node in _nodes.Values)
foreach (var child in node.Traverse())
yield return child;
}
}
public class KeyTreeNode<TKey, TValue>
@@ -26,6 +45,8 @@ namespace Torch.Collections
Value = value;
}
public KeyTreeNode<TKey, TValue> this[TKey key] => _children[key];
public KeyTreeNode<TKey, TValue> GetChild(TKey key)
{
if (_children.TryGetValue(key, out KeyTreeNode<TKey, TValue> value))

View File

@@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Torch.API;
namespace Torch
{
public static class PlayerInfoCache
{
private static readonly Dictionary<ulong, Player> _cache = new Dictionary<ulong, Player>();
public static Player GetOrCreate(ulong steamId)
{
if (_cache.TryGetValue(steamId, out Player info))
return info;
info = new Player(steamId) {State = ConnectionState.Unknown};
_cache.Add(steamId, info);
return info;
}
public static void Add(Player info)
{
if (_cache.ContainsKey(info.SteamId))
return;
_cache.Add(info.SteamId, info);
}
public static void Reset()
{
_cache.Clear();
}
}
}

View File

@@ -25,12 +25,17 @@ namespace Torch.Commands
/// The command arguments split by spaces and quotes. Ex. "this is" a command -> {this is, a, command}
/// </summary>
public List<string> Args { get; }
/// <summary>
/// The non-split argument string.
/// </summary>
public string RawArgs { get; }
public CommandContext(ITorchBase torch, ITorchPlugin plugin, IMyPlayer player, List<string> args = null)
public CommandContext(ITorchBase torch, ITorchPlugin plugin, IMyPlayer player, string rawArgs = null, List<string> args = null)
{
Torch = torch;
Plugin = plugin;
Player = player;
RawArgs = rawArgs;
Args = args ?? new List<string>();
}

View File

@@ -19,13 +19,12 @@ namespace Torch.Commands
public CommandTree Commands { get; set; } = new CommandTree();
private Logger _log = LogManager.GetLogger(nameof(CommandManager));
private readonly ITorchBase _torch;
private readonly ChatManager _chatManager = ChatManager.Instance;
public CommandManager(ITorchBase torch, char prefix = '/')
{
_torch = torch;
Prefix = prefix;
_chatManager.MessageRecieved += HandleCommand;
ChatManager.Instance.MessageRecieved += HandleCommand;
RegisterCommandModule(typeof(TorchCommands));
}
@@ -84,12 +83,7 @@ namespace Torch.Commands
}
var cmdText = new string(msg.Text.Skip(1).ToArray());
var split = Regex.Matches(cmdText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();
if (split.Count == 0)
return;
var command = Commands.ParseCommand(split, out List<string> args);
var command = Commands.GetCommand(cmdText, out string argText);
if (command != null)
{
@@ -102,8 +96,9 @@ namespace Torch.Commands
return;
}
var splitArgs = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();
_log.Trace($"Invoking {cmdPath} for player {player.DisplayName}");
var context = new CommandContext(_torch, command.Plugin, player, args);
var context = new CommandContext(_torch, command.Plugin, player, argText, splitArgs);
command.Invoke(context);
_log.Info($"Player {player.DisplayName} ran command '{msg.Text}'");
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Torch.API;
using VRage.Collections;
using VRage.Library.Collections;
@@ -75,13 +76,17 @@ namespace Torch.Commands
{
node = node.Subcommands[current];
}
else
{
break;
}
}
commandNode = node;
return path.Count - i + 1;
return i;
}
public Command ParseCommand(List<string> path, out List<string> args)
public Command GetCommand(List<string> path, out List<string> args)
{
args = new List<string>();
var skip = GetNode(path, out CommandNode node);
@@ -89,6 +94,29 @@ namespace Torch.Commands
return node.Command;
}
public Command GetCommand(string commandText, out string argText)
{
var split = commandText.Split(new []{' '}, StringSplitOptions.RemoveEmptyEntries).ToList();
var skip = GetNode(split, out CommandNode node);
if (skip == -1)
{
argText = "";
return null;
}
if (split.Count > skip)
{
var substringIndex = commandText.IndexOf(split[skip]);
if (substringIndex <= commandText.Length)
{
argText = commandText.Substring(substringIndex);
return node.Command;
}
}
argText = "";
return node.Command;
}
public string GetTreeString()
{
@@ -102,6 +130,17 @@ namespace Torch.Commands
return sb.ToString();
}
public IEnumerable<CommandNode> WalkTree(CommandNode root = null)
{
foreach (var node in root?.GetChildren() ?? _root.Values)
{
yield return node;
foreach (var child in WalkTree(node))
yield return child;
}
}
private void DebugNode(CommandNode commandNode, StringBuilder sb, ref int indent)
{
sb.AppendLine(new string(' ', indent) + commandNode.Name);
@@ -143,6 +182,11 @@ namespace Torch.Commands
return Subcommands.TryGetValue(name, out node);
}
public IEnumerable<CommandNode> GetChildren()
{
return _subcommands.Values;
}
public List<string> GetPath()
{
var path = new List<string> {Name};

View File

@@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.Game.ModAPI;
namespace Torch.Commands
{
public class DefaultPermissionAttribute : Attribute
{
public MyPromoteLevel Level { get; }
public DefaultPermissionAttribute(MyPromoteLevel level)
{
Level = level;
}
}
}

View File

@@ -3,21 +3,20 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog;
using Sandbox.Engine.Multiplayer;
using VRage;
using VRage.Library.Collections;
using VRage.Network;
using VRage.Serialization;
using VRage.Utils;
namespace Torch.Managers
{
public class ChatManager
{
private ChatManager()
{
NetworkManager.Instance.RegisterNetworkHandlers(new ChatIntercept());
}
private static ChatManager _instance;
public static ChatManager Instance => _instance ?? (_instance = new ChatManager());
public static ChatManager Instance { get; } = new ChatManager();
private static Logger _log = LogManager.GetLogger(nameof(ChatManager));
public delegate void MessageRecievedDel(ChatMsg msg, ref bool sendToOthers);
@@ -25,6 +24,22 @@ namespace Torch.Managers
internal void RaiseMessageRecieved(ChatMsg msg, ref bool sendToOthers) =>
MessageRecieved?.Invoke(msg, ref sendToOthers);
public void Init()
{
//HACK
MyMultiplayer.Static.ChatMessageReceived += Static_ChatMessageReceived;
_log.Warn("Chat intercept broken, chat hiding will not work!");
//NetworkManager.Instance.RegisterNetworkHandlers(new ChatIntercept());
}
private void Static_ChatMessageReceived(ulong arg1, string arg2, SteamSDK.ChatEntryTypeEnum arg3)
{
var msg = new ChatMsg {Author = arg1, Text = arg2};
var sendToOthers = true;
RaiseMessageRecieved(msg, ref sendToOthers);
}
}
internal class ChatIntercept : NetworkHandlerBase
@@ -53,11 +68,32 @@ namespace Torch.Managers
return _unitTestResult.Value;
}
public override bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj)
public override bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet)
{
/*
var msg = new ChatMsg();
base.Serialize(site.MethodInfo, stream, ref msg);
var msgLength = stream.ByteLength - stream.BytePosition - 8;
var bytes = new byte[msgLength];
stream.ReadBytes(bytes, stream.BytePosition, msgLength);
msg.Text = Encoding.Unicode.GetString(bytes);
msg.Author = stream.ReadUInt64();
var log = LogManager.GetLogger("HandleDebug");
var sb = new StringBuilder();
foreach (char c in msg.Text.ToCharArray())
{
sb.Append(Convert.ToString(c, 2).PadLeft(16, '0'));
}
log.Debug(sb.ToString());*/
//stream.ResetRead(packet);
//var msg = MySerializer.CreateAndRead<ChatMsg>(stream);
var msg = new ChatMsg();
Serialize(site.MethodInfo, stream, ref msg);
bool sendToOthers = true;
ChatManager.Instance.RaiseMessageRecieved(msg, ref sendToOthers);

View File

@@ -0,0 +1,349 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using NLog;
using Sandbox;
using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Entities;
using Sandbox.Game.Entities.Blocks;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.World;
using Sandbox.ModAPI;
using SpaceEngineers.Game.Entities.Blocks;
using SpaceEngineers.Game.ModAPI;
using Torch.API;
using VRage;
using VRage.Collections;
using VRage.Game;
using VRage.Game.Entity;
using VRage.Game.ModAPI;
using VRage.Groups;
using VRage.ModAPI;
using VRage.ObjectBuilders;
using VRage.Sync;
using VRageMath;
namespace Torch.Managers
{
public class EntityManager
{
private readonly ITorchBase _torch;
private static readonly Logger Log = LogManager.GetLogger(nameof(EntityManager));
public EntityManager(ITorchBase torch)
{
_torch = torch;
_torch.SessionLoaded += () => InitConcealment(60000);
}
public void ExportGrid(IMyCubeGrid grid, string path)
{
var ob = grid.GetObjectBuilder(true);
using (var f = File.Open(path, FileMode.CreateNew))
MyObjectBuilderSerializer.SerializeXML(f, ob);
}
public void ImportGrid(string path, Vector3D position)
{
MyObjectBuilder_EntityBase gridOb;
using (var f = File.OpenRead(path))
MyObjectBuilderSerializer.DeserializeXML(f, out gridOb);
var grid = MyEntities.CreateFromObjectBuilderParallel(gridOb);
grid.PositionComp.SetPosition(position);
MyEntities.Add(grid);
}
#region Concealment
private readonly List<ConcealGroup> _concealGroups = new List<ConcealGroup>();
private MyDynamicAABBTreeD _concealedAabbTree;
public void GetConcealedGrids(List<IMyCubeGrid> grids)
{
_concealGroups.SelectMany(x => x.Grids).ForEach(grids.Add);
}
private Timer _concealTimer;
private Timer _revealTimer;
private volatile bool _concealInProgress;
public void InitConcealment(double concealInterval)
{
Log.Info($"Initializing concealment to run every {concealInterval}ms");
_concealedAabbTree = new MyDynamicAABBTreeD(MyConstants.GAME_PRUNING_STRUCTURE_AABB_EXTENSION);
_concealTimer = new Timer(concealInterval);
_concealTimer.Elapsed += ConcealTimerElapsed;
_concealTimer.Start();
_revealTimer = new Timer(1000);
_revealTimer.Elapsed += RevealTimerElapsed;
_revealTimer.Start();
MySession.Static.Players.PlayerRequesting += RevealSpawns;
MyMultiplayer.Static.ClientJoined += RevealCryoPod;
}
private void RevealTimerElapsed(object sender, ElapsedEventArgs e)
{
_torch.Invoke(() => RevealNearbyGrids(MyMultiplayer.Static.ViewDistance));
}
private void RevealCryoPod(ulong steamId)
{
_torch.Invoke(() =>
{
Log.Debug(nameof(RevealCryoPod));
for (var i = _concealGroups.Count - 1; i >= 0; i--)
{
var group = _concealGroups[i];
if (group.IsCryoOccupied(steamId))
{
RevealGroup(group);
return;
}
}
});
}
private void RevealSpawns(PlayerRequestArgs args)
{
_torch.Invoke(() =>
{
Log.Debug(nameof(RevealSpawns));
var identityId = MySession.Static.Players.TryGetIdentityId(args.PlayerId.SteamId);
if (identityId == 0)
return;
for (var i = _concealGroups.Count - 1; i >= 0; i--)
{
var group = _concealGroups[i];
if (group.IsMedicalRoomAvailable(identityId))
RevealGroup(group);
}
});
}
private void ConcealTimerElapsed(object sender, ElapsedEventArgs e)
{
if (_concealInProgress)
{
Log.Warn($"Concealment taking longer to complete than the conceal interval of {_concealTimer.Interval}ms");
return;
}
_concealInProgress = true;
Log.Debug("Running concealment");
_torch.Invoke(() =>
{
if (MyAPIGateway.Session == null)
return;
var viewDistance = MyMultiplayer.Static.ViewDistance;
var concealDistance = viewDistance > 50000 ? viewDistance : 50000;
ConcealDistantGrids(concealDistance * 2);
_concealInProgress = false;
});
}
private void ConcealEntity(IMyEntity entity)
{
if (entity != entity.GetTopMostParent())
throw new InvalidOperationException("Can only conceal top-level entities.");
MyGamePruningStructure.Remove((MyEntity)entity);
entity.Physics?.Deactivate();
UnregisterRecursive(entity);
void UnregisterRecursive(IMyEntity e)
{
MyEntities.UnregisterForUpdate((MyEntity)e);
foreach (var child in e.Hierarchy.Children)
UnregisterRecursive(child.Entity);
}
}
private void RevealEntity(IMyEntity entity)
{
if (entity != entity.GetTopMostParent())
throw new InvalidOperationException("Can only conceal top-level entities.");
MyGamePruningStructure.Add((MyEntity)entity);
entity.Physics?.Activate();
RegisterRecursive(entity);
void RegisterRecursive(IMyEntity e)
{
MyEntities.RegisterForUpdate((MyEntity)e);
foreach (var child in e.Hierarchy.Children)
RegisterRecursive(child.Entity);
}
}
private bool ConcealGroup(ConcealGroup group)
{
if (_concealGroups.Any(g => g.Id == group.Id))
return false;
Log.Info($"Concealing grids: {string.Join(", ", group.Grids.Select(g => g.DisplayName))}");
group.ConcealTime = DateTime.Now;
group.Grids.ForEach(ConcealEntity);
Task.Run(() =>
{
group.UpdatePostConceal();
var aabb = group.WorldAABB;
group.ProxyId = _concealedAabbTree.AddProxy(ref aabb, group, 0);
Log.Debug($"Group {group.Id} cached");
_torch.Invoke(() => _concealGroups.Add(group));
});
return true;
}
private void RevealGroup(ConcealGroup group)
{
Log.Info($"Revealing grids: {string.Join(", ", group.Grids.Select(g => g.DisplayName))}");
group.Grids.ForEach(RevealEntity);
_concealGroups.Remove(group);
_concealedAabbTree.RemoveProxy(group.ProxyId);
}
private readonly List<ConcealGroup> _intersectGroups = new List<ConcealGroup>();
public void RevealGridsInSphere(BoundingSphereD sphere)
{
_concealedAabbTree.OverlapAllBoundingSphere(ref sphere, _intersectGroups);
foreach (var group in _intersectGroups)
{
RevealGroup(group);
}
_intersectGroups.Clear();
}
public void RevealNearbyGrids(double distanceFromPlayers)
{
var playerSpheres = GetPlayerBoundingSpheres(distanceFromPlayers);
foreach (var sphere in playerSpheres)
{
RevealGridsInSphere(sphere);
}
}
public void ConcealDistantGrids(double distanceFromPlayers)
{
var playerSpheres = GetPlayerBoundingSpheres(distanceFromPlayers);
foreach (var group in MyCubeGridGroups.Static.Physical.Groups)
{
var volume = group.GetWorldAABB();
if (playerSpheres.Any(s => s.Contains(volume) != ContainmentType.Disjoint))
continue;
ConcealGroup(new ConcealGroup(group));
}
}
private List<BoundingSphereD> GetPlayerBoundingSpheres(double distance)
{
return ((MyPlayerCollection)MyAPIGateway.Multiplayer.Players).GetOnlinePlayers()
.Where(p => p.Controller?.ControlledEntity != null)
.Select(p => new BoundingSphereD(p.Controller.ControlledEntity.Entity.PositionComp.GetPosition(), distance)).ToList();
}
#endregion Concealment
}
public static class GroupExtensions
{
public static BoundingBoxD GetWorldAABB(this MyGroups<MyCubeGrid, MyGridPhysicalGroupData>.Group group)
{
var grids = group.Nodes.Select(n => n.NodeData);
var startPos = grids.First().PositionComp.GetPosition();
var box = new BoundingBoxD(startPos, startPos);
foreach (var aabb in grids.Select(g => g.PositionComp.WorldAABB))
box.Include(aabb);
return box;
}
}
internal class ConcealGroup
{
/// <summary>
/// Entity ID of the first grid in the group.
/// </summary>
public long Id { get; }
public DateTime ConcealTime { get; set; }
public BoundingBoxD WorldAABB { get; private set; }
public List<MyCubeGrid> Grids { get; }
public List<MyMedicalRoom> MedicalRooms { get; } = new List<MyMedicalRoom>();
public List<MyCryoChamber> CryoChambers { get; } = new List<MyCryoChamber>();
internal volatile int ProxyId = -1;
public ConcealGroup(MyGroups<MyCubeGrid, MyGridPhysicalGroupData>.Group group)
{
Grids = group.Nodes.Select(n => n.NodeData).ToList();
Id = Grids.First().EntityId;
}
public void UpdatePostConceal()
{
UpdateAABB();
CacheSpawns();
}
private void UpdateAABB()
{
var startPos = Grids.First().PositionComp.GetPosition();
var box = new BoundingBoxD(startPos, startPos);
foreach (var aabb in Grids.Select(g => g.PositionComp.WorldAABB))
box.Include(aabb);
WorldAABB = box;
}
private void CacheSpawns()
{
MedicalRooms.Clear();
CryoChambers.Clear();
foreach (var block in Grids.SelectMany(x => x.GetFatBlocks()))
{
if (block is MyMedicalRoom medical)
MedicalRooms.Add(medical);
else if (block is MyCryoChamber cryo)
CryoChambers.Add(cryo);
}
}
public bool IsMedicalRoomAvailable(long identityId)
{
foreach (var room in MedicalRooms)
{
if (room.HasPlayerAccess(identityId) && room.IsWorking)
return true;
}
return false;
}
public bool IsCryoOccupied(ulong steamId)
{
var currentIdField = typeof(MyCryoChamber).GetField("m_currentPlayerId", BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var chamber in CryoChambers)
{
var value = (MyPlayer.PlayerId?)currentIdField.GetValue(chamber);
if (value?.SteamId == steamId)
return true;
}
return false;
}
}
}

View File

@@ -18,6 +18,7 @@ using Sandbox;
using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.World;
using Sandbox.ModAPI;
using SteamSDK;
using Torch.API;
using Torch.ViewModels;
@@ -34,21 +35,30 @@ namespace Torch.Managers
/// </summary>
public class MultiplayerManager : IMultiplayer
{
public event Action<IPlayer> PlayerJoined;
public event Action<IPlayer> PlayerLeft;
public event Action<IChatItem> MessageReceived;
public event Action<ulong> PlayerJoined;
public event Action<ulong, ConnectionState> PlayerLeft;
public event MessageReceivedDel MessageReceived;
public List<IChatItem> Chat { get; } = new List<IChatItem>();
public Dictionary<ulong, IPlayer> Players { get; } = new Dictionary<ulong, IPlayer>();
public Player LocalPlayer { get; private set; }
public MTObservableCollection<IChatMessage> ChatHistory { get; } = new MTObservableCollection<IChatMessage>();
public Dictionary<ulong, IMyPlayer> Players { get; } = new Dictionary<ulong, IMyPlayer>();
public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer;
private readonly ITorchBase _torch;
private static Logger _log = LogManager.GetLogger(nameof(MultiplayerManager));
private static Logger _chatLog = LogManager.GetLogger("Chat");
private Dictionary<MyPlayer.PlayerId, MyPlayer> _onlinePlayers;
internal MultiplayerManager(ITorchBase torch)
{
_torch = torch;
_torch.SessionLoaded += OnSessionLoaded;
ChatManager.Instance.MessageRecieved += Instance_MessageRecieved;
}
private void Instance_MessageRecieved(ChatMsg msg, ref bool sendToOthers)
{
var message = ChatMessage.FromChatMsg(msg);
ChatHistory.Add(message);
MessageReceived?.Invoke(message, ref sendToOthers);
}
public void KickPlayer(ulong steamId) => _torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
@@ -74,6 +84,11 @@ namespace Torch.Managers
return p;
}
public string GetSteamUsername(ulong steamId)
{
return MyMultiplayer.Static.GetMemberName(steamId);
}
/// <summary>
/// Send a message in chat.
/// </summary>
@@ -85,12 +100,8 @@ namespace Torch.Managers
private void OnSessionLoaded()
{
LocalPlayer = new Player(MyMultiplayer.Static.ServerId) { Name = "Server", State = ConnectionState.Connected };
MyMultiplayer.Static.ChatMessageReceived += OnChatMessage;
MyMultiplayer.Static.ClientKicked += OnClientKicked;
MyMultiplayer.Static.ClientLeft += OnClientLeft;
MySession.Static.Players.PlayerRequesting += OnPlayerRequesting;
_onlinePlayers = MySession.Static.Players.GetPrivateField<Dictionary<MyPlayer.PlayerId, MyPlayer>>("m_players");
@@ -102,40 +113,6 @@ namespace Torch.Managers
_waitingForGroup = (HashSet<ulong>)typeof(MyDedicatedServerBase).GetField("m_waitingForGroup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
}
private void OnChatMessage(ulong steamId, string message, ChatEntryTypeEnum chatType)
{
var player = MyMultiplayer.Static.GetMemberName(steamId);
if (string.IsNullOrEmpty(player))
return;
var info = new ChatItem(new Player(steamId), message);
Chat.Add(info);
MessageReceived?.Invoke(info);
}
/// <summary>
/// Invoked when a client logs in and hits the respawn screen.
/// </summary>
private void OnPlayerRequesting(PlayerRequestArgs args)
{
var steamId = args.PlayerId.SteamId;
IPlayer player;
if (!Players.ContainsKey(steamId))
{
player = new Player(steamId) { State = ConnectionState.Connected };
Players.Add(steamId, player);
}
else
{
player = Players[steamId];
player.SetConnectionState(ConnectionState.Connected);
}
_log.Info($"{player.Name} connected.");
PlayerJoined?.Invoke(player);
}
private void OnClientKicked(ulong steamId)
{
OnClientLeft(steamId, ChatMemberStateChangeEnum.Kicked);
@@ -143,13 +120,8 @@ namespace Torch.Managers
private void OnClientLeft(ulong steamId, ChatMemberStateChangeEnum stateChange)
{
if (!Players.ContainsKey(steamId))
return;
var player = Players[steamId];
_log.Info($"{player.Name} disconnected ({(ConnectionState)stateChange}).");
player.SetConnectionState((ConnectionState)stateChange);
PlayerLeft?.Invoke(player);
_log.Info($"{GetSteamUsername(steamId)} disconnected ({(ConnectionState)stateChange}).");
PlayerLeft?.Invoke(steamId, (ConnectionState)stateChange);
}
//TODO: Split the following into a new file?
@@ -299,8 +271,8 @@ namespace Torch.Managers
private void UserAccepted(ulong steamId)
{
//TODO: Raise user joined event here
typeof(MyDedicatedServerBase).GetMethod("UserAccepted", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId});
PlayerJoined?.Invoke(steamId);
}
private void UserRejected(ulong steamId, JoinResult reason)

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VRage;
using VRage.Library.Collections;
using VRage.Network;
using VRage.Serialization;
@@ -27,7 +28,7 @@ namespace Torch.Managers
/// <param name="stream"></param>
/// <param name="obj"></param>
/// <returns></returns>
public abstract bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj);
public abstract bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
/// <summary>
/// Extracts method arguments from the bitstream or packs them back in, depending on stream read mode.

View File

@@ -14,29 +14,15 @@ namespace Torch.Managers
{
public class NetworkManager
{
private NetworkManager()
{
try
{
if (ReflectionUnitTest())
InitNetworkIntercept();
}
catch (Exception ex)
{
_log.Error("Error initializing network intercept");
_log.Error(ex);
}
}
private static Logger _log = LogManager.GetLogger(nameof(NetworkManager));
private static NetworkManager _instance;
public static NetworkManager Instance => _instance ?? (_instance = new NetworkManager());
public static NetworkManager Instance { get; } = new NetworkManager();
private const string MyTransportLayerField = "TransportLayer";
private const string TypeTableField = "m_typeTable";
private const string TransportHandlersField = "m_handlers";
private MyTypeTable m_typeTable = new MyTypeTable();
private HashSet<NetworkHandlerBase> _networkHandlers = new HashSet<NetworkHandlerBase>();
private bool _init;
private bool ReflectionUnitTest(bool suppress = false)
{
@@ -70,8 +56,16 @@ namespace Torch.Managers
/// <summary>
/// Loads the network intercept system
/// </summary>
public void InitNetworkIntercept()
public void Init()
{
if (_init)
return;
_init = true;
if (!ReflectionUnitTest())
throw new InvalidOperationException("Reflection unit test failed.");
m_typeTable = typeof(MyReplicationLayerBase).GetField(TypeTableField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(MyMultiplayer.ReplicationLayer) as MyTypeTable;
//don't bother with nullchecks here, it was all handled in ReflectionUnitTest
var transportType = typeof(MySyncLayer).GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance).FieldType;
@@ -163,7 +157,7 @@ namespace Torch.Managers
try
{
if (handler.CanHandle(site))
discard |= handler.Handle(packet.Sender.Value, site, stream, obj);
discard |= handler.Handle(packet.Sender.Value, site, stream, obj, packet);
}
catch (Exception ex)
{

View File

@@ -21,14 +21,13 @@ using VRage.Library.Collections;
namespace Torch.Managers
{
public class PluginManager : IPluginManager
public class PluginManager : IPluginManager, IPlugin
{
private readonly ITorchBase _torch;
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
public const string PluginDir = "Plugins";
private readonly List<ITorchPlugin> _plugins = new List<ITorchPlugin>();
private readonly PluginUpdater _updater;
public CommandManager Commands { get; private set; }
public float LastUpdateMs => _lastUpdateMs;
@@ -37,7 +36,6 @@ namespace Torch.Managers
public PluginManager(ITorchBase torch)
{
_torch = torch;
_updater = new PluginUpdater(this);
if (!Directory.Exists(PluginDir))
Directory.CreateDirectory(PluginDir);
@@ -46,7 +44,7 @@ namespace Torch.Managers
}
/// <summary>
/// Adds the plugin updater plugin to VRage's plugin system.
/// Adds the plugin manager "plugin" to VRage's plugin system.
/// </summary>
private void InitUpdater()
{
@@ -55,7 +53,7 @@ namespace Torch.Managers
if (pluginList == null)
throw new TypeLoadException($"{fieldName} field not found in {nameof(MyPlugins)}");
pluginList.Add(_updater);
pluginList.Add(this);
}
/// <summary>
@@ -85,7 +83,8 @@ namespace Torch.Managers
/// </summary>
public void Init()
{
var network = NetworkManager.Instance;
((TorchBase)_torch).Network.Init();
ChatManager.Instance.Init();
Commands = new CommandManager(_torch);
_log.Info("Loading plugins");
@@ -93,8 +92,7 @@ namespace Torch.Managers
var dlls = Directory.GetFiles(pluginsPath, "*.dll", SearchOption.AllDirectories);
foreach (var dllPath in dlls)
{
UnblockDll(dllPath);
var asm = Assembly.LoadFrom(dllPath);
var asm = Assembly.UnsafeLoadFrom(dllPath);
foreach (var type in asm.GetExportedTypes())
{
@@ -134,45 +132,19 @@ namespace Torch.Managers
return GetEnumerator();
}
/// <summary>
/// Removes the lock on a DLL downloaded from the internet.
/// </summary>
/// <returns></returns>
public bool UnblockDll(string fileName)
void IPlugin.Init(object obj)
{
return DeleteFile(fileName + ":Zone.Identifier");
Init();
}
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteFile(string name);
/// <summary>
/// Tiny "plugin" to call <see cref="PluginManager"/>'s update method after each game tick.
/// </summary>
private class PluginUpdater : IPlugin
void IPlugin.Update()
{
private readonly IPluginManager _manager;
public PluginUpdater(IPluginManager manager)
{
_manager = manager;
}
public void Init(object obj)
{
_manager.Init();
}
public void Update()
{
_manager.UpdatePlugins();
UpdatePlugins();
}
public void Dispose()
{
_manager.DisposePlugins();
}
DisposePlugins();
}
}
}

View File

@@ -1,55 +0,0 @@
using System;
using System.Collections.Generic;
using Sandbox.Engine.Multiplayer;
using Torch.API;
namespace Torch
{
/// <summary>
/// Stores player information in an observable format.
/// </summary>
public class Player : ViewModel, IPlayer
{
private ulong _steamId;
private string _name;
private ConnectionState _state;
public ulong SteamId
{
get { return _steamId; }
set { _steamId = value; OnPropertyChanged(); }
}
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
//TODO: track identity history
public List<ulong> IdentityIds { get; } = new List<ulong>();
public DateTime LastConnected { get; private set; }
public ConnectionState State
{
get { return _state; }
set { _state = value; OnPropertyChanged(); }
}
public Player(ulong steamId)
{
_steamId = steamId;
_name = MyMultiplayer.Static.GetMemberName(steamId);
_state = ConnectionState.Unknown;
}
public void SetConnectionState(ConnectionState state)
{
if (state == ConnectionState.Connected)
LastConnected = DateTime.Now;
State = state;
}
}
}

View File

@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32;
using NLog;
using Sandbox;
using Sandbox.Engine.Networking;
@@ -19,9 +22,13 @@ namespace Torch
private static CancellationTokenSource _tokenSource = new CancellationTokenSource();
private static CancellationToken _cancelToken;
private static Logger _log = LogManager.GetLogger(nameof(SteamHelper));
public static string BasePath { get; private set; }
private static string _libraryFolders;
public static void Init()
{
BasePath = Registry.GetValue(@"HKEY_CURRENT_USER\Software\Valve\Steam", "SteamPath", null) as string;
_libraryFolders = File.ReadAllText(Path.Combine(BasePath, @"steamapps\libraryfolders.vdf"));
_cancelToken = _tokenSource.Token;
Task.Run(() =>
@@ -106,5 +113,20 @@ namespace Torch
{
return new MyObjectBuilder_Checkpoint.ModItem(null, details.PublishedFileId, details.Title);
}
public static string GetInstallFolder(string subfolderName)
{
var basePaths = new List<string>();
var matches = Regex.Matches(_libraryFolders, @"""\d+""[ \t]+""([^""]+)""", RegexOptions.Singleline);
foreach (Match match in matches)
{
basePaths.Add(match.Groups[1].Value);
}
var path = basePaths.Select(p => Path.Combine(p, "SteamApps", "common", subfolderName)).FirstOrDefault(Directory.Exists);
if (path != null && !path.EndsWith("\\"))
path += "\\";
return path;
}
}
}

View File

@@ -145,6 +145,7 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="ChatMessage.cs" />
<Compile Include="Collections\KeyTree.cs" />
<Compile Include="Commands\CategoryAttribute.cs" />
<Compile Include="Commands\Command.cs" />
@@ -153,22 +154,20 @@
<Compile Include="Commands\CommandContext.cs" />
<Compile Include="Commands\CommandManager.cs" />
<Compile Include="Commands\CommandTree.cs" />
<Compile Include="Commands\DefaultPermissionAttribute.cs" />
<Compile Include="Commands\Permissions\PermissionAttribute.cs" />
<Compile Include="Commands\Permissions\PermissonsSystem.cs" />
<Compile Include="Commands\TorchCommands.cs" />
<Compile Include="Managers\ChatManager.cs" />
<Compile Include="Managers\EntityManager.cs" />
<Compile Include="Managers\NetworkManager\NetworkHandlerBase.cs" />
<Compile Include="Managers\NetworkManager\NetworkManager.cs" />
<Compile Include="Managers\MultiplayerManager.cs" />
<Compile Include="Reflection.cs" />
<Compile Include="Managers\ScriptingManager.cs" />
<Compile Include="TorchBase.cs" />
<Compile Include="Player.cs" />
<Compile Include="Collections\PlayerInfoCache.cs" />
<Compile Include="SteamService.cs" />
<Compile Include="TorchConfig.cs" />
<Compile Include="TorchPluginBase.cs" />
<Compile Include="ViewModels\ChatItem.cs" />
<Compile Include="ViewModels\ModViewModel.cs" />
<Compile Include="Collections\MTObservableCollection.cs" />
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />

View File

@@ -17,6 +17,7 @@ using Sandbox.ModAPI;
using SpaceEngineers.Game;
using Torch.API;
using Torch.Managers;
using VRage.FileSystem;
using VRage.Scripting;
using VRage.Utils;
@@ -35,6 +36,8 @@ namespace Torch
public string[] RunArgs { get; set; }
public IPluginManager Plugins { get; protected set; }
public IMultiplayer Multiplayer { get; protected set; }
public EntityManager Entities { get; protected set; }
public NetworkManager Network { get; protected set; }
public event Action SessionLoading;
public event Action SessionLoaded;
public event Action SessionUnloading;
@@ -53,6 +56,13 @@ namespace Torch
RunArgs = new string[0];
Plugins = new PluginManager(this);
Multiplayer = new MultiplayerManager(this);
Entities = new EntityManager(this);
Network = NetworkManager.Instance;
}
public bool IsOnGameThread()
{
return Thread.CurrentThread.ManagedThreadId == MySandboxGame.Static.UpdateThread.ManagedThreadId;
}
public async Task SaveGameAsync()

69
Torch/TorchConfig.cs Normal file
View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using NLog;
namespace Torch
{
public class TorchConfig
{
private static Logger _log = LogManager.GetLogger("Config");
public string InstancePath { get; set; }
public string InstanceName { get; set; }
public int Autosave { get; set; }
public bool AutoRestart { get; set; }
public bool LogChat { get; set; }
public TorchConfig() : this("Torch") { }
public TorchConfig(string instanceName = "Torch", string instancePath = null, int autosaveInterval = 5, bool autoRestart = false)
{
InstanceName = instanceName;
InstancePath = instancePath ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Torch", InstanceName);
Autosave = autosaveInterval;
AutoRestart = autoRestart;
}
public static TorchConfig LoadFrom(string path)
{
try
{
var serializer = new XmlSerializer(typeof(TorchConfig));
TorchConfig config;
using (var f = File.OpenRead(path))
{
config = (TorchConfig)serializer.Deserialize(f);
}
return config;
}
catch (Exception e)
{
_log.Error(e);
return null;
}
}
public bool SaveTo(string path)
{
try
{
var serializer = new XmlSerializer(typeof(TorchConfig));
using (var f = File.OpenWrite(path))
{
serializer.Serialize(f, this);
}
return true;
}
catch (Exception e)
{
_log.Error(e);
return false;
}
}
}
}

View File

@@ -1,43 +0,0 @@
using System;
using Torch.API;
namespace Torch.ViewModels
{
public class ChatItem : ViewModel, IChatItem
{
private IPlayer _sender;
private string _message;
private DateTime _timestamp;
public IPlayer Player
{
get { return _sender; }
set { _sender = value; OnPropertyChanged(); }
}
public string Message
{
get { return _message; }
set { _message = value; OnPropertyChanged(); }
}
public DateTime Time
{
get { return _timestamp; }
set { _timestamp = value; OnPropertyChanged(); }
}
public string TimeString => Time.ToShortTimeString();
public ChatItem(IPlayer sender, string message, DateTime timestamp = default(DateTime))
{
_sender = sender;
_message = message;
if (timestamp == default(DateTime))
_timestamp = DateTime.Now;
else
_timestamp = timestamp;
}
}
}