Client manager components
- ReflectedManager now supports MemberInfo and event replacement - New chat manager interfaces for both client and server - New multiplayer manager interfaces for both client and server
This commit is contained in:
@@ -44,10 +44,6 @@ namespace Torch.API
|
||||
/// </summary>
|
||||
ITorchConfig Config { get; }
|
||||
|
||||
/// <inheritdoc cref="IMultiplayerManager"/>
|
||||
[Obsolete]
|
||||
IMultiplayerManager Multiplayer { get; }
|
||||
|
||||
/// <inheritdoc cref="IPluginManager"/>
|
||||
[Obsolete]
|
||||
IPluginManager Plugins { get; }
|
||||
@@ -55,6 +51,12 @@ namespace Torch.API
|
||||
/// <inheritdoc cref="IDependencyManager"/>
|
||||
IDependencyManager Managers { get; }
|
||||
|
||||
[Obsolete("Prefer using Managers.GetManager for global managers")]
|
||||
T GetManager<T>() where T : class, IManager;
|
||||
|
||||
[Obsolete("Prefer using Managers.AddManager for global managers")]
|
||||
bool AddManager<T>(T manager) where T : class, IManager;
|
||||
|
||||
/// <summary>
|
||||
/// The binary version of the current instance.
|
||||
/// </summary>
|
||||
|
74
Torch.API/Managers/IChatManagerClient.cs
Normal file
74
Torch.API/Managers/IChatManagerClient.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VRage.Network;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a scripted or user chat message.
|
||||
/// </summary>
|
||||
public struct TorchChatMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// The author's steam ID, if available. Else, null.
|
||||
/// </summary>
|
||||
public ulong? AuthorSteamId;
|
||||
/// <summary>
|
||||
/// The author's name, if available. Else, null.
|
||||
/// </summary>
|
||||
public string Author;
|
||||
/// <summary>
|
||||
/// The message contents.
|
||||
/// </summary>
|
||||
public string Message;
|
||||
/// <summary>
|
||||
/// The font, or null if default.
|
||||
/// </summary>
|
||||
public string Font;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback used to indicate that a messaage has been recieved.
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
|
||||
public delegate void DelMessageRecieved(TorchChatMessage msg, ref bool consumed);
|
||||
|
||||
/// <summary>
|
||||
/// Callback used to indicate the user is attempting to send a message locally.
|
||||
/// </summary>
|
||||
/// <param name="msg">Message the user is attempting to send</param>
|
||||
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
|
||||
public delegate void DelMessageSending(string msg, ref bool consumed);
|
||||
|
||||
public interface IChatManagerClient : IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Event that is raised when a message addressed to us is recieved. <see cref="DelMessageRecieved"/>
|
||||
/// </summary>
|
||||
event DelMessageRecieved MessageRecieved;
|
||||
|
||||
/// <summary>
|
||||
/// Event that is raised when we are attempting to send a message. <see cref="DelMessageSending"/>
|
||||
/// </summary>
|
||||
event DelMessageSending MessageSending;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the <see cref="MessageSending"/> event,
|
||||
/// typically raised by the user entering text into the chat window.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to send</param>
|
||||
void SendMessageAsSelf(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Displays a message on the UI given an author name and a message.
|
||||
/// </summary>
|
||||
/// <param name="author">Author name</param>
|
||||
/// <param name="message">Message content</param>
|
||||
/// <param name="font">font to use</param>
|
||||
void DisplayMessageOnSelf(string author, string message, string font = "Blue" );
|
||||
}
|
||||
}
|
45
Torch.API/Managers/IChatManagerServer.cs
Normal file
45
Torch.API/Managers/IChatManagerServer.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using VRage.Network;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Callback used to indicate the server has recieved a message to process.
|
||||
/// </summary>
|
||||
/// <param name="authorId">Steam ID of the user sending a message</param>
|
||||
/// <param name="msg">Message the user is attempting to send</param>
|
||||
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
|
||||
public delegate void DelMessageProcessing(TorchChatMessage msg, ref bool consumed);
|
||||
|
||||
public interface IChatManagerServer : IChatManagerClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Event triggered when the server has recieved a message and should process it. <see cref="DelMessageProcessing"/>
|
||||
/// </summary>
|
||||
event DelMessageProcessing MessageProcessing;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message with the given author and message to the given player, or all players by default.
|
||||
/// </summary>
|
||||
/// <param name="authorId">Author's steam ID</param>
|
||||
/// <param name="message">The message to send</param>
|
||||
/// <param name="targetSteamId">Player to send the message to, or everyone by default</param>
|
||||
void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends a scripted message with the given author and message to the given player, or all players by default.
|
||||
/// </summary>
|
||||
/// <param name="author">Author name</param>
|
||||
/// <param name="message">The message to send</param>
|
||||
/// <param name="font">Font to use</param>
|
||||
/// <param name="targetSteamId">Player to send the message to, or everyone by default</param>
|
||||
void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0);
|
||||
}
|
||||
}
|
@@ -16,7 +16,7 @@ namespace Torch.API.Managers
|
||||
/// <summary>
|
||||
/// API for multiplayer related functions.
|
||||
/// </summary>
|
||||
public interface IMultiplayerManager : IManager
|
||||
public interface IMultiplayerManagerBase : IManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired when a player joins.
|
||||
@@ -28,26 +28,6 @@ namespace Torch.API.Managers
|
||||
/// </summary>
|
||||
event Action<IPlayer> PlayerLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a chat message is received.
|
||||
/// </summary>
|
||||
event MessageReceivedDel MessageReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat message to all or one specific player.
|
||||
/// </summary>
|
||||
void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Blue);
|
||||
|
||||
/// <summary>
|
||||
/// Kicks the player from the game.
|
||||
/// </summary>
|
||||
void KickPlayer(ulong steamId);
|
||||
|
||||
/// <summary>
|
||||
/// Bans or unbans a player from the game.
|
||||
/// </summary>
|
||||
void BanPlayer(ulong steamId, bool banned = true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a player by their Steam64 ID or returns null if the player isn't found.
|
||||
/// </summary>
|
||||
@@ -57,5 +37,12 @@ namespace Torch.API.Managers
|
||||
/// Gets a player by their display name or returns null if the player isn't found.
|
||||
/// </summary>
|
||||
IMyPlayer GetPlayerByName(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the steam username of a member's steam ID
|
||||
/// </summary>
|
||||
/// <param name="steamId">steam ID</param>
|
||||
/// <returns>steam username</returns>
|
||||
string GetSteamUsername(ulong steamId);
|
||||
}
|
||||
}
|
12
Torch.API/Managers/IMultiplayerManagerClient.cs
Normal file
12
Torch.API/Managers/IMultiplayerManagerClient.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
public interface IMultiplayerManagerClient : IMultiplayerManagerBase
|
||||
{
|
||||
}
|
||||
}
|
21
Torch.API/Managers/IMultiplayerManagerServer.cs
Normal file
21
Torch.API/Managers/IMultiplayerManagerServer.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Torch.API.Managers
|
||||
{
|
||||
public interface IMultiplayerManagerServer : IMultiplayerManagerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Kicks the player from the game.
|
||||
/// </summary>
|
||||
void KickPlayer(ulong steamId);
|
||||
|
||||
/// <summary>
|
||||
/// Bans or unbans a player from the game.
|
||||
/// </summary>
|
||||
void BanPlayer(ulong steamId, bool banned = true);
|
||||
}
|
||||
}
|
@@ -18,6 +18,12 @@ namespace Torch.API.Managers
|
||||
/// Register a network handler.
|
||||
/// </summary>
|
||||
void RegisterNetworkHandler(INetworkHandler handler);
|
||||
|
||||
/// <summary>
|
||||
/// Unregister a network handler.
|
||||
/// </summary>
|
||||
/// <returns>true if the handler was unregistered, false if it wasn't registered to begin with</returns>
|
||||
bool UnregisterNetworkHandler(INetworkHandler handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -33,6 +39,7 @@ namespace Torch.API.Managers
|
||||
/// <summary>
|
||||
/// Processes a network message.
|
||||
/// </summary>
|
||||
/// <returns>true if the message should be discarded</returns>
|
||||
bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
|
||||
}
|
||||
}
|
||||
|
@@ -164,11 +164,15 @@
|
||||
<Compile Include="ITorchConfig.cs" />
|
||||
<Compile Include="Managers\DependencyManagerExtensions.cs" />
|
||||
<Compile Include="Managers\DependencyProviderExtensions.cs" />
|
||||
<Compile Include="Managers\IChatManagerClient.cs" />
|
||||
<Compile Include="Managers\IChatManagerServer.cs" />
|
||||
<Compile Include="Managers\IDependencyManager.cs" />
|
||||
<Compile Include="Managers\IDependencyProvider.cs" />
|
||||
<Compile Include="Managers\IManager.cs" />
|
||||
<Compile Include="Managers\IMultiplayerManager.cs" />
|
||||
<Compile Include="Managers\IMultiplayerManagerClient.cs" />
|
||||
<Compile Include="Managers\IMultiplayerManagerBase.cs" />
|
||||
<Compile Include="IPlayer.cs" />
|
||||
<Compile Include="Managers\IMultiplayerManagerServer.cs" />
|
||||
<Compile Include="Managers\INetworkManager.cs" />
|
||||
<Compile Include="Managers\IPluginManager.cs" />
|
||||
<Compile Include="Plugins\ITorchPlugin.cs" />
|
||||
|
@@ -29,6 +29,10 @@ namespace Torch.Client.Tests
|
||||
|
||||
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
||||
|
||||
public static IEnumerable<object[]> MemberInfo => Manager().MemberInfo;
|
||||
|
||||
public static IEnumerable<object[]> Events => Manager().Events;
|
||||
|
||||
#region Binding
|
||||
[Theory]
|
||||
[MemberData(nameof(Getters))]
|
||||
@@ -62,6 +66,28 @@ namespace Torch.Client.Tests
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MemberInfo))]
|
||||
public void TestBindingMemberInfo(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Events))]
|
||||
public void TestBindingEvents(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
32
Torch.Client/Manager/MultiplayerManagerClient.cs
Normal file
32
Torch.Client/Manager/MultiplayerManagerClient.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Managers;
|
||||
|
||||
namespace Torch.Client.Manager
|
||||
{
|
||||
public class MultiplayerManagerClient : MultiplayerManagerBase, IMultiplayerManagerClient
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public MultiplayerManagerClient(ITorchBase torch) : base(torch) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Attach()
|
||||
{
|
||||
base.Attach();
|
||||
MyMultiplayer.Static.ClientJoined += RaiseClientJoined;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Detach()
|
||||
{
|
||||
MyMultiplayer.Static.ClientJoined -= RaiseClientJoined;
|
||||
base.Detach();
|
||||
}
|
||||
}
|
||||
}
|
38
Torch.Client/Manager/MultiplayerManagerLobby.cs
Normal file
38
Torch.Client/Manager/MultiplayerManagerLobby.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Managers;
|
||||
|
||||
namespace Torch.Client.Manager
|
||||
{
|
||||
public class MultiplayerManagerLobby : MultiplayerManagerBase, IMultiplayerManagerServer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public MultiplayerManagerLobby(ITorchBase torch) : base(torch) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void BanPlayer(ulong steamId, bool banned = true) => Torch.Invoke(() => MyMultiplayer.Static.BanClient(steamId, banned));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Attach()
|
||||
{
|
||||
base.Attach();
|
||||
MyMultiplayer.Static.ClientJoined += RaiseClientJoined;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Detach()
|
||||
{
|
||||
MyMultiplayer.Static.ClientJoined -= RaiseClientJoined;
|
||||
base.Detach();
|
||||
}
|
||||
}
|
||||
}
|
@@ -121,6 +121,8 @@
|
||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||
<Link>Properties\AssemblyVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Manager\MultiplayerManagerClient.cs" />
|
||||
<Compile Include="Manager\MultiplayerManagerLobby.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TorchClient.cs" />
|
||||
<Compile Include="TorchClientConfig.cs" />
|
||||
@@ -167,6 +169,7 @@
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||
<PropertyGroup>
|
||||
|
@@ -4,12 +4,17 @@ using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Engine.Platform;
|
||||
using Sandbox.Game;
|
||||
using SpaceEngineers.Game;
|
||||
using VRage.Steam;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.Session;
|
||||
using Torch.Client.Manager;
|
||||
using Torch.Session;
|
||||
using VRage;
|
||||
using VRage.FileSystem;
|
||||
using VRage.GameServices;
|
||||
@@ -27,6 +32,13 @@ namespace Torch.Client
|
||||
public TorchClient()
|
||||
{
|
||||
Config = new TorchClientConfig();
|
||||
var sessionManager = Managers.GetManager<ITorchSessionManager>();
|
||||
sessionManager.AddFactory((x) => MyMultiplayer.Static is MyMultiplayerLobby
|
||||
? new MultiplayerManagerLobby(this)
|
||||
: null);
|
||||
sessionManager.AddFactory((x) => MyMultiplayer.Static is MyMultiplayerClientBase
|
||||
? new MultiplayerManagerClient(this)
|
||||
: null);
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
|
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Sandbox.Game.Gui;
|
||||
using Sandbox.Graphics;
|
||||
using Sandbox.Graphics.GUI;
|
||||
using Sandbox.Gui;
|
||||
@@ -16,6 +17,15 @@ namespace Torch.Client
|
||||
{
|
||||
public class TorchMainMenuScreen : MyGuiScreenMainMenu
|
||||
{
|
||||
public TorchMainMenuScreen()
|
||||
: this(false)
|
||||
{
|
||||
}
|
||||
|
||||
public TorchMainMenuScreen(bool pauseGame)
|
||||
: base(pauseGame)
|
||||
{
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override void RecreateControls(bool constructor)
|
||||
{
|
||||
|
@@ -28,6 +28,10 @@ namespace Torch.Server.Tests
|
||||
|
||||
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
||||
|
||||
public static IEnumerable<object[]> MemberInfo => Manager().MemberInfo;
|
||||
|
||||
public static IEnumerable<object[]> Events => Manager().Events;
|
||||
|
||||
#region Binding
|
||||
[Theory]
|
||||
[MemberData(nameof(Getters))]
|
||||
@@ -61,6 +65,17 @@ namespace Torch.Server.Tests
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Events))]
|
||||
public void TestBindingEvents(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
166
Torch.Server/Managers/MultiplayerManagerDedicated.cs
Normal file
166
Torch.Server/Managers/MultiplayerManagerDedicated.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NLog.Fluent;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Managers;
|
||||
using Torch.Utils;
|
||||
using Torch.ViewModels;
|
||||
using VRage.GameServices;
|
||||
using VRage.Network;
|
||||
|
||||
namespace Torch.Server.Managers
|
||||
{
|
||||
public class MultiplayerManagerDedicated : MultiplayerManagerBase, IMultiplayerManagerServer
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
#pragma warning disable 649
|
||||
[ReflectedGetter(Name = "m_members")]
|
||||
private static Func<MyDedicatedServerBase, List<ulong>> _members;
|
||||
[ReflectedGetter(Name = "m_waitingForGroup")]
|
||||
private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup;
|
||||
#pragma warning restore 649
|
||||
|
||||
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public MultiplayerManagerDedicated(ITorchBase torch) : base(torch) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void BanPlayer(ulong steamId, bool banned = true)
|
||||
{
|
||||
Torch.Invoke(() =>
|
||||
{
|
||||
MyMultiplayer.Static.BanClient(steamId, banned);
|
||||
if (_gameOwnerIds.ContainsKey(steamId))
|
||||
MyMultiplayer.Static.BanClient(_gameOwnerIds[steamId], banned);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Attach()
|
||||
{
|
||||
base.Attach();
|
||||
_gameServerValidateAuthTicketReplacer = _gameServerValidateAuthTicketFactory.Invoke();
|
||||
_gameServerUserGroupStatusReplacer = _gameServerUserGroupStatusFactory.Invoke();
|
||||
_gameServerValidateAuthTicketReplacer.Replace(new Action<ulong, JoinResult, ulong>(ValidateAuthTicketResponse), MyGameService.GameServer);
|
||||
_gameServerUserGroupStatusReplacer.Replace(new Action<ulong, ulong, bool, bool>(UserGroupStatusResponse), MyGameService.GameServer);
|
||||
_log.Info("Inserted steam authentication intercept");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Detach()
|
||||
{
|
||||
if (_gameServerValidateAuthTicketReplacer != null && _gameServerValidateAuthTicketReplacer.Replaced)
|
||||
_gameServerValidateAuthTicketReplacer.Restore(MyGameService.GameServer);
|
||||
if (_gameServerUserGroupStatusReplacer != null && _gameServerUserGroupStatusReplacer.Replaced)
|
||||
_gameServerUserGroupStatusReplacer.Restore(MyGameService.GameServer);
|
||||
_log.Info("Removed steam authentication intercept");
|
||||
base.Detach();
|
||||
}
|
||||
|
||||
|
||||
#pragma warning disable 649
|
||||
[ReflectedEventReplace(typeof(IMyGameServer), nameof(IMyGameServer.ValidateAuthTicketResponse), typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
|
||||
private static Func<ReflectedEventReplacer> _gameServerValidateAuthTicketFactory;
|
||||
[ReflectedEventReplace(typeof(IMyGameServer), nameof(IMyGameServer.UserGroupStatusResponse), typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
|
||||
private static Func<ReflectedEventReplacer> _gameServerUserGroupStatusFactory;
|
||||
private ReflectedEventReplacer _gameServerValidateAuthTicketReplacer;
|
||||
private ReflectedEventReplacer _gameServerUserGroupStatusReplacer;
|
||||
#pragma warning restore 649
|
||||
|
||||
#region CustomAuth
|
||||
#pragma warning disable 649
|
||||
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase), Name = "ConvertSteamIDFrom64")]
|
||||
private static Func<ulong, string> _convertSteamIDFrom64;
|
||||
|
||||
[ReflectedStaticMethod(Type = typeof(MyGameService), Name = "GetServerAccountType")]
|
||||
private static Func<ulong, MyGameServiceAccountType> _getServerAccountType;
|
||||
|
||||
[ReflectedMethod(Name = "UserAccepted")]
|
||||
private static Action<MyDedicatedServerBase, ulong> _userAcceptedImpl;
|
||||
|
||||
[ReflectedMethod(Name = "UserRejected")]
|
||||
private static Action<MyDedicatedServerBase, ulong, JoinResult> _userRejected;
|
||||
[ReflectedMethod(Name = "IsClientBanned")]
|
||||
private static Func<MyMultiplayerBase, ulong, bool> _isClientBanned;
|
||||
[ReflectedMethod(Name = "IsClientKicked")]
|
||||
private static Func<MyMultiplayerBase, ulong, bool> _isClientKicked;
|
||||
[ReflectedMethod(Name = "RaiseClientKicked")]
|
||||
private static Action<MyMultiplayerBase, ulong> _raiseClientKicked;
|
||||
#pragma warning restore 649
|
||||
|
||||
//Largely copied from SE
|
||||
private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner)
|
||||
{
|
||||
_log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}");
|
||||
if (_isClientBanned.Invoke(MyMultiplayer.Static, steamOwner) || MySandboxGame.ConfigDedicated.Banned.Contains(steamOwner))
|
||||
{
|
||||
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins);
|
||||
_raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||
}
|
||||
else if (_isClientKicked.Invoke(MyMultiplayer.Static, steamOwner))
|
||||
{
|
||||
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently);
|
||||
_raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||
}
|
||||
if (response != JoinResult.OK)
|
||||
{
|
||||
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response);
|
||||
return;
|
||||
}
|
||||
if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit)
|
||||
{
|
||||
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.ServerFull);
|
||||
return;
|
||||
}
|
||||
if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
|
||||
MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) ||
|
||||
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(steamID)))
|
||||
{
|
||||
this.UserAccepted(steamID);
|
||||
return;
|
||||
}
|
||||
if (_getServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
|
||||
{
|
||||
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid);
|
||||
return;
|
||||
}
|
||||
if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
|
||||
{
|
||||
_waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID);
|
||||
return;
|
||||
}
|
||||
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline);
|
||||
}
|
||||
|
||||
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
|
||||
{
|
||||
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId))
|
||||
{
|
||||
if (member || officer)
|
||||
UserAccepted(userId);
|
||||
else
|
||||
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup);
|
||||
}
|
||||
}
|
||||
private void UserAccepted(ulong steamId)
|
||||
{
|
||||
_userAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId);
|
||||
base.RaiseClientJoined(steamId);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -191,6 +191,7 @@
|
||||
<Link>Properties\AssemblyVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="ListBoxExtensions.cs" />
|
||||
<Compile Include="Managers\MultiplayerManagerDedicated.cs" />
|
||||
<Compile Include="Managers\InstanceManager.cs" />
|
||||
<Compile Include="NativeMethods.cs" />
|
||||
<Compile Include="Initializer.cs" />
|
||||
|
@@ -17,6 +17,8 @@ using Sandbox.Game.Multiplayer;
|
||||
using Sandbox.ModAPI;
|
||||
using SteamSDK;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.Session;
|
||||
using Torch.Managers;
|
||||
using Torch.Server.Managers;
|
||||
using Torch.Utils;
|
||||
@@ -63,6 +65,9 @@ namespace Torch.Server
|
||||
AddManager(DedicatedInstance);
|
||||
Config = config ?? new TorchConfig();
|
||||
MyFakes.ENABLE_INFINARIO = false;
|
||||
|
||||
var sessionManager = Managers.GetManager<ITorchSessionManager>();
|
||||
sessionManager.AddFactory((x) => new MultiplayerManagerDedicated(this));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -253,19 +258,20 @@ namespace Torch.Server
|
||||
{
|
||||
case SaveGameStatus.Success:
|
||||
Log.Info("Save completed.");
|
||||
Multiplayer.SendMessage("Saved game.", playerId: callerId);
|
||||
// TODO
|
||||
// Multiplayer.SendMessage("Saved game.", playerId: callerId);
|
||||
break;
|
||||
case SaveGameStatus.SaveInProgress:
|
||||
Log.Error("Save failed, a save is already in progress.");
|
||||
Multiplayer.SendMessage("Save failed, a save is already in progress.", playerId: callerId, font: MyFontEnum.Red);
|
||||
// Multiplayer.SendMessage("Save failed, a save is already in progress.", playerId: callerId, font: MyFontEnum.Red);
|
||||
break;
|
||||
case SaveGameStatus.GameNotReady:
|
||||
Log.Error("Save failed, game was not ready.");
|
||||
Multiplayer.SendMessage("Save failed, game was not ready.", playerId: callerId, font: MyFontEnum.Red);
|
||||
// Multiplayer.SendMessage("Save failed, game was not ready.", playerId: callerId, font: MyFontEnum.Red);
|
||||
break;
|
||||
case SaveGameStatus.TimedOut:
|
||||
Log.Error("Save failed, save timed out.");
|
||||
Multiplayer.SendMessage("Save failed, save timed out.", playerId: callerId, font: MyFontEnum.Red);
|
||||
// Multiplayer.SendMessage("Save failed, save timed out.", playerId: callerId, font: MyFontEnum.Red);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@@ -20,7 +20,9 @@ using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Game.World;
|
||||
using SteamSDK;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Managers;
|
||||
using Torch.Server.Managers;
|
||||
|
||||
namespace Torch.Server
|
||||
{
|
||||
@@ -30,7 +32,6 @@ namespace Torch.Server
|
||||
public partial class ChatControl : UserControl
|
||||
{
|
||||
private TorchBase _server;
|
||||
private MultiplayerManager _multiplayer;
|
||||
|
||||
public ChatControl()
|
||||
{
|
||||
@@ -40,11 +41,15 @@ namespace Torch.Server
|
||||
public void BindServer(ITorchServer server)
|
||||
{
|
||||
_server = (TorchBase)server;
|
||||
_multiplayer = (MultiplayerManager)server.Multiplayer;
|
||||
ChatItems.Items.Clear();
|
||||
DataContext = _multiplayer;
|
||||
if (_multiplayer.ChatHistory is INotifyCollectionChanged ncc)
|
||||
ncc.CollectionChanged += ChatHistory_CollectionChanged;
|
||||
server.SessionLoaded += () =>
|
||||
{
|
||||
var multiplayer = server.CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>();
|
||||
DataContext = multiplayer;
|
||||
// TODO
|
||||
// if (multiplayer.ChatHistory is INotifyCollectionChanged ncc)
|
||||
// ncc.CollectionChanged += ChatHistory_CollectionChanged;
|
||||
};
|
||||
}
|
||||
|
||||
private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
@@ -78,10 +83,10 @@ namespace Torch.Server
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
var commands = _server.Commands;
|
||||
if (commands.IsCommand(text))
|
||||
var commands = _server.CurrentSession?.Managers.GetManager<Torch.Commands.CommandManager>();
|
||||
if (commands != null && commands.IsCommand(text))
|
||||
{
|
||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text));
|
||||
// TODO _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text));
|
||||
_server.Invoke(() =>
|
||||
{
|
||||
var response = commands.HandleCommandFromServer(text);
|
||||
@@ -90,15 +95,15 @@ namespace Torch.Server
|
||||
}
|
||||
else
|
||||
{
|
||||
_server.Multiplayer.SendMessage(text);
|
||||
_server.CurrentSession?.Managers.GetManager<IChatManagerClient>().SendMessageAsSelf(text);
|
||||
}
|
||||
Message.Text = "";
|
||||
}
|
||||
|
||||
private void OnMessageEntered_Callback(string response)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(response))
|
||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
|
||||
//if (!string.IsNullOrEmpty(response))
|
||||
// TODO _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,7 +20,9 @@ using Sandbox.Game.World;
|
||||
using Sandbox.ModAPI;
|
||||
using SteamSDK;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Managers;
|
||||
using Torch.Server.Managers;
|
||||
using Torch.ViewModels;
|
||||
using VRage.Game.ModAPI;
|
||||
|
||||
@@ -41,19 +43,23 @@ namespace Torch.Server
|
||||
public void BindServer(ITorchServer server)
|
||||
{
|
||||
_server = server;
|
||||
DataContext = (MultiplayerManager)_server.Multiplayer;
|
||||
server.SessionLoaded += () =>
|
||||
{
|
||||
var multiplayer = server.CurrentSession?.Managers.GetManager<MultiplayerManagerDedicated>();
|
||||
DataContext = multiplayer;
|
||||
};
|
||||
}
|
||||
|
||||
private void KickButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
||||
_server.Multiplayer.KickPlayer(player.Key);
|
||||
_server.CurrentSession?.Managers.GetManager<IMultiplayerManagerServer>()?.KickPlayer(player.Key);
|
||||
}
|
||||
|
||||
private void BanButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var player = (KeyValuePair<ulong, PlayerViewModel>) PlayerList.SelectedItem;
|
||||
_server.Multiplayer.BanPlayer(player.Key);
|
||||
_server.CurrentSession?.Managers.GetManager<IMultiplayerManagerServer>()?.BanPlayer(player.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Torch.Utils;
|
||||
using Xunit;
|
||||
|
||||
@@ -19,6 +20,10 @@ namespace Torch.Tests
|
||||
|
||||
public static IEnumerable<object[]> Invokers => _manager.Invokers;
|
||||
|
||||
public static IEnumerable<object[]> MemberInfo => _manager.MemberInfo;
|
||||
|
||||
public static IEnumerable<object[]> Events => _manager.Events;
|
||||
|
||||
#region Binding
|
||||
[Theory]
|
||||
[MemberData(nameof(Getters))]
|
||||
@@ -52,6 +57,28 @@ namespace Torch.Tests
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MemberInfo))]
|
||||
public void TestBindingMemberInfo(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Events))]
|
||||
public void TestBindingEvents(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Results
|
||||
@@ -79,10 +106,51 @@ namespace Torch.Tests
|
||||
{
|
||||
return k >= 0;
|
||||
}
|
||||
|
||||
public event Action Event1;
|
||||
|
||||
public ReflectionTestTarget()
|
||||
{
|
||||
Event1 += Callback1;
|
||||
}
|
||||
|
||||
public bool Callback1Flag = false;
|
||||
public void Callback1()
|
||||
{
|
||||
Callback1Flag = true;
|
||||
}
|
||||
public bool Callback2Flag = false;
|
||||
public void Callback2()
|
||||
{
|
||||
Callback2Flag = true;
|
||||
}
|
||||
|
||||
public void RaiseEvent()
|
||||
{
|
||||
Event1?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private class ReflectionTestBinding
|
||||
{
|
||||
#region Instance
|
||||
#region MemberInfo
|
||||
[ReflectedFieldInfo(typeof(ReflectionTestTarget), "TestField")]
|
||||
public static FieldInfo TestFieldInfo;
|
||||
|
||||
[ReflectedPropertyInfo(typeof(ReflectionTestTarget), "TestProperty")]
|
||||
public static PropertyInfo TestPropertyInfo;
|
||||
|
||||
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall")]
|
||||
public static MethodInfo TestMethodInfoGeneral;
|
||||
|
||||
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall", Parameters = new[] { typeof(int) })]
|
||||
public static MethodInfo TestMethodInfoExplicitArgs;
|
||||
|
||||
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall", ReturnType = typeof(bool))]
|
||||
public static MethodInfo TestMethodInfoExplicitReturn;
|
||||
#endregion
|
||||
|
||||
[ReflectedGetter(Name = "TestField")]
|
||||
public static Func<ReflectionTestTarget, int> TestFieldGetter;
|
||||
[ReflectedSetter(Name = "TestField")]
|
||||
@@ -96,7 +164,27 @@ namespace Torch.Tests
|
||||
[ReflectedMethod]
|
||||
public static Func<ReflectionTestTarget, int, bool> TestCall;
|
||||
|
||||
[ReflectedEventReplace(typeof(ReflectionTestTarget), "Event1", typeof(ReflectionTestTarget), "Callback1")]
|
||||
public static Func<ReflectedEventReplacer> TestEventReplacer;
|
||||
#endregion
|
||||
|
||||
#region Static
|
||||
#region MemberInfo
|
||||
[ReflectedFieldInfo(typeof(ReflectionTestTarget), "TestFieldStatic")]
|
||||
public static FieldInfo TestStaticFieldInfo;
|
||||
|
||||
[ReflectedPropertyInfo(typeof(ReflectionTestTarget), "TestPropertyStatic")]
|
||||
public static PropertyInfo TestStaticPropertyInfo;
|
||||
|
||||
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic")]
|
||||
public static MethodInfo TestStaticMethodInfoGeneral;
|
||||
|
||||
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic", Parameters = new[] { typeof(int) })]
|
||||
public static MethodInfo TestStaticMethodInfoExplicitArgs;
|
||||
|
||||
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic", ReturnType = typeof(bool))]
|
||||
public static MethodInfo TestStaticMethodInfoExplicitReturn;
|
||||
#endregion
|
||||
[ReflectedGetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
|
||||
public static Func<int> TestStaticFieldGetter;
|
||||
[ReflectedSetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
|
||||
@@ -109,6 +197,7 @@ namespace Torch.Tests
|
||||
|
||||
[ReflectedStaticMethod(Type = typeof(ReflectionTestTarget))]
|
||||
public static Func<int, bool> TestCallStatic;
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -215,6 +304,32 @@ namespace Torch.Tests
|
||||
Assert.True(ReflectionTestBinding.TestCallStatic.Invoke(1));
|
||||
Assert.False(ReflectionTestBinding.TestCallStatic.Invoke(-1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestInstanceEventReplace()
|
||||
{
|
||||
var target = new ReflectionTestTarget();
|
||||
target.Callback1Flag = false;
|
||||
target.RaiseEvent();
|
||||
Assert.True(target.Callback1Flag, "Control test failed");
|
||||
|
||||
target.Callback1Flag = false;
|
||||
target.Callback2Flag = false;
|
||||
ReflectedEventReplacer binder = ReflectionTestBinding.TestEventReplacer.Invoke();
|
||||
Assert.True(binder.Test(target), "Binder was unable to find the requested method");
|
||||
|
||||
binder.Replace(new Action(() => target.Callback2()), target);
|
||||
target.RaiseEvent();
|
||||
Assert.True(target.Callback2Flag, "Substitute callback wasn't called");
|
||||
Assert.False(target.Callback1Flag, "Original callback wasn't removed");
|
||||
|
||||
target.Callback1Flag = false;
|
||||
target.Callback2Flag = false;
|
||||
binder.Restore(target);
|
||||
target.RaiseEvent();
|
||||
Assert.False(target.Callback2Flag, "Substitute callback wasn't removed");
|
||||
Assert.True(target.Callback1Flag, "Original callback wasn't restored");
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
|
@@ -28,12 +28,16 @@ namespace Torch.Tests
|
||||
private readonly HashSet<object[]> _getters = new HashSet<object[]>();
|
||||
private readonly HashSet<object[]> _setters = new HashSet<object[]>();
|
||||
private readonly HashSet<object[]> _invokers = new HashSet<object[]>();
|
||||
private readonly HashSet<object[]> _memberInfo = new HashSet<object[]>();
|
||||
private readonly HashSet<object[]> _events = new HashSet<object[]>();
|
||||
|
||||
public ReflectionTestManager()
|
||||
{
|
||||
_getters.Add(new object[] { new FieldRef(null) });
|
||||
_setters.Add(new object[] { new FieldRef(null) });
|
||||
_invokers.Add(new object[] { new FieldRef(null) });
|
||||
_memberInfo.Add(new object[] {new FieldRef(null)});
|
||||
_events.Add(new object[] {new FieldRef(null)});
|
||||
}
|
||||
|
||||
public ReflectionTestManager Init(Assembly asm)
|
||||
@@ -50,12 +54,36 @@ namespace Torch.Tests
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic))
|
||||
{
|
||||
if (field.GetCustomAttribute<ReflectedMethodAttribute>() != null)
|
||||
_invokers.Add(new object[] { new FieldRef(field) });
|
||||
if (field.GetCustomAttribute<ReflectedGetterAttribute>() != null)
|
||||
_getters.Add(new object[] { new FieldRef(field) });
|
||||
if (field.GetCustomAttribute<ReflectedSetterAttribute>() != null)
|
||||
_setters.Add(new object[] { new FieldRef(field) });
|
||||
var args = new object[] { new FieldRef(field) };
|
||||
foreach (ReflectedMemberAttribute attr in field.GetCustomAttributes<ReflectedMemberAttribute>())
|
||||
{
|
||||
if (!field.IsStatic)
|
||||
throw new ArgumentException("Field must be static to be reflected");
|
||||
switch (attr)
|
||||
{
|
||||
case ReflectedMethodAttribute rma:
|
||||
_invokers.Add(args);
|
||||
break;
|
||||
case ReflectedGetterAttribute rga:
|
||||
_getters.Add(args);
|
||||
break;
|
||||
case ReflectedSetterAttribute rsa:
|
||||
_setters.Add(args);
|
||||
break;
|
||||
case ReflectedFieldInfoAttribute rfia:
|
||||
case ReflectedPropertyInfoAttribute rpia:
|
||||
case ReflectedMethodInfoAttribute rmia:
|
||||
_memberInfo.Add(args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var reflectedEventReplacer = field.GetCustomAttribute<ReflectedEventReplaceAttribute>();
|
||||
if (reflectedEventReplacer != null)
|
||||
{
|
||||
if (!field.IsStatic)
|
||||
throw new ArgumentException("Field must be static to be reflected");
|
||||
_events.Add(args);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -66,6 +94,10 @@ namespace Torch.Tests
|
||||
|
||||
public IEnumerable<object[]> Invokers => _invokers;
|
||||
|
||||
public IEnumerable<object[]> MemberInfo => _memberInfo;
|
||||
|
||||
public IEnumerable<object[]> Events => _events;
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
@@ -27,6 +27,10 @@ namespace Torch.Tests
|
||||
|
||||
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
||||
|
||||
public static IEnumerable<object[]> MemberInfo => Manager().MemberInfo;
|
||||
|
||||
public static IEnumerable<object[]> Events => Manager().Events;
|
||||
|
||||
#region Binding
|
||||
[Theory]
|
||||
[MemberData(nameof(Getters))]
|
||||
@@ -60,6 +64,28 @@ namespace Torch.Tests
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MemberInfo))]
|
||||
public void TestBindingMemberInfo(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Events))]
|
||||
public void TestBindingEvents(ReflectionTestManager.FieldRef field)
|
||||
{
|
||||
if (field.Field == null)
|
||||
return;
|
||||
Assert.True(ReflectedManager.Process(field.Field));
|
||||
if (field.Field.IsStatic)
|
||||
Assert.NotNull(field.Field.GetValue(null));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.Plugins;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
@@ -47,7 +48,7 @@ namespace Torch.Commands
|
||||
Response = message;
|
||||
|
||||
if (Player != null)
|
||||
Torch.Multiplayer.SendMessage(message, sender, Player.IdentityId, font);
|
||||
Torch.CurrentSession.Managers.GetManager<IChatManagerServer>()?.SendMessageAsOther(sender, message, font, Player.SteamUserId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,6 +9,7 @@ using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.Plugins;
|
||||
using Torch.Managers;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
using VRage.Network;
|
||||
|
||||
@@ -21,7 +22,7 @@ namespace Torch.Commands
|
||||
public CommandTree Commands { get; set; } = new CommandTree();
|
||||
private Logger _log = LogManager.GetLogger(nameof(CommandManager));
|
||||
[Dependency]
|
||||
private ChatManager _chatManager;
|
||||
private IChatManagerServer _chatManager;
|
||||
|
||||
public CommandManager(ITorchBase torch, char prefix = '!') : base(torch)
|
||||
{
|
||||
@@ -31,7 +32,7 @@ namespace Torch.Commands
|
||||
public override void Attach()
|
||||
{
|
||||
RegisterCommandModule(typeof(TorchCommands));
|
||||
_chatManager.MessageRecieved += HandleCommand;
|
||||
_chatManager.MessageProcessing += HandleCommand;
|
||||
}
|
||||
|
||||
public bool HasPermission(ulong steamId, Command command)
|
||||
@@ -93,20 +94,21 @@ namespace Torch.Commands
|
||||
return context.Response;
|
||||
}
|
||||
|
||||
public void HandleCommand(ChatMsg msg, ref bool sendToOthers)
|
||||
public void HandleCommand(TorchChatMessage msg, ref bool consumed)
|
||||
{
|
||||
HandleCommand(msg.Text, msg.Author, ref sendToOthers);
|
||||
if (msg.AuthorSteamId.HasValue)
|
||||
HandleCommand(msg.Message, msg.AuthorSteamId.Value, ref consumed);
|
||||
}
|
||||
|
||||
public void HandleCommand(string message, ulong steamId, ref bool sendToOthers, bool serverConsole = false)
|
||||
public void HandleCommand(string message, ulong steamId, ref bool consumed, bool serverConsole = false)
|
||||
{
|
||||
|
||||
if (message.Length < 1 || message[0] != Prefix)
|
||||
return;
|
||||
|
||||
sendToOthers = false;
|
||||
consumed = true;
|
||||
|
||||
var player = Torch.Multiplayer.GetPlayerBySteamId(steamId);
|
||||
var player = Torch.GetManager<IMultiplayerManagerBase>().GetPlayerBySteamId(steamId);
|
||||
if (player == null)
|
||||
{
|
||||
_log.Error($"Command {message} invoked by nonexistant player");
|
||||
@@ -123,7 +125,7 @@ namespace Torch.Commands
|
||||
if (!HasPermission(steamId, command))
|
||||
{
|
||||
_log.Info($"{player.DisplayName} tried to use command {cmdPath} without permission");
|
||||
Torch.Multiplayer.SendMessage($"You need to be a {command.MinimumPromoteLevel} or higher to use that command.", playerId: player.IdentityId);
|
||||
_chatManager.SendMessageAsOther("Server", $"You need to be a {command.MinimumPromoteLevel} or higher to use that command.", MyFontEnum.Red, steamId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,7 @@ namespace Torch.Commands
|
||||
[Permission(MyPromoteLevel.None)]
|
||||
public void Help()
|
||||
{
|
||||
var commandManager = ((TorchBase)Context.Torch).Commands;
|
||||
var commandManager = ((TorchBase)Context.Torch).CurrentSession.Managers.GetManager<CommandManager>();
|
||||
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
|
||||
|
||||
if (node != null)
|
||||
@@ -128,13 +128,15 @@ namespace Torch.Commands
|
||||
{
|
||||
if (i >= 60 && i % 60 == 0)
|
||||
{
|
||||
Context.Torch.Multiplayer.SendMessage($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
|
||||
yield return null;
|
||||
// TODO
|
||||
// Context.Torch.Multiplayer.SendMessage($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
|
||||
// yield return null;
|
||||
}
|
||||
else if (i > 0)
|
||||
{
|
||||
if (i < 11)
|
||||
Context.Torch.Multiplayer.SendMessage($"Restarting server in {i} second{Pluralize(i)}.");
|
||||
// TODO
|
||||
// if (i < 11)
|
||||
// Context.Torch.Multiplayer.SendMessage($"Restarting server in {i} second{Pluralize(i)}.");
|
||||
yield return null;
|
||||
}
|
||||
else
|
||||
|
@@ -1,104 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using VRage;
|
||||
using VRage.Library.Collections;
|
||||
using VRage.Network;
|
||||
using VRage.Serialization;
|
||||
using VRage.Utils;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
[Manager]
|
||||
public class ChatManager : Manager
|
||||
{
|
||||
private static Logger _log = LogManager.GetLogger(nameof(ChatManager));
|
||||
|
||||
public delegate void MessageRecievedDel(ChatMsg msg, ref bool sendToOthers);
|
||||
|
||||
public event MessageRecievedDel MessageRecieved;
|
||||
|
||||
internal void RaiseMessageRecieved(ChatMsg msg, ref bool sendToOthers) =>
|
||||
MessageRecieved?.Invoke(msg, ref sendToOthers);
|
||||
|
||||
[Dependency]
|
||||
private INetworkManager _networkManager;
|
||||
|
||||
public ChatManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Attach()
|
||||
{
|
||||
try
|
||||
{
|
||||
_networkManager.RegisterNetworkHandler(new ChatIntercept(this));
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Error("Failed to initialize network intercept, command hiding will not work! Falling back to another method.");
|
||||
MyMultiplayer.Static.ChatMessageReceived += Static_ChatMessageReceived;
|
||||
}
|
||||
}
|
||||
|
||||
private void Static_ChatMessageReceived(ulong arg1, string arg2)
|
||||
{
|
||||
var msg = new ChatMsg {Author = arg1, Text = arg2};
|
||||
var sendToOthers = true;
|
||||
|
||||
RaiseMessageRecieved(msg, ref sendToOthers);
|
||||
}
|
||||
|
||||
internal class ChatIntercept : NetworkHandlerBase, INetworkHandler
|
||||
{
|
||||
private ChatManager _chatManager;
|
||||
private bool? _unitTestResult;
|
||||
|
||||
public ChatIntercept(ChatManager chatManager)
|
||||
{
|
||||
_chatManager = chatManager;
|
||||
}
|
||||
|
||||
public override bool CanHandle(CallSite site)
|
||||
{
|
||||
if (site.MethodInfo.Name != "OnChatMessageRecieved")
|
||||
return false;
|
||||
|
||||
if (_unitTestResult.HasValue)
|
||||
return _unitTestResult.Value;
|
||||
|
||||
var parameters = site.MethodInfo.GetParameters();
|
||||
if (parameters.Length != 1)
|
||||
{
|
||||
_unitTestResult = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parameters[0].ParameterType != typeof(ChatMsg))
|
||||
_unitTestResult = false;
|
||||
|
||||
_unitTestResult = true;
|
||||
|
||||
return _unitTestResult.Value;
|
||||
}
|
||||
|
||||
public override bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet)
|
||||
{
|
||||
var msg = new ChatMsg();
|
||||
Serialize(site.MethodInfo, stream, ref msg);
|
||||
|
||||
bool sendToOthers = true;
|
||||
_chatManager.RaiseMessageRecieved(msg, ref sendToOthers);
|
||||
|
||||
return !sendToOthers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
144
Torch/Managers/ChatManager/ChatManagerClient.cs
Normal file
144
Torch/Managers/ChatManager/ChatManagerClient.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Game.Entities.Character;
|
||||
using Sandbox.Game.Gui;
|
||||
using Sandbox.Game.World;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Utils;
|
||||
using VRage.Game;
|
||||
using Game = Sandbox.Engine.Platform.Game;
|
||||
|
||||
namespace Torch.Managers.ChatManager
|
||||
{
|
||||
public class ChatManagerClient : Manager, IChatManagerClient
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ChatManagerClient(ITorchBase torchInstance) : base(torchInstance) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
// TODO doesn't work in Offline worlds. Method injection or network injection.
|
||||
public event DelMessageRecieved MessageRecieved;
|
||||
|
||||
/// <inheritdoc />
|
||||
// TODO doesn't work at all. Method injection or network injection.
|
||||
public event DelMessageSending MessageSending;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendMessageAsSelf(string message)
|
||||
{
|
||||
if (MyMultiplayer.Static != null)
|
||||
{
|
||||
if (Game.IsDedicated)
|
||||
{
|
||||
var scripted = new ScriptedChatMsg()
|
||||
{
|
||||
Author = "Server",
|
||||
Font = MyFontEnum.Red,
|
||||
Text = message,
|
||||
Target = 0
|
||||
};
|
||||
MyMultiplayerBase.SendScriptedChatMessage(ref scripted);
|
||||
}
|
||||
else
|
||||
MyMultiplayer.Static.SendChatMessage(message);
|
||||
}
|
||||
else if (MyHud.Chat != null)
|
||||
MyHud.Chat.ShowMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DisplayMessageOnSelf(string author, string message, string font)
|
||||
{
|
||||
MyHud.Chat.ShowMessage(author, message, font);
|
||||
MySession.Static.GlobalChatHistory.GlobalChatHistory.Chat.Enqueue(new MyGlobalChatItem()
|
||||
{
|
||||
Author = author,
|
||||
AuthorFont = font,
|
||||
Text = message
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Attach()
|
||||
{
|
||||
base.Attach();
|
||||
if (MyMultiplayer.Static == null)
|
||||
_log.Warn("Currently ChatManagerClient doesn't support handling on an offline client");
|
||||
else
|
||||
{
|
||||
_chatMessageRecievedReplacer = _chatMessageReceivedFactory.Invoke();
|
||||
_scriptedChatMessageRecievedReplacer = _scriptedChatMessageReceivedFactory.Invoke();
|
||||
_chatMessageRecievedReplacer.Replace(new Action<ulong, string>(Multiplayer_ChatMessageReceived),
|
||||
MyMultiplayer.Static);
|
||||
_scriptedChatMessageRecievedReplacer.Replace(
|
||||
new Action<string, string, string>(Multiplayer_ScriptedChatMessageReceived), MyMultiplayer.Static);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Detach()
|
||||
{
|
||||
if (_chatMessageRecievedReplacer != null && _chatMessageRecievedReplacer.Replaced)
|
||||
_chatMessageRecievedReplacer.Restore(MyHud.Chat);
|
||||
if (_scriptedChatMessageRecievedReplacer != null && _scriptedChatMessageRecievedReplacer.Replaced)
|
||||
_scriptedChatMessageRecievedReplacer.Restore(MyHud.Chat);
|
||||
base.Detach();
|
||||
}
|
||||
|
||||
private void Multiplayer_ChatMessageReceived(ulong steamUserId, string message)
|
||||
{
|
||||
var torchMsg = new TorchChatMessage()
|
||||
{
|
||||
AuthorSteamId = steamUserId,
|
||||
Author = Torch.CurrentSession.Managers.GetManager<IMultiplayerManagerBase>()
|
||||
?.GetSteamUsername(steamUserId),
|
||||
Font = (steamUserId == MyGameService.UserId) ? "DarkBlue" : "Blue",
|
||||
Message = message
|
||||
};
|
||||
var consumed = false;
|
||||
MessageRecieved?.Invoke(torchMsg, ref consumed);
|
||||
if (!consumed)
|
||||
_hudChatMessageReceived.Invoke(MyHud.Chat, steamUserId, message);
|
||||
}
|
||||
|
||||
private void Multiplayer_ScriptedChatMessageReceived(string author, string message, string font)
|
||||
{
|
||||
var torchMsg = new TorchChatMessage()
|
||||
{
|
||||
AuthorSteamId = null,
|
||||
Author = author,
|
||||
Font = font,
|
||||
Message = message
|
||||
};
|
||||
var consumed = false;
|
||||
MessageRecieved?.Invoke(torchMsg, ref consumed);
|
||||
if (!consumed)
|
||||
_hudChatScriptedMessageReceived.Invoke(MyHud.Chat, author, message, font);
|
||||
}
|
||||
|
||||
private const string _hudChatMessageReceivedName = "Multiplayer_ChatMessageReceived";
|
||||
private const string _hudChatScriptedMessageReceivedName = "multiplayer_ScriptedChatMessageReceived";
|
||||
|
||||
[ReflectedMethod(Name = _hudChatMessageReceivedName)]
|
||||
private static Action<MyHudChat, ulong, string> _hudChatMessageReceived;
|
||||
[ReflectedMethod(Name = _hudChatScriptedMessageReceivedName)]
|
||||
private static Action<MyHudChat, string, string, string> _hudChatScriptedMessageReceived;
|
||||
|
||||
[ReflectedEventReplace(typeof(MyMultiplayerBase), nameof(MyMultiplayerBase.ChatMessageReceived), typeof(MyHudChat), _hudChatMessageReceivedName)]
|
||||
private static Func<ReflectedEventReplacer> _chatMessageReceivedFactory;
|
||||
[ReflectedEventReplace(typeof(MyMultiplayerBase), nameof(MyMultiplayerBase.ScriptedChatMessageReceived), typeof(MyHudChat), _hudChatScriptedMessageReceivedName)]
|
||||
private static Func<ReflectedEventReplacer> _scriptedChatMessageReceivedFactory;
|
||||
|
||||
private ReflectedEventReplacer _chatMessageRecievedReplacer;
|
||||
private ReflectedEventReplacer _scriptedChatMessageRecievedReplacer;
|
||||
}
|
||||
}
|
193
Torch/Managers/ChatManager/ChatManagerServer.cs
Normal file
193
Torch/Managers/ChatManager/ChatManagerServer.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Game.Gui;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
using Sandbox.Game.World;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Utils;
|
||||
using VRage;
|
||||
using VRage.Library.Collections;
|
||||
using VRage.Network;
|
||||
|
||||
namespace Torch.Managers.ChatManager
|
||||
{
|
||||
public class ChatManagerServer : ChatManagerClient, IChatManagerServer
|
||||
{
|
||||
[Dependency(Optional = true)]
|
||||
private INetworkManager _networkManager;
|
||||
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly ChatIntercept _chatIntercept;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ChatManagerServer(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
_chatIntercept = new ChatIntercept(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event DelMessageProcessing MessageProcessing;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0)
|
||||
{
|
||||
if (MyMultiplayer.Static == null)
|
||||
{
|
||||
if (targetSteamId == MyGameService.UserId || targetSteamId == 0)
|
||||
MyHud.Chat?.ShowMessage(authorId == MyGameService.UserId ?
|
||||
(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player") : $"user_{authorId}", message);
|
||||
return;
|
||||
}
|
||||
if (MyMultiplayer.Static is MyDedicatedServerBase dedicated)
|
||||
{
|
||||
var msg = new ChatMsg() { Author = authorId, Text = message };
|
||||
_dedicatedServerBaseSendChatMessage.Invoke(ref msg);
|
||||
_dedicatedServerBaseOnChatMessage.Invoke(dedicated, new object[] { msg });
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma warning disable 649
|
||||
private delegate void DelMultiplayerBaseSendChatMessage(ref ChatMsg arg);
|
||||
[ReflectedStaticMethod(Name = "SendChatMessage", Type = typeof(MyMultiplayerBase))]
|
||||
private static DelMultiplayerBaseSendChatMessage _dedicatedServerBaseSendChatMessage;
|
||||
|
||||
// [ReflectedMethod] doesn't play well with instance methods and refs.
|
||||
[ReflectedMethodInfo(typeof(MyDedicatedServerBase), "OnChatMessage")]
|
||||
private static MethodInfo _dedicatedServerBaseOnChatMessage;
|
||||
#pragma warning restore 649
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0)
|
||||
{
|
||||
if (MyMultiplayer.Static == null)
|
||||
{
|
||||
if (targetSteamId == MyGameService.UserId || targetSteamId == 0)
|
||||
MyHud.Chat?.ShowMessage(author, message, font);
|
||||
return;
|
||||
}
|
||||
var scripted = new ScriptedChatMsg()
|
||||
{
|
||||
Author = author,
|
||||
Text = message,
|
||||
Font = font,
|
||||
Target = (long)targetSteamId
|
||||
};
|
||||
MyMultiplayerBase.SendScriptedChatMessage(ref scripted);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Attach()
|
||||
{
|
||||
base.Attach();
|
||||
if (_networkManager != null)
|
||||
try
|
||||
{
|
||||
_networkManager.RegisterNetworkHandler(_chatIntercept);
|
||||
_log.Debug("Initialized network intercept for chat messages");
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Discard exception and use second method
|
||||
}
|
||||
|
||||
if (MyMultiplayer.Static != null)
|
||||
{
|
||||
MyMultiplayer.Static.ChatMessageReceived += MpStaticChatMessageReceived;
|
||||
_log.Warn("Failed to initialize network intercept, we can't discard chat messages! Falling back to another method.");
|
||||
}
|
||||
else
|
||||
_log.Error("Failed to initialize network intercept, we can't discard chat messages!");
|
||||
}
|
||||
|
||||
private void MpStaticChatMessageReceived(ulong a, string b)
|
||||
{
|
||||
var tmp = false;
|
||||
RaiseMessageRecieved(new ChatMsg()
|
||||
{
|
||||
Author = a,
|
||||
Text = b
|
||||
}, ref tmp);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Detach()
|
||||
{
|
||||
if (MyMultiplayer.Static != null)
|
||||
MyMultiplayer.Static.ChatMessageReceived -= MpStaticChatMessageReceived;
|
||||
_networkManager?.UnregisterNetworkHandler(_chatIntercept);
|
||||
base.Detach();
|
||||
}
|
||||
|
||||
internal void RaiseMessageRecieved(ChatMsg message, ref bool consumed)
|
||||
{
|
||||
var torchMsg = new TorchChatMessage()
|
||||
{
|
||||
AuthorSteamId = message.Author,
|
||||
Author = MyMultiplayer.Static?.GetMemberName(message.Author) ?? $"user_{message.Author}",
|
||||
Message = message.Text
|
||||
};
|
||||
MessageProcessing?.Invoke(torchMsg, ref consumed);
|
||||
}
|
||||
|
||||
internal class ChatIntercept : NetworkHandlerBase, INetworkHandler
|
||||
{
|
||||
private readonly ChatManagerServer _chatManager;
|
||||
private bool? _unitTestResult;
|
||||
|
||||
public ChatIntercept(ChatManagerServer chatManager)
|
||||
{
|
||||
_chatManager = chatManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanHandle(CallSite site)
|
||||
{
|
||||
if (site.MethodInfo.Name != "OnChatMessageRecieved")
|
||||
return false;
|
||||
|
||||
if (_unitTestResult.HasValue)
|
||||
return _unitTestResult.Value;
|
||||
|
||||
ParameterInfo[] parameters = site.MethodInfo.GetParameters();
|
||||
if (parameters.Length != 1)
|
||||
{
|
||||
_unitTestResult = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parameters[0].ParameterType != typeof(ChatMsg))
|
||||
_unitTestResult = false;
|
||||
|
||||
_unitTestResult = true;
|
||||
|
||||
return _unitTestResult.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet)
|
||||
{
|
||||
var msg = new ChatMsg();
|
||||
Serialize(site.MethodInfo, stream, ref msg);
|
||||
|
||||
var consumed = false;
|
||||
_chatManager.RaiseMessageRecieved(msg, ref consumed);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,338 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Threading;
|
||||
using NLog;
|
||||
using Torch;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Game.Entities.Character;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
using Sandbox.Game.World;
|
||||
using Sandbox.ModAPI;
|
||||
using SteamSDK;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Collections;
|
||||
using Torch.Commands;
|
||||
using Torch.Utils;
|
||||
using Torch.ViewModels;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
using VRage.GameServices;
|
||||
using VRage.Library.Collections;
|
||||
using VRage.Network;
|
||||
using VRage.Steam;
|
||||
using VRage.Utils;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class MultiplayerManager : Manager, IMultiplayerManager
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public event Action<IPlayer> PlayerJoined;
|
||||
/// <inheritdoc />
|
||||
public event Action<IPlayer> PlayerLeft;
|
||||
/// <inheritdoc />
|
||||
public event MessageReceivedDel MessageReceived;
|
||||
|
||||
public IList<IChatMessage> ChatHistory { get; } = new ObservableList<IChatMessage>();
|
||||
public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>();
|
||||
public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer;
|
||||
private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager));
|
||||
private static readonly Logger ChatLog = LogManager.GetLogger("Chat");
|
||||
|
||||
[ReflectedGetter(Name = "m_players")]
|
||||
private static Func<MyPlayerCollection, Dictionary<MyPlayer.PlayerId, MyPlayer>> _onlinePlayers;
|
||||
|
||||
[Dependency]
|
||||
private ChatManager _chatManager;
|
||||
[Dependency]
|
||||
private CommandManager _commandManager;
|
||||
[Dependency]
|
||||
private NetworkManager _networkManager;
|
||||
|
||||
internal MultiplayerManager(ITorchBase torch) : base(torch)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Attach()
|
||||
{
|
||||
Torch.SessionLoaded += OnSessionLoaded;
|
||||
_chatManager.MessageRecieved += Instance_MessageRecieved;
|
||||
}
|
||||
|
||||
private void Instance_MessageRecieved(ChatMsg msg, ref bool sendToOthers)
|
||||
{
|
||||
var message = ChatMessage.FromChatMsg(msg);
|
||||
ChatHistory.Add(message);
|
||||
ChatLog.Info($"{message.Name}: {message.Message}");
|
||||
MessageReceived?.Invoke(message, ref sendToOthers);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void BanPlayer(ulong steamId, bool banned = true)
|
||||
{
|
||||
Torch.Invoke(() =>
|
||||
{
|
||||
MyMultiplayer.Static.BanClient(steamId, banned);
|
||||
if (_gameOwnerIds.ContainsKey(steamId))
|
||||
MyMultiplayer.Static.BanClient(_gameOwnerIds[steamId], banned);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMyPlayer GetPlayerByName(string name)
|
||||
{
|
||||
return _onlinePlayers.Invoke(MySession.Static.Players).FirstOrDefault(x => x.Value.DisplayName == name).Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMyPlayer GetPlayerBySteamId(ulong steamId)
|
||||
{
|
||||
_onlinePlayers.Invoke(MySession.Static.Players).TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p);
|
||||
return p;
|
||||
}
|
||||
|
||||
public ulong GetSteamId(long identityId)
|
||||
{
|
||||
foreach (var kv in _onlinePlayers.Invoke(MySession.Static.Players))
|
||||
{
|
||||
if (kv.Value.Identity.IdentityId == identityId)
|
||||
return kv.Key.SteamId;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetSteamUsername(ulong steamId)
|
||||
{
|
||||
return MyMultiplayer.Static.GetMemberName(steamId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Red)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, message));
|
||||
if (_commandManager.IsCommand(message))
|
||||
{
|
||||
var response = _commandManager.HandleCommandFromServer(message);
|
||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, response));
|
||||
}
|
||||
else
|
||||
{
|
||||
var msg = new ScriptedChatMsg { Author = author, Font = font, Target = playerId, Text = message };
|
||||
MyMultiplayerBase.SendScriptedChatMessage(ref msg);
|
||||
var character = MySession.Static.Players.TryGetIdentity(playerId)?.Character;
|
||||
var steamId = GetSteamId(playerId);
|
||||
if (character == null)
|
||||
return;
|
||||
|
||||
var addToGlobalHistoryMethod = typeof(MyCharacter).GetMethod("OnGlobalMessageSuccess", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
_networkManager.RaiseEvent(addToGlobalHistoryMethod, character, steamId, steamId, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSessionLoaded()
|
||||
{
|
||||
Log.Info("Initializing Steam auth");
|
||||
MyMultiplayer.Static.ClientKicked += OnClientKicked;
|
||||
MyMultiplayer.Static.ClientLeft += OnClientLeft;
|
||||
|
||||
//TODO: Move these with the methods?
|
||||
if (!RemoveHandlers())
|
||||
{
|
||||
Log.Error("Steam auth failed to initialize");
|
||||
return;
|
||||
}
|
||||
MyGameService.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse;
|
||||
MyGameService.GameServer.UserGroupStatusResponse += UserGroupStatusResponse;
|
||||
Log.Info("Steam auth initialized");
|
||||
}
|
||||
|
||||
private void OnClientKicked(ulong steamId)
|
||||
{
|
||||
OnClientLeft(steamId, MyChatMemberStateChangeEnum.Kicked);
|
||||
}
|
||||
|
||||
private void OnClientLeft(ulong steamId, MyChatMemberStateChangeEnum stateChange)
|
||||
{
|
||||
Players.TryGetValue(steamId, out PlayerViewModel vm);
|
||||
if (vm == null)
|
||||
vm = new PlayerViewModel(steamId);
|
||||
Log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}.");
|
||||
PlayerLeft?.Invoke(vm);
|
||||
Players.Remove(steamId);
|
||||
}
|
||||
|
||||
//TODO: Split the following into a new file?
|
||||
//These methods override some Keen code to allow us full control over client authentication.
|
||||
//This lets us have a server set to private (admins only) or friends (friends of all listed admins)
|
||||
[ReflectedGetter(Name = "m_members")]
|
||||
private static Func<MyDedicatedServerBase, List<ulong>> _members;
|
||||
[ReflectedGetter(Name = "m_waitingForGroup")]
|
||||
private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup;
|
||||
[ReflectedGetter(Name = "m_kickedClients")]
|
||||
private static Func<MyMultiplayerBase, Dictionary<ulong, int>> _kickedClients;
|
||||
//private HashSet<ulong> _waitingForFriends;
|
||||
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
|
||||
//private IMultiplayer _multiplayerImplementation;
|
||||
|
||||
/// <summary>
|
||||
/// Removes Keen's hooks into some Steam events so we have full control over client authentication
|
||||
/// </summary>
|
||||
private static bool RemoveHandlers()
|
||||
{
|
||||
MethodInfo methodValidateAuthTicket = typeof(MyDedicatedServerBase).GetMethod("GameServer_ValidateAuthTicketResponse",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (methodValidateAuthTicket == null)
|
||||
{
|
||||
Log.Error("Unable to find the GameServer_ValidateAuthTicketResponse method to unhook");
|
||||
return false;
|
||||
}
|
||||
var eventValidateAuthTicket = Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.ValidateAuthTicketResponse))
|
||||
.FirstOrDefault(x => x.Method == methodValidateAuthTicket) as Action<ulong, JoinResult, ulong>;
|
||||
if (eventValidateAuthTicket == null)
|
||||
{
|
||||
Log.Error(
|
||||
"Unable to unhook the GameServer_ValidateAuthTicketResponse method from GameServer.ValidateAuthTicketResponse");
|
||||
Log.Debug(" Want to unhook {0}", methodValidateAuthTicket);
|
||||
Log.Debug(" Registered handlers: ");
|
||||
foreach (Delegate method in Reflection.GetInstanceEvent(MyGameService.GameServer,
|
||||
nameof(MyGameService.GameServer.ValidateAuthTicketResponse)))
|
||||
Log.Debug(" - " + method.Method);
|
||||
return false;
|
||||
}
|
||||
|
||||
MethodInfo methodUserGroupStatus = typeof(MyDedicatedServerBase).GetMethod("GameServer_UserGroupStatus",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (methodUserGroupStatus == null)
|
||||
{
|
||||
Log.Error("Unable to find the GameServer_UserGroupStatus method to unhook");
|
||||
return false;
|
||||
}
|
||||
var eventUserGroupStatus = Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse))
|
||||
.FirstOrDefault(x => x.Method == methodUserGroupStatus)
|
||||
as Action<ulong, ulong, bool, bool>;
|
||||
if (eventUserGroupStatus == null)
|
||||
{
|
||||
Log.Error("Unable to unhook the GameServer_UserGroupStatus method from GameServer.UserGroupStatus");
|
||||
Log.Debug(" Want to unhook {0}", methodUserGroupStatus);
|
||||
Log.Debug(" Registered handlers: ");
|
||||
foreach (Delegate method in Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse)))
|
||||
Log.Debug(" - " + method.Method);
|
||||
return false;
|
||||
}
|
||||
|
||||
MyGameService.GameServer.ValidateAuthTicketResponse -=
|
||||
eventValidateAuthTicket;
|
||||
MyGameService.GameServer.UserGroupStatusResponse -=
|
||||
eventUserGroupStatus;
|
||||
return true;
|
||||
}
|
||||
|
||||
//Largely copied from SE
|
||||
private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner)
|
||||
{
|
||||
Log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}");
|
||||
if (IsClientBanned.Invoke(MyMultiplayer.Static, steamOwner) || MySandboxGame.ConfigDedicated.Banned.Contains(steamOwner))
|
||||
{
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins);
|
||||
RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||
}
|
||||
else if (IsClientKicked.Invoke(MyMultiplayer.Static, steamOwner))
|
||||
{
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently);
|
||||
RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||
}
|
||||
if (response != JoinResult.OK)
|
||||
{
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response);
|
||||
return;
|
||||
}
|
||||
if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit)
|
||||
{
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.ServerFull);
|
||||
return;
|
||||
}
|
||||
if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
|
||||
MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) ||
|
||||
MySandboxGame.ConfigDedicated.Administrators.Contains(ConvertSteamIDFrom64(steamID)))
|
||||
{
|
||||
this.UserAccepted(steamID);
|
||||
return;
|
||||
}
|
||||
if (GetServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
|
||||
{
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid);
|
||||
return;
|
||||
}
|
||||
if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
|
||||
{
|
||||
_waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID);
|
||||
return;
|
||||
}
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline);
|
||||
}
|
||||
|
||||
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
|
||||
{
|
||||
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId))
|
||||
{
|
||||
if (member || officer)
|
||||
UserAccepted(userId);
|
||||
else
|
||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup);
|
||||
}
|
||||
}
|
||||
|
||||
private void UserAccepted(ulong steamId)
|
||||
{
|
||||
UserAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId);
|
||||
|
||||
var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected };
|
||||
Log.Info($"Player {vm.Name} joined ({vm.SteamId})");
|
||||
Players.Add(steamId, vm);
|
||||
PlayerJoined?.Invoke(vm);
|
||||
}
|
||||
|
||||
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase))]
|
||||
private static Func<ulong, string> ConvertSteamIDFrom64;
|
||||
|
||||
[ReflectedStaticMethod(Type = typeof(MyGameService))]
|
||||
private static Func<ulong, MyGameServiceAccountType> GetServerAccountType;
|
||||
|
||||
[ReflectedMethod(Name = "UserAccepted")]
|
||||
private static Action<MyDedicatedServerBase, ulong> UserAcceptedImpl;
|
||||
|
||||
[ReflectedMethod]
|
||||
private static Action<MyDedicatedServerBase, ulong, JoinResult> UserRejected;
|
||||
[ReflectedMethod]
|
||||
private static Func<MyMultiplayerBase, ulong, bool> IsClientBanned;
|
||||
[ReflectedMethod]
|
||||
private static Func<MyMultiplayerBase, ulong, bool> IsClientKicked;
|
||||
[ReflectedMethod]
|
||||
private static Action<MyMultiplayerBase, ulong> RaiseClientKicked;
|
||||
}
|
||||
}
|
123
Torch/Managers/MultiplayerManagerBase.cs
Normal file
123
Torch/Managers/MultiplayerManagerBase.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Threading;
|
||||
using NLog;
|
||||
using Torch;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Game.Entities.Character;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
using Sandbox.Game.World;
|
||||
using Sandbox.ModAPI;
|
||||
using SteamSDK;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.Collections;
|
||||
using Torch.Commands;
|
||||
using Torch.Utils;
|
||||
using Torch.ViewModels;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
using VRage.GameServices;
|
||||
using VRage.Library.Collections;
|
||||
using VRage.Network;
|
||||
using VRage.Steam;
|
||||
using VRage.Utils;
|
||||
|
||||
namespace Torch.Managers
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public abstract class MultiplayerManagerBase : Manager, IMultiplayerManagerBase
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<IPlayer> PlayerJoined;
|
||||
/// <inheritdoc />
|
||||
public event Action<IPlayer> PlayerLeft;
|
||||
|
||||
public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>();
|
||||
|
||||
#pragma warning disable 649
|
||||
[ReflectedGetter(Name = "m_players")]
|
||||
private static Func<MyPlayerCollection, Dictionary<MyPlayer.PlayerId, MyPlayer>> _onlinePlayers;
|
||||
#pragma warning restore 649
|
||||
|
||||
protected MultiplayerManagerBase(ITorchBase torch) : base(torch)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Attach()
|
||||
{
|
||||
MyMultiplayer.Static.ClientLeft += OnClientLeft;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Detach()
|
||||
{
|
||||
MyMultiplayer.Static.ClientLeft -= OnClientLeft;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMyPlayer GetPlayerByName(string name)
|
||||
{
|
||||
return _onlinePlayers.Invoke(MySession.Static.Players).FirstOrDefault(x => x.Value.DisplayName == name).Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMyPlayer GetPlayerBySteamId(ulong steamId)
|
||||
{
|
||||
_onlinePlayers.Invoke(MySession.Static.Players).TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p);
|
||||
return p;
|
||||
}
|
||||
|
||||
public ulong GetSteamId(long identityId)
|
||||
{
|
||||
foreach (KeyValuePair<MyPlayer.PlayerId, MyPlayer> kv in _onlinePlayers.Invoke(MySession.Static.Players))
|
||||
{
|
||||
if (kv.Value.Identity.IdentityId == identityId)
|
||||
return kv.Key.SteamId;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetSteamUsername(ulong steamId)
|
||||
{
|
||||
return MyMultiplayer.Static.GetMemberName(steamId);
|
||||
}
|
||||
|
||||
private void OnClientLeft(ulong steamId, MyChatMemberStateChangeEnum stateChange)
|
||||
{
|
||||
Players.TryGetValue(steamId, out PlayerViewModel vm);
|
||||
if (vm == null)
|
||||
vm = new PlayerViewModel(steamId);
|
||||
_log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}.");
|
||||
PlayerLeft?.Invoke(vm);
|
||||
Players.Remove(steamId);
|
||||
}
|
||||
|
||||
protected void RaiseClientJoined(ulong steamId)
|
||||
{
|
||||
var vm = new PlayerViewModel(steamId){State=ConnectionState.Connected};
|
||||
_log.Info($"Plat {vm.Name} joined ({vm.SteamId}");
|
||||
Players.Add(steamId, vm);
|
||||
PlayerJoined?.Invoke(vm);
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,9 +20,9 @@ namespace Torch.Managers
|
||||
{
|
||||
private static Logger _log = LogManager.GetLogger(nameof(NetworkManager));
|
||||
|
||||
private const string MyTransportLayerField = "TransportLayer";
|
||||
private const string TransportHandlersField = "m_handlers";
|
||||
private HashSet<INetworkHandler> _networkHandlers = new HashSet<INetworkHandler>();
|
||||
private const string _myTransportLayerField = "TransportLayer";
|
||||
private const string _transportHandlersField = "m_handlers";
|
||||
private readonly HashSet<INetworkHandler> _networkHandlers = new HashSet<INetworkHandler>();
|
||||
private bool _init;
|
||||
|
||||
[ReflectedGetter(Name = "m_typeTable")]
|
||||
@@ -40,14 +40,14 @@ namespace Torch.Managers
|
||||
try
|
||||
{
|
||||
var syncLayerType = typeof(MySyncLayer);
|
||||
var transportLayerField = syncLayerType.GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var transportLayerField = syncLayerType.GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (transportLayerField == null)
|
||||
throw new TypeLoadException("Could not find internal type for TransportLayer");
|
||||
|
||||
var transportLayerType = transportLayerField.FieldType;
|
||||
|
||||
if (!Reflection.HasField(transportLayerType, TransportHandlersField))
|
||||
if (!Reflection.HasField(transportLayerType, _transportHandlersField))
|
||||
throw new TypeLoadException("Could not find Handlers field");
|
||||
|
||||
return true;
|
||||
@@ -79,9 +79,9 @@ namespace Torch.Managers
|
||||
throw new InvalidOperationException("Reflection unit test failed.");
|
||||
|
||||
//don't bother with nullchecks here, it was all handled in ReflectionUnitTest
|
||||
var transportType = typeof(MySyncLayer).GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance).FieldType;
|
||||
var transportInstance = typeof(MySyncLayer).GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(MyMultiplayer.Static.SyncLayer);
|
||||
var handlers = (IDictionary)transportType.GetField(TransportHandlersField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(transportInstance);
|
||||
var transportType = typeof(MySyncLayer).GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance).FieldType;
|
||||
var transportInstance = typeof(MySyncLayer).GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(MyMultiplayer.Static.SyncLayer);
|
||||
var handlers = (IDictionary)transportType.GetField(_transportHandlersField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(transportInstance);
|
||||
var handlerTypeField = handlers.GetType().GenericTypeArguments[0].GetField("messageId"); //Should be MyTransportLayer.HandlerId
|
||||
object id = null;
|
||||
foreach (var key in handlers.Keys)
|
||||
@@ -205,6 +205,8 @@ namespace Torch.Managers
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterNetworkHandler(INetworkHandler handler)
|
||||
{
|
||||
var handlerType = handler.GetType().FullName;
|
||||
@@ -225,6 +227,12 @@ namespace Torch.Managers
|
||||
_networkHandlers.Add(handler);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UnregisterNetworkHandler(INetworkHandler handler)
|
||||
{
|
||||
return _networkHandlers.Remove(handler);
|
||||
}
|
||||
|
||||
public void RegisterNetworkHandlers(params INetworkHandler[] handlers)
|
||||
{
|
||||
foreach (var handler in handlers)
|
||||
|
@@ -155,6 +155,8 @@
|
||||
<Compile Include="ChatMessage.cs" />
|
||||
<Compile Include="Collections\ObservableList.cs" />
|
||||
<Compile Include="DispatcherExtensions.cs" />
|
||||
<Compile Include="Managers\ChatManager\ChatManagerClient.cs" />
|
||||
<Compile Include="Managers\ChatManager\ChatManagerServer.cs" />
|
||||
<Compile Include="Managers\DependencyManager.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SaveGameStatus.cs" />
|
||||
@@ -172,13 +174,12 @@
|
||||
<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\FilesystemManager.cs" />
|
||||
<Compile Include="Managers\Manager.cs" />
|
||||
<Compile Include="Managers\NetworkManager\NetworkHandlerBase.cs" />
|
||||
<Compile Include="Managers\NetworkManager\NetworkManager.cs" />
|
||||
<Compile Include="Managers\MultiplayerManager.cs" />
|
||||
<Compile Include="Managers\MultiplayerManagerBase.cs" />
|
||||
<Compile Include="Managers\UpdateManager.cs" />
|
||||
<Compile Include="Persistent.cs" />
|
||||
<Compile Include="PluginManifest.cs" />
|
||||
|
@@ -10,6 +10,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Sandbox;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.Game;
|
||||
using Sandbox.Game.Multiplayer;
|
||||
using Sandbox.Game.Screens.Helpers;
|
||||
@@ -22,10 +23,12 @@ using Torch.API.ModAPI;
|
||||
using Torch.API.Session;
|
||||
using Torch.Commands;
|
||||
using Torch.Managers;
|
||||
using Torch.Managers.ChatManager;
|
||||
using Torch.Utils;
|
||||
using Torch.Session;
|
||||
using VRage.Collections;
|
||||
using VRage.FileSystem;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ObjectBuilder;
|
||||
using VRage.ObjectBuilders;
|
||||
using VRage.Plugins;
|
||||
@@ -67,18 +70,6 @@ namespace Torch
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
||||
public IPluginManager Plugins { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
||||
public IMultiplayerManager Multiplayer { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
||||
public EntityManager Entities { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
||||
public INetworkManager Network { get; protected set; }
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
||||
public CommandManager Commands { get; protected set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ITorchSession CurrentSession => Managers?.GetManager<ITorchSessionManager>()?.CurrentSession;
|
||||
@@ -120,31 +111,28 @@ namespace Torch
|
||||
Managers = new DependencyManager();
|
||||
|
||||
Plugins = new PluginManager(this);
|
||||
Multiplayer = new MultiplayerManager(this);
|
||||
Entities = new EntityManager(this);
|
||||
Network = new NetworkManager(this);
|
||||
Commands = new CommandManager(this);
|
||||
|
||||
Managers.AddManager(new TorchSessionManager(this));
|
||||
var sessionManager = new TorchSessionManager(this);
|
||||
sessionManager.AddFactory((x) => MyMultiplayer.Static?.SyncLayer != null ? new NetworkManager(this) : null);
|
||||
sessionManager.AddFactory((x) => Sync.IsServer ? new ChatManagerServer(this) : new ChatManagerClient(this));
|
||||
sessionManager.AddFactory((x) => Sync.IsServer ? new CommandManager(this) : null);
|
||||
sessionManager.AddFactory((x) => new EntityManager(this));
|
||||
|
||||
Managers.AddManager(sessionManager);
|
||||
Managers.AddManager(new FilesystemManager(this));
|
||||
Managers.AddManager(new UpdateManager(this));
|
||||
Managers.AddManager(Network);
|
||||
Managers.AddManager(Commands);
|
||||
Managers.AddManager(Plugins);
|
||||
Managers.AddManager(Multiplayer);
|
||||
Managers.AddManager(Entities);
|
||||
Managers.AddManager(new ChatManager(this));
|
||||
|
||||
TorchAPI.Instance = this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Prefer using Managers.GetManager for global managers")]
|
||||
public T GetManager<T>() where T : class, IManager
|
||||
{
|
||||
return Managers.GetManager<T>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Prefer using Managers.AddManager for global managers")]
|
||||
public bool AddManager<T>(T manager) where T : class, IManager
|
||||
{
|
||||
return Managers.AddManager(manager);
|
||||
@@ -254,10 +242,13 @@ namespace Torch
|
||||
|
||||
Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null");
|
||||
GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));
|
||||
try { Console.Title = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}"; }
|
||||
try
|
||||
{
|
||||
Console.Title = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
///Running as service
|
||||
// Running as service
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
@@ -25,6 +25,71 @@ namespace Torch.Utils
|
||||
public Type Type { get; set; } = null;
|
||||
}
|
||||
|
||||
#region MemberInfoAttributes
|
||||
/// <summary>
|
||||
/// Indicates that this field should contain the <see cref="System.Reflection.FieldInfo"/> instance for the given field.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ReflectedFieldInfoAttribute : ReflectedMemberAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a reflected field info attribute using the given type and name.
|
||||
/// </summary>
|
||||
/// <param name="type">Type that contains the member</param>
|
||||
/// <param name="name">Name of the member</param>
|
||||
public ReflectedFieldInfoAttribute(Type type, string name)
|
||||
{
|
||||
Type = type;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that this field should contain the <see cref="System.Reflection.MethodInfo"/> instance for the given method.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ReflectedMethodInfoAttribute : ReflectedMemberAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a reflected method info attribute using the given type and name.
|
||||
/// </summary>
|
||||
/// <param name="type">Type that contains the member</param>
|
||||
/// <param name="name">Name of the member</param>
|
||||
public ReflectedMethodInfoAttribute(Type type, string name)
|
||||
{
|
||||
Type = type;
|
||||
Name = name;
|
||||
}
|
||||
/// <summary>
|
||||
/// Expected parameters of this method, or null if any parameters are accepted.
|
||||
/// </summary>
|
||||
public Type[] Parameters { get; set; } = null;
|
||||
/// <summary>
|
||||
/// Expected return type of this method, or null if any return type is accepted.
|
||||
/// </summary>
|
||||
public Type ReturnType { get; set; } = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that this field should contain the <see cref="System.Reflection.PropertyInfo"/> instance for the given property.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ReflectedPropertyInfoAttribute : ReflectedMemberAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a reflected property info attribute using the given type and name.
|
||||
/// </summary>
|
||||
/// <param name="type">Type that contains the member</param>
|
||||
/// <param name="name">Name of the member</param>
|
||||
public ReflectedPropertyInfoAttribute(Type type, string name)
|
||||
{
|
||||
Type = type;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region FieldPropGetSet
|
||||
/// <summary>
|
||||
/// Indicates that this field should contain a delegate capable of retrieving the value of a field.
|
||||
/// </summary>
|
||||
@@ -72,7 +137,9 @@ namespace Torch.Utils
|
||||
public class ReflectedSetterAttribute : ReflectedMemberAttribute
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Invoker
|
||||
/// <summary>
|
||||
/// Indicates that this field should contain a delegate capable of invoking an instance method.
|
||||
/// </summary>
|
||||
@@ -116,6 +183,184 @@ namespace Torch.Utils
|
||||
public class ReflectedStaticMethodAttribute : ReflectedMethodAttribute
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region EventReplacer
|
||||
/// <summary>
|
||||
/// Instance of statefully replacing and restoring the callbacks of an event.
|
||||
/// </summary>
|
||||
public class ReflectedEventReplacer
|
||||
{
|
||||
private const BindingFlags BindFlagAll = BindingFlags.Static |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic;
|
||||
|
||||
private object _instance;
|
||||
private Func<IEnumerable<Delegate>> _backingStoreReader;
|
||||
private Action<Delegate> _callbackAdder;
|
||||
private Action<Delegate> _callbackRemover;
|
||||
private readonly ReflectedEventReplaceAttribute _attributes;
|
||||
private readonly HashSet<Delegate> _registeredCallbacks = new HashSet<Delegate>();
|
||||
private readonly MethodInfo _targetMethodInfo;
|
||||
|
||||
internal ReflectedEventReplacer(ReflectedEventReplaceAttribute attr)
|
||||
{
|
||||
_attributes = attr;
|
||||
FieldInfo backingStore = GetEventBackingField(attr.EventName, attr.EventDeclaringType);
|
||||
if (backingStore == null)
|
||||
throw new ArgumentException($"Unable to find backing field for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
|
||||
EventInfo evtInfo = ReflectedManager.GetFieldPropRecursive(attr.EventDeclaringType, attr.EventName, BindFlagAll, (a, b, c) => a.GetEvent(b, c));
|
||||
if (evtInfo == null)
|
||||
throw new ArgumentException($"Unable to find event info for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
|
||||
_backingStoreReader = () => GetEventsInternal(_instance, backingStore);
|
||||
_callbackAdder = (x) => evtInfo.AddEventHandler(_instance, x);
|
||||
_callbackRemover = (x) => evtInfo.RemoveEventHandler(_instance, x);
|
||||
if (attr.TargetParameters == null)
|
||||
{
|
||||
_targetMethodInfo = attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll);
|
||||
if (_targetMethodInfo == null)
|
||||
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName} to replace");
|
||||
}
|
||||
else
|
||||
{
|
||||
_targetMethodInfo =
|
||||
attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll, null, attr.TargetParameters, null);
|
||||
if (_targetMethodInfo == null)
|
||||
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName}){string.Join(", ", attr.TargetParameters.Select(x => x.FullName))}) to replace");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that this replacement can be performed.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance to operate on, or null if static</param>
|
||||
/// <returns>true if possible, false if unsuccessful</returns>
|
||||
public bool Test(object instance)
|
||||
{
|
||||
_instance = instance;
|
||||
_registeredCallbacks.Clear();
|
||||
foreach (Delegate callback in _backingStoreReader.Invoke())
|
||||
if (callback.Method == _targetMethodInfo)
|
||||
_registeredCallbacks.Add(callback);
|
||||
|
||||
return _registeredCallbacks.Count > 0;
|
||||
}
|
||||
|
||||
private Delegate _newCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Removes the target callback defined in the attribute and replaces it with the provided callback.
|
||||
/// </summary>
|
||||
/// <param name="newCallback">The new event callback</param>
|
||||
/// <param name="instance">The instance to operate on, or null if static</param>
|
||||
public void Replace(Delegate newCallback, object instance)
|
||||
{
|
||||
_instance = instance;
|
||||
if (_newCallback != null)
|
||||
throw new Exception("Reflected event replacer is in invalid state: Replace when already replaced");
|
||||
_newCallback = newCallback;
|
||||
Test(instance);
|
||||
if (_registeredCallbacks.Count == 0)
|
||||
throw new Exception("Reflected event replacer is in invalid state: Nothing to replace");
|
||||
foreach (Delegate callback in _registeredCallbacks)
|
||||
_callbackRemover.Invoke(callback);
|
||||
_callbackAdder.Invoke(_newCallback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the callback is currently replaced
|
||||
/// </summary>
|
||||
public bool Replaced => _newCallback != null;
|
||||
|
||||
/// <summary>
|
||||
/// Removes the callback added by <see cref="Replace"/> and puts the original callback back.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance to operate on, or null if static</param>
|
||||
public void Restore(object instance)
|
||||
{
|
||||
_instance = instance;
|
||||
if (_newCallback == null)
|
||||
throw new Exception("Reflected event replacer is in invalid state: Restore when not replaced");
|
||||
_callbackRemover.Invoke(_newCallback);
|
||||
foreach (Delegate callback in _registeredCallbacks)
|
||||
_callbackAdder.Invoke(callback);
|
||||
_newCallback = null;
|
||||
}
|
||||
|
||||
|
||||
private static readonly string[] _backingFieldForEvent = { "{0}", "<backing_store>{0}" };
|
||||
|
||||
private static FieldInfo GetEventBackingField(string eventName, Type baseType)
|
||||
{
|
||||
FieldInfo eventField = null;
|
||||
Type type = baseType;
|
||||
while (type != null && eventField == null)
|
||||
{
|
||||
for (var i = 0; i < _backingFieldForEvent.Length && eventField == null; i++)
|
||||
eventField = type.GetField(string.Format(_backingFieldForEvent[i], eventName), BindFlagAll);
|
||||
type = type.BaseType;
|
||||
}
|
||||
return eventField;
|
||||
}
|
||||
|
||||
private static IEnumerable<Delegate> GetEventsInternal(object instance, FieldInfo eventField)
|
||||
{
|
||||
if (eventField.GetValue(instance) is MulticastDelegate eventDel)
|
||||
{
|
||||
foreach (Delegate handle in eventDel.GetInvocationList())
|
||||
yield return handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attribute used to indicate that the the given field, of type <![CDATA[Func<ReflectedEventReplacer>]]>, should be filled with
|
||||
/// a function used to create a new event replacer.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class ReflectedEventReplaceAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Type that the event is declared in
|
||||
/// </summary>
|
||||
public Type EventDeclaringType { get; set; }
|
||||
/// <summary>
|
||||
/// Name of the event
|
||||
/// </summary>
|
||||
public string EventName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type that the method to replace is declared in
|
||||
/// </summary>
|
||||
public Type TargetDeclaringType { get; set; }
|
||||
/// <summary>
|
||||
/// Name of the method to replace
|
||||
/// </summary>
|
||||
public string TargetName { get; set; }
|
||||
/// <summary>
|
||||
/// Optional parameters of the method to replace. Null to ignore.
|
||||
/// </summary>
|
||||
public Type[] TargetParameters { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a reflected event replacer attribute to, for the event defined as eventName in eventDeclaringType,
|
||||
/// replace the method defined as targetName in targetDeclaringType with a custom callback.
|
||||
/// </summary>
|
||||
/// <param name="eventDeclaringType">Type the event is declared in</param>
|
||||
/// <param name="eventName">Name of the event</param>
|
||||
/// <param name="targetDeclaringType">Type the method to remove is declared in</param>
|
||||
/// <param name="targetName">Name of the method to remove</param>
|
||||
public ReflectedEventReplaceAttribute(Type eventDeclaringType, string eventName, Type targetDeclaringType,
|
||||
string targetName)
|
||||
{
|
||||
EventDeclaringType = eventDeclaringType;
|
||||
EventName = eventName;
|
||||
TargetDeclaringType = targetDeclaringType;
|
||||
TargetName = targetName;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Automatically calls <see cref="ReflectedManager.Process(Assembly)"/> for every assembly already loaded, and every assembly that is loaded in the future.
|
||||
@@ -185,37 +430,94 @@ namespace Torch.Utils
|
||||
/// <exception cref="ArgumentException">If the field failed to process</exception>
|
||||
public static bool Process(FieldInfo field)
|
||||
{
|
||||
var attr = field.GetCustomAttribute<ReflectedMethodAttribute>();
|
||||
if (attr != null)
|
||||
foreach (ReflectedMemberAttribute attr in field.GetCustomAttributes<ReflectedMemberAttribute>())
|
||||
{
|
||||
if (!field.IsStatic)
|
||||
throw new ArgumentException("Field must be static to be reflected");
|
||||
ProcessReflectedMethod(field, attr);
|
||||
switch (attr)
|
||||
{
|
||||
case ReflectedMethodAttribute rma:
|
||||
ProcessReflectedMethod(field, rma);
|
||||
return true;
|
||||
case ReflectedGetterAttribute rga:
|
||||
ProcessReflectedField(field, rga);
|
||||
return true;
|
||||
case ReflectedSetterAttribute rsa:
|
||||
ProcessReflectedField(field, rsa);
|
||||
return true;
|
||||
case ReflectedFieldInfoAttribute rfia:
|
||||
ProcessReflectedMemberInfo(field, rfia);
|
||||
return true;
|
||||
case ReflectedPropertyInfoAttribute rpia:
|
||||
ProcessReflectedMemberInfo(field, rpia);
|
||||
return true;
|
||||
case ReflectedMethodInfoAttribute rmia:
|
||||
ProcessReflectedMemberInfo(field, rmia);
|
||||
return true;
|
||||
}
|
||||
var attr2 = field.GetCustomAttribute<ReflectedGetterAttribute>();
|
||||
if (attr2 != null)
|
||||
{
|
||||
if (!field.IsStatic)
|
||||
throw new ArgumentException("Field must be static to be reflected");
|
||||
ProcessReflectedField(field, attr2);
|
||||
return true;
|
||||
}
|
||||
var attr3 = field.GetCustomAttribute<ReflectedSetterAttribute>();
|
||||
if (attr3 != null)
|
||||
{
|
||||
if (!field.IsStatic)
|
||||
{
|
||||
throw new ArgumentException("Field must be static to be reflected");
|
||||
}
|
||||
|
||||
ProcessReflectedField(field, attr3);
|
||||
var reflectedEventReplacer = field.GetCustomAttribute<ReflectedEventReplaceAttribute>();
|
||||
if (reflectedEventReplacer != null)
|
||||
{
|
||||
if (!field.IsStatic)
|
||||
throw new ArgumentException("Field must be static to be reflected");
|
||||
field.SetValue(null, new Func<ReflectedEventReplacer>(() => new ReflectedEventReplacer(reflectedEventReplacer)));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ProcessReflectedMemberInfo(FieldInfo field, ReflectedMemberAttribute attr)
|
||||
{
|
||||
MemberInfo info = null;
|
||||
if (attr.Type == null)
|
||||
throw new ArgumentException("Reflected member info attributes require Type to be defined");
|
||||
if (attr.Name == null)
|
||||
throw new ArgumentException("Reflected member info attributes require Name to be defined");
|
||||
switch (attr)
|
||||
{
|
||||
case ReflectedFieldInfoAttribute rfia:
|
||||
info = GetFieldPropRecursive(rfia.Type, rfia.Name,
|
||||
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
|
||||
(a, b, c) => a.GetField(b));
|
||||
if (info == null)
|
||||
throw new ArgumentException($"Unable to find field {rfia.Type.FullName}#{rfia.Name}");
|
||||
break;
|
||||
case ReflectedPropertyInfoAttribute rpia:
|
||||
info = GetFieldPropRecursive(rpia.Type, rpia.Name,
|
||||
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
|
||||
(a, b, c) => a.GetProperty(b));
|
||||
if (info == null)
|
||||
throw new ArgumentException($"Unable to find property {rpia.Type.FullName}#{rpia.Name}");
|
||||
break;
|
||||
case ReflectedMethodInfoAttribute rmia:
|
||||
if (rmia.Parameters != null)
|
||||
{
|
||||
info = rmia.Type.GetMethod(rmia.Name,
|
||||
BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
|
||||
null, CallingConventions.Any, rmia.Parameters, null);
|
||||
if (info == null)
|
||||
throw new ArgumentException(
|
||||
$"Unable to find method {rmia.Type.FullName}#{rmia.Name}({string.Join(", ", rmia.Parameters.Select(x => x.FullName))})");
|
||||
}
|
||||
else
|
||||
{
|
||||
info = rmia.Type.GetMethod(rmia.Name,
|
||||
BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (info == null)
|
||||
throw new ArgumentException(
|
||||
$"Unable to find method {rmia.Type.FullName}#{rmia.Name}");
|
||||
}
|
||||
if (rmia.ReturnType != null && !rmia.ReturnType.IsAssignableFrom(((MethodInfo)info).ReturnType))
|
||||
throw new ArgumentException($"Method {rmia.Type.FullName}#{rmia.Name} has return type {((MethodInfo)info).ReturnType.FullName}, expected {rmia.ReturnType.FullName}");
|
||||
break;
|
||||
}
|
||||
if (info == null)
|
||||
throw new ArgumentException($"Unable to find member info for {attr.GetType().Name}[{attr.Type.FullName}#{attr.Name}");
|
||||
field.SetValue(null, info);
|
||||
}
|
||||
|
||||
private static void ProcessReflectedMethod(FieldInfo field, ReflectedMethodAttribute attr)
|
||||
{
|
||||
MethodInfo delegateMethod = field.FieldType.GetMethod("Invoke");
|
||||
@@ -249,14 +551,14 @@ namespace Torch.Utils
|
||||
field.SetValue(null, Delegate.CreateDelegate(field.FieldType, methodInstance));
|
||||
else
|
||||
{
|
||||
ParameterExpression[] paramExp = parameters.Select(x => Expression.Parameter(x.ParameterType)).ToArray();
|
||||
ParameterExpression[] paramExp = parameters.Select(x => Expression.Parameter(x.ParameterType, x.Name)).ToArray();
|
||||
field.SetValue(null,
|
||||
Expression.Lambda(Expression.Call(paramExp[0], methodInstance, paramExp.Skip(1)), paramExp)
|
||||
.Compile());
|
||||
}
|
||||
}
|
||||
|
||||
private static T GetFieldPropRecursive<T>(Type baseType, string name, BindingFlags flags, Func<Type, string, BindingFlags, T> getter) where T : class
|
||||
internal static T GetFieldPropRecursive<T>(Type baseType, string name, BindingFlags flags, Func<Type, string, BindingFlags, T> getter) where T : class
|
||||
{
|
||||
while (baseType != null)
|
||||
{
|
||||
|
Reference in New Issue
Block a user