From 0cabaceb48cac0a28664139b90bf811254c5cac4 Mon Sep 17 00:00:00 2001 From: Tim Sarbin Date: Fri, 30 Nov 2018 23:37:08 -0500 Subject: [PATCH] Added client/server architecture support. --- .../Attributes/MessageFrameAttribute.cs | 19 +++ OpenDiablo2.Common/Enums/eMessageFrameType.cs | 19 +++ OpenDiablo2.Common/Interfaces/IGameState.cs | 5 +- .../IMessageFrame.cs} | 4 +- .../MessageBus/ISessionEventProvider.cs | 18 +++ .../{ => MessageBus}/ISessionManager.cs | 4 +- .../Interfaces/MessageBus/ISessionServer.cs | 13 ++ OpenDiablo2.Common/Models/MPQDS1.cs | 3 - OpenDiablo2.Common/Models/Mobs/PlayerState.cs | 1 + OpenDiablo2.Common/OpenDiablo2.Common.csproj | 13 +- OpenDiablo2.Core/AutofacModule.cs | 1 - OpenDiablo2.Core/GameState/GameState.cs | 19 ++- OpenDiablo2.Core/OpenDiablo2.Core.csproj | 1 - OpenDiablo2.Core/SessionServer.cs | 18 --- OpenDiablo2.ServiceBus/AutofacModule.cs | 42 ++++-- OpenDiablo2.ServiceBus/LocalSessionManager.cs | 47 ------- .../Message Frames/MFJoinGame.cs | 49 +++++++ .../Message Frames/MFSetSeed.cs | 35 +++++ .../OpenDiablo2.ServiceBus.csproj | 5 +- OpenDiablo2.ServiceBus/SessionManager.cs | 129 ++++++++++++++++++ OpenDiablo2.ServiceBus/SessionServer.cs | 104 ++++++++++++++ OpenDiablo2/OpenDiablo2.csproj | 2 +- OpenDiablo2/Program.cs | 17 ++- README.md | 2 +- 24 files changed, 467 insertions(+), 103 deletions(-) create mode 100644 OpenDiablo2.Common/Attributes/MessageFrameAttribute.cs create mode 100644 OpenDiablo2.Common/Enums/eMessageFrameType.cs rename OpenDiablo2.Common/Interfaces/{ISessionServer.cs => MessageBus/IMessageFrame.cs} (53%) create mode 100644 OpenDiablo2.Common/Interfaces/MessageBus/ISessionEventProvider.cs rename OpenDiablo2.Common/Interfaces/{ => MessageBus}/ISessionManager.cs (65%) create mode 100644 OpenDiablo2.Common/Interfaces/MessageBus/ISessionServer.cs delete mode 100644 OpenDiablo2.Core/SessionServer.cs delete mode 100644 OpenDiablo2.ServiceBus/LocalSessionManager.cs create mode 100644 OpenDiablo2.ServiceBus/Message Frames/MFJoinGame.cs create mode 100644 OpenDiablo2.ServiceBus/Message Frames/MFSetSeed.cs create mode 100644 OpenDiablo2.ServiceBus/SessionManager.cs create mode 100644 OpenDiablo2.ServiceBus/SessionServer.cs diff --git a/OpenDiablo2.Common/Attributes/MessageFrameAttribute.cs b/OpenDiablo2.Common/Attributes/MessageFrameAttribute.cs new file mode 100644 index 00000000..34cac939 --- /dev/null +++ b/OpenDiablo2.Common/Attributes/MessageFrameAttribute.cs @@ -0,0 +1,19 @@ +using System; +using OpenDiablo2.Common.Enums; + +namespace OpenDiablo2.Common.Attributes +{ + [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] + public sealed class MessageFrameAttribute : Attribute + { + public eMessageFrameType FrameType { get; private set; } + + // This is a positional argument + public MessageFrameAttribute(eMessageFrameType frameType) + { + this.FrameType = frameType; + } + + + } +} diff --git a/OpenDiablo2.Common/Enums/eMessageFrameType.cs b/OpenDiablo2.Common/Enums/eMessageFrameType.cs new file mode 100644 index 00000000..ad5556dd --- /dev/null +++ b/OpenDiablo2.Common/Enums/eMessageFrameType.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenDiablo2.Common.Enums +{ + public enum eMessageFrameType + { + None = 0x00, + SetSeed = 0x01, + JoinGame = 0x02, + + MAX = 0xFF, // NOTE: + // You absolutely cannot have a higher ID than this without + // changing the message header to multi-byte for ALL frame types!!! + } +} diff --git a/OpenDiablo2.Common/Interfaces/IGameState.cs b/OpenDiablo2.Common/Interfaces/IGameState.cs index 042d4e70..4a4b5097 100644 --- a/OpenDiablo2.Common/Interfaces/IGameState.cs +++ b/OpenDiablo2.Common/Interfaces/IGameState.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Drawing; using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Models; namespace OpenDiablo2.Common.Interfaces { - public interface IGameState + public interface IGameState : IDisposable { int Act { get; } int Seed { get; } diff --git a/OpenDiablo2.Common/Interfaces/ISessionServer.cs b/OpenDiablo2.Common/Interfaces/MessageBus/IMessageFrame.cs similarity index 53% rename from OpenDiablo2.Common/Interfaces/ISessionServer.cs rename to OpenDiablo2.Common/Interfaces/MessageBus/IMessageFrame.cs index cba01fa6..4ea50c20 100644 --- a/OpenDiablo2.Common/Interfaces/ISessionServer.cs +++ b/OpenDiablo2.Common/Interfaces/MessageBus/IMessageFrame.cs @@ -6,7 +6,9 @@ using System.Threading.Tasks; namespace OpenDiablo2.Common.Interfaces { - public interface ISessionServer : IDisposable + public interface IMessageFrame { + byte[] Data { get; set; } + void Process(object sender, ISessionEventProvider sessionEventProvider); } } diff --git a/OpenDiablo2.Common/Interfaces/MessageBus/ISessionEventProvider.cs b/OpenDiablo2.Common/Interfaces/MessageBus/ISessionEventProvider.cs new file mode 100644 index 00000000..ea11ee7d --- /dev/null +++ b/OpenDiablo2.Common/Interfaces/MessageBus/ISessionEventProvider.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenDiablo2.Common.Interfaces +{ + public delegate void OnSetSeedEvent(object sender, int seed); + public delegate void OnJoinGameEvent(object sender, Guid playerId, string playerName); // TODO: Not the final version.. + + public interface ISessionEventProvider + { + + OnSetSeedEvent OnSetSeed { get; set; } + OnJoinGameEvent OnJoinGame { get; set; } + } +} diff --git a/OpenDiablo2.Common/Interfaces/ISessionManager.cs b/OpenDiablo2.Common/Interfaces/MessageBus/ISessionManager.cs similarity index 65% rename from OpenDiablo2.Common/Interfaces/ISessionManager.cs rename to OpenDiablo2.Common/Interfaces/MessageBus/ISessionManager.cs index e4dffb3f..6e832818 100644 --- a/OpenDiablo2.Common/Interfaces/ISessionManager.cs +++ b/OpenDiablo2.Common/Interfaces/MessageBus/ISessionManager.cs @@ -6,9 +6,11 @@ using System.Threading.Tasks; namespace OpenDiablo2.Common.Interfaces { - public interface ISessionManager : IDisposable + + public interface ISessionManager : ISessionEventProvider, IDisposable { void Initialize(); void Stop(); + void JoinGame(string playerName); } } diff --git a/OpenDiablo2.Common/Interfaces/MessageBus/ISessionServer.cs b/OpenDiablo2.Common/Interfaces/MessageBus/ISessionServer.cs new file mode 100644 index 00000000..6025788a --- /dev/null +++ b/OpenDiablo2.Common/Interfaces/MessageBus/ISessionServer.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading; + +namespace OpenDiablo2.Common.Interfaces +{ + public interface ISessionServer : IDisposable + { + AutoResetEvent WaitServerStartEvent { get; set; } + + void Start(); + void Stop(); + } +} diff --git a/OpenDiablo2.Common/Models/MPQDS1.cs b/OpenDiablo2.Common/Models/MPQDS1.cs index dae54170..e5dc099d 100644 --- a/OpenDiablo2.Common/Models/MPQDS1.cs +++ b/OpenDiablo2.Common/Models/MPQDS1.cs @@ -101,7 +101,6 @@ namespace OpenDiablo2.Common.Models // TODO: DI magic please public MPQDS1(Stream stream, LevelPreset level, LevelDetail levelDetail, LevelType levelType, IEngineDataManager engineDataManager, IResourceManager resourceManager) { - log.Debug($"Loading {level.Name} (Act {levelDetail.Act})..."); var br = new BinaryReader(stream); Version = br.ReadInt32(); Width = br.ReadInt32() + 1; @@ -296,8 +295,6 @@ namespace OpenDiablo2.Common.Models if (!isMasked || levelType.File[i] == "0") continue; - log.Debug($"Loading DT resource {levelType.File[i]}"); - DT1s[i] = resourceManager.GetMPQDT1("data\\global\\tiles\\" + levelType.File[i].Replace("/", "\\")); } diff --git a/OpenDiablo2.Common/Models/Mobs/PlayerState.cs b/OpenDiablo2.Common/Models/Mobs/PlayerState.cs index 1c6e07e7..bbdbd35b 100644 --- a/OpenDiablo2.Common/Models/Mobs/PlayerState.cs +++ b/OpenDiablo2.Common/Models/Mobs/PlayerState.cs @@ -11,6 +11,7 @@ namespace OpenDiablo2.Common.Models.Mobs { public class PlayerState : MobState { + public Guid Id { get; protected set; } public eHero HeroType { get; protected set; } private IHeroTypeConfig HeroTypeConfig; private ILevelExperienceConfig ExperienceConfig; diff --git a/OpenDiablo2.Common/OpenDiablo2.Common.csproj b/OpenDiablo2.Common/OpenDiablo2.Common.csproj index d8f8826e..528c3a50 100644 --- a/OpenDiablo2.Common/OpenDiablo2.Common.csproj +++ b/OpenDiablo2.Common/OpenDiablo2.Common.csproj @@ -52,8 +52,10 @@ + + @@ -67,8 +69,10 @@ - - + + + + @@ -137,9 +141,6 @@ - - - - + \ No newline at end of file diff --git a/OpenDiablo2.Core/AutofacModule.cs b/OpenDiablo2.Core/AutofacModule.cs index 7bb1497c..52793968 100644 --- a/OpenDiablo2.Core/AutofacModule.cs +++ b/OpenDiablo2.Core/AutofacModule.cs @@ -27,7 +27,6 @@ namespace OpenDiablo2.Core builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().InstancePerDependency(); - builder.RegisterType().As().InstancePerLifetimeScope(); } } } diff --git a/OpenDiablo2.Core/GameState/GameState.cs b/OpenDiablo2.Core/GameState/GameState.cs index cc7cdc64..b7b18c98 100644 --- a/OpenDiablo2.Core/GameState/GameState.cs +++ b/OpenDiablo2.Core/GameState/GameState.cs @@ -60,14 +60,20 @@ namespace OpenDiablo2.Core.GameState_ sessionManager = getSessionManager(sessionType); sessionManager.Initialize(); - var random = new Random(); - Seed = random.Next(); // TODO: Seed does not go here ;-( + sessionManager.OnSetSeed += OnSetSeedEvent; - sceneManager.ChangeScene("Game"); mapInfo = new List(); - (new MapGenerator(this)).Generate(); + sceneManager.ChangeScene("Game"); + + sessionManager.JoinGame(characterName); // TODO: we need more attributes... } + private void OnSetSeedEvent(object sender, int seed) + { + log.Info($"Setting seed to {seed}"); + this.Seed = seed; + (new MapGenerator(this)).Generate(); + } public MapInfo LoadSubMap(int levelDefId, Point origin) { @@ -391,5 +397,10 @@ namespace OpenDiablo2.Core.GameState_ animationTime += ((float)ms / 1000f); animationTime -= (float)Math.Truncate(animationTime); } + + public void Dispose() + { + sessionManager?.Dispose(); + } } } diff --git a/OpenDiablo2.Core/OpenDiablo2.Core.csproj b/OpenDiablo2.Core/OpenDiablo2.Core.csproj index 3bf583db..f8b2fa72 100644 --- a/OpenDiablo2.Core/OpenDiablo2.Core.csproj +++ b/OpenDiablo2.Core/OpenDiablo2.Core.csproj @@ -65,7 +65,6 @@ - diff --git a/OpenDiablo2.Core/SessionServer.cs b/OpenDiablo2.Core/SessionServer.cs deleted file mode 100644 index 932d8755..00000000 --- a/OpenDiablo2.Core/SessionServer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using OpenDiablo2.Common.Interfaces; - -namespace OpenDiablo2.Core -{ - public sealed class SessionServer : ISessionServer - { - - public void Dispose() - { - - } - } -} diff --git a/OpenDiablo2.ServiceBus/AutofacModule.cs b/OpenDiablo2.ServiceBus/AutofacModule.cs index 4123564c..cf214aaf 100644 --- a/OpenDiablo2.ServiceBus/AutofacModule.cs +++ b/OpenDiablo2.ServiceBus/AutofacModule.cs @@ -1,5 +1,7 @@ using System; +using System.Linq; using Autofac; +using OpenDiablo2.Common.Attributes; using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Interfaces; @@ -9,24 +11,38 @@ namespace OpenDiablo2.ServiceBus { protected override void Load(ContainerBuilder builder) { - builder.RegisterType().AsSelf().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + + var types = ThisAssembly.GetTypes().Where(x => typeof(IMessageFrame).IsAssignableFrom(x) && x.IsClass); + foreach (var type in types) + { + var att = type.GetCustomAttributes(true).First(x => typeof(MessageFrameAttribute).IsAssignableFrom(x.GetType())) as MessageFrameAttribute; + builder + .RegisterType(type) + .Keyed(att.FrameType) + .InstancePerDependency(); + } + + builder.Register>(c => + { + var componentContext = c.Resolve(); + return (frameType) => componentContext.ResolveKeyed(frameType); + }); builder.Register>(c => { var componentContext = c.Resolve(); - return (sessionType) => - { - switch (sessionType) - { - case eSessionType.Local: - return componentContext.Resolve(); - case eSessionType.Server: - case eSessionType.Remote: - default: - throw new ApplicationException("Unsupported session type."); - } - }; + return (sessionType) => componentContext.Resolve(new NamedParameter("sessionType", sessionType)); }); + + builder.Register>(c => + { + var componentContext = c.Resolve(); + return (sessionType) => componentContext.Resolve(new NamedParameter("sessionType", sessionType)); + }); + + } } } diff --git a/OpenDiablo2.ServiceBus/LocalSessionManager.cs b/OpenDiablo2.ServiceBus/LocalSessionManager.cs deleted file mode 100644 index f0720116..00000000 --- a/OpenDiablo2.ServiceBus/LocalSessionManager.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using OpenDiablo2.Common.Interfaces; - -namespace OpenDiablo2.ServiceBus -{ - public sealed class LocalSessionManager : ISessionManager - { - private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); - - private readonly ISessionServer sessionServer; - volatile bool running = false; - - public LocalSessionManager(ISessionServer sessionServer) - { - this.sessionServer = sessionServer; - } - - public void Initialize() - { - log.Info("Initializing a local multiplayer session."); - running = true; - Task.Run(() => Listen()); - } - - private void Listen() - { - log.Info("Local session manager is starting."); - while (running) - { - - } - - log.Info("Local session manager has stopped."); - } - - public void Dispose() - { - - } - - public void Stop() => running = false; - } -} diff --git a/OpenDiablo2.ServiceBus/Message Frames/MFJoinGame.cs b/OpenDiablo2.ServiceBus/Message Frames/MFJoinGame.cs new file mode 100644 index 00000000..6f72d557 --- /dev/null +++ b/OpenDiablo2.ServiceBus/Message Frames/MFJoinGame.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using System.Text; +using OpenDiablo2.Common.Attributes; +using OpenDiablo2.Common.Enums; +using OpenDiablo2.Common.Interfaces; + +namespace OpenDiablo2.ServiceBus.Message_Frames +{ + [MessageFrame(eMessageFrameType.JoinGame)] + public sealed class MFJoinGame : IMessageFrame + { + public Guid PlayerId { get; set; } + public string PlayerName { get; set; } + public byte[] Data + { + get + { + return PlayerId.ToByteArray() + .Concat(BitConverter.GetBytes((UInt16)PlayerName.Length)) + .Concat(Encoding.UTF8.GetBytes(PlayerName)) + .ToArray(); + } + + set + { + + PlayerId = new Guid(value.Take(16).ToArray()); + var playerNameLen = BitConverter.ToUInt16(value, 16); + PlayerName = Encoding.UTF8.GetString(value, 18, value.Length - 18); + + if (PlayerName.Length != playerNameLen) + throw new ApplicationException("Invalid player length!"); + } + } + + public MFJoinGame() { } + public MFJoinGame(string playerName) + { + PlayerId = Guid.NewGuid(); + PlayerName = playerName; + } + + public void Process(object sender, ISessionEventProvider sessionEventProvider) + { + sessionEventProvider.OnJoinGame(sender, PlayerId, PlayerName); + } + } +} diff --git a/OpenDiablo2.ServiceBus/Message Frames/MFSetSeed.cs b/OpenDiablo2.ServiceBus/Message Frames/MFSetSeed.cs new file mode 100644 index 00000000..eb7901ac --- /dev/null +++ b/OpenDiablo2.ServiceBus/Message Frames/MFSetSeed.cs @@ -0,0 +1,35 @@ +using System; +using OpenDiablo2.Common.Attributes; +using OpenDiablo2.Common.Enums; +using OpenDiablo2.Common.Interfaces; + +namespace OpenDiablo2.ServiceBus.Message_Frames +{ + [MessageFrame(eMessageFrameType.SetSeed)] + public sealed class MFSetSeed : IMessageFrame + { + + public byte[] Data + { + get => BitConverter.GetBytes(Seed); + set => BitConverter.ToInt32(value, 0); + } + + public Int32 Seed { get; private set; } + + public MFSetSeed() + { + Seed = (new Random()).Next(); + } + + public MFSetSeed(int seed) + { + Seed = seed; + } + + public void Process(object sender, ISessionEventProvider sessionEventProvider) + { + sessionEventProvider.OnSetSeed?.Invoke(sender, Seed); + } + } +} diff --git a/OpenDiablo2.ServiceBus/OpenDiablo2.ServiceBus.csproj b/OpenDiablo2.ServiceBus/OpenDiablo2.ServiceBus.csproj index b22a6bbc..40c0077f 100644 --- a/OpenDiablo2.ServiceBus/OpenDiablo2.ServiceBus.csproj +++ b/OpenDiablo2.ServiceBus/OpenDiablo2.ServiceBus.csproj @@ -56,8 +56,11 @@ - + + + + diff --git a/OpenDiablo2.ServiceBus/SessionManager.cs b/OpenDiablo2.ServiceBus/SessionManager.cs new file mode 100644 index 00000000..5c417149 --- /dev/null +++ b/OpenDiablo2.ServiceBus/SessionManager.cs @@ -0,0 +1,129 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NetMQ; +using NetMQ.Sockets; +using OpenDiablo2.Common.Attributes; +using OpenDiablo2.Common.Enums; +using OpenDiablo2.Common.Interfaces; +using OpenDiablo2.ServiceBus.Message_Frames; + +namespace OpenDiablo2.ServiceBus +{ + public sealed class SessionManager : ISessionManager + { + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + private readonly Func getSessionServer; + private readonly eSessionType sessionType; + private readonly Func getMessageFrame; + + private RequestSocket requestSocket; + private AutoResetEvent resetEvent = new AutoResetEvent(false); + private ISessionServer sessionServer; + private Guid playerId; + private bool running = false; + + public OnSetSeedEvent OnSetSeed { get; set; } + public OnJoinGameEvent OnJoinGame { get; set; } + + public SessionManager(eSessionType sessionType, Func getSessionServer, Func getMessageFrame) + { + this.getSessionServer = getSessionServer; + this.sessionType = sessionType; + this.getMessageFrame = getMessageFrame; + } + + public void Initialize() + { + if (sessionType == eSessionType.Local || sessionType == eSessionType.Server) + { + sessionServer = getSessionServer(sessionType); + sessionServer.Start(); + sessionServer.WaitServerStartEvent.WaitOne(); // Wait until the server starts... + } + else sessionServer = null; + + log.Info("Initializing a local multiplayer session."); + Task.Run(() => Listen()); + } + + private void Listen() + { + log.Info("Session manager is starting."); + requestSocket = new RequestSocket(); + + switch (sessionType) + { + case eSessionType.Local: + requestSocket.Connect("inproc://opendiablo2-session"); + break; + case eSessionType.Server: + case eSessionType.Remote: + default: + throw new ApplicationException("This session type is currently unsupported."); + } + + + //var bytes = message.First().ToByteArray(); + //var frameType = (eMessageFrameType)bytes[0]; + //var frameData = bytes.Skip(1).ToArray(); // TODO: Can we maybe use pointers? This seems wasteful + //var messageFrame = getMessageFrame(frameType); + //messageFrame.Data = frameData; + //messageFrame.Process(socket, this); + + running = true; + resetEvent.WaitOne(); + running = false; + requestSocket.Dispose(); + log.Info("Session manager has stopped."); + + } + public void Stop() + { + if (!running) + return; + + resetEvent.Set(); + + if (sessionType == eSessionType.Local || sessionType == eSessionType.Server) + sessionServer?.Stop(); + + } + + public void Dispose() + { + Stop(); + } + + public void Send(IMessageFrame messageFrame) + { + var attr = messageFrame.GetType().GetCustomAttributes(true).First(x => typeof(MessageFrameAttribute).IsAssignableFrom(x.GetType())) as MessageFrameAttribute; + requestSocket.SendFrame(new byte[] { (byte)attr.FrameType }.Concat(messageFrame.Data).ToArray()); + } + + private void ProcessMessageFrame() where T : IMessageFrame, new() + { + if (!running) + throw new ApplicationException("You have made a terrible mistake. Cannot get a message frame if you are not connected."); + + var bytes = requestSocket.ReceiveFrameBytes(); + var frameType = (eMessageFrameType)bytes[0]; + var frameData = bytes.Skip(1).ToArray(); // TODO: Can we maybe use pointers? This seems wasteful + var messageFrame = getMessageFrame(frameType); + if (messageFrame.GetType() != typeof(T)) + throw new ApplicationException("Recieved unexpected message frame!"); + messageFrame.Data = frameData; + messageFrame.Process(requestSocket, this); + } + + public void JoinGame(string playerName) + { + var mf = new MFJoinGame(playerName); + playerId = mf.PlayerId; + Send(mf); + ProcessMessageFrame(); + } + } +} diff --git a/OpenDiablo2.ServiceBus/SessionServer.cs b/OpenDiablo2.ServiceBus/SessionServer.cs new file mode 100644 index 00000000..c57e2109 --- /dev/null +++ b/OpenDiablo2.ServiceBus/SessionServer.cs @@ -0,0 +1,104 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NetMQ; +using NetMQ.Sockets; +using OpenDiablo2.Common.Attributes; +using OpenDiablo2.Common.Enums; +using OpenDiablo2.Common.Interfaces; +using OpenDiablo2.ServiceBus.Message_Frames; + +namespace OpenDiablo2.ServiceBus +{ + public sealed class SessionServer : ISessionServer, ISessionEventProvider + { + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + private readonly eSessionType sessionType; + private readonly Func getMessageFrame; + private AutoResetEvent resetEvent = new AutoResetEvent(false); + public AutoResetEvent WaitServerStartEvent { get; set; } = new AutoResetEvent(false); + + private int gameSeed; + private bool running = false; + private ResponseSocket responseSocket; + + public OnSetSeedEvent OnSetSeed { get; set; } + public OnJoinGameEvent OnJoinGame { get; set; } + + public SessionServer(eSessionType sessionType, Func getMessageFrame) + { + this.sessionType = sessionType; + this.getMessageFrame = getMessageFrame; + } + + public void Start() + { + gameSeed = (new Random()).Next(); + Task.Run(() => Serve()); + } + + private void Serve() + { + log.Info("Session server is starting."); + responseSocket = new ResponseSocket(); + + switch (sessionType) + { + case eSessionType.Local: + responseSocket.Bind("inproc://opendiablo2-session"); + break; + case eSessionType.Server: + case eSessionType.Remote: + default: + throw new ApplicationException("This session type is currently unsupported."); + } + + OnJoinGame += OnJoinGameHandler; + + var proactor = new NetMQProactor(responseSocket, (socket, message) => + { + var bytes = message.First().ToByteArray(); + var frameType = (eMessageFrameType)bytes[0]; + var frameData = bytes.Skip(1).ToArray(); // TODO: Can we maybe use pointers? This seems wasteful + var messageFrame = getMessageFrame(frameType); + messageFrame.Data = frameData; + messageFrame.Process(socket, this); + }); + running = true; + WaitServerStartEvent.Set(); + resetEvent.WaitOne(); + proactor.Dispose(); + running = false; + responseSocket.Dispose(); + log.Info("Session server has stopped."); + } + + + public void Stop() + { + if (!running) + return; + + resetEvent.Set(); + } + + public void Dispose() + { + Stop(); + } + + private void Send(NetMQSocket target, IMessageFrame messageFrame) + { + var attr = messageFrame.GetType().GetCustomAttributes(true).First(x => typeof(MessageFrameAttribute).IsAssignableFrom(x.GetType())) as MessageFrameAttribute; + responseSocket.SendFrame(new byte[] { (byte)attr.FrameType }.Concat(messageFrame.Data).ToArray()); + } + + private void OnJoinGameHandler(object sender, Guid playerId, string playerName) + { + // TODO: Try to make this less stupid + Send(sender as NetMQSocket, new MFSetSeed(gameSeed)); + } + } +} diff --git a/OpenDiablo2/OpenDiablo2.csproj b/OpenDiablo2/OpenDiablo2.csproj index e6ada74e..09886d24 100644 --- a/OpenDiablo2/OpenDiablo2.csproj +++ b/OpenDiablo2/OpenDiablo2.csproj @@ -5,7 +5,7 @@ Debug AnyCPU {2B0CF1AC-06DD-4322-AE8B-FF8A8C70A3CD} - WinExe + Exe OpenDiablo2 OpenDiablo2 v4.6.1 diff --git a/OpenDiablo2/Program.cs b/OpenDiablo2/Program.cs index 4d9a559c..417f9908 100644 --- a/OpenDiablo2/Program.cs +++ b/OpenDiablo2/Program.cs @@ -34,9 +34,20 @@ namespace OpenDiablo2 try { #endif - BuildContainer() - .Resolve() - .Run(); + var container = BuildContainer(); + try + { + using (var gameEngine = container.Resolve()) + { + gameEngine.Run(); + } + } + finally + { + container.Dispose(); + } + + #if !DEBUG } catch (Exception ex) diff --git a/README.md b/README.md index a9727f7a..86a5ba29 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ You need to have MonoDevelop installed, as well as any depenencies for that. You ## Command Line Parameters | Long Name | Description | | ------------ | ------------------------------------------------------------ | -| --datapath | (-d) Defines the path where the data files can be found | +| --datapath | (-p) Defines the path where the data files can be found | | --hwmouse | Use the hardware mouse instead of software | | --mousescale | When hardware mouse is enabled, this defines the pixel scale of the mouse. No effect for software mode | | --fullscreen | (-f) When set, the game launches in full screen mode at 800x600. |