From d911a76cadf368b84eba815054061460e1f649db Mon Sep 17 00:00:00 2001 From: Tim Sarbin Date: Sat, 15 Dec 2018 17:42:36 -0500 Subject: [PATCH] Restructured how movement works. Restructured network protocols. Player ID is now GUID based. --- .../Interfaces/Drawing/ICharacterRenderer.cs | 4 - OpenDiablo2.Common/Interfaces/IGameState.cs | 2 - OpenDiablo2.Common/Interfaces/IMapEngine.cs | 12 -- OpenDiablo2.Common/Interfaces/IMapRenderer.cs | 15 ++ OpenDiablo2.Common/Interfaces/IMobLocation.cs | 46 +++++ .../Interfaces/MessageBus/IMessageFrame.cs | 4 +- .../MessageBus/ISessionEventProvider.cs | 10 +- .../Interfaces/MessageBus/ISessionManager.cs | 3 +- OpenDiablo2.Common/Models/LocationDetails.cs | 96 +++++++++ OpenDiablo2.Common/Models/Mobs/MobState.cs | 9 + OpenDiablo2.Common/Models/Mobs/PlayerState.cs | 8 +- OpenDiablo2.Common/Models/PlayerInfo.cs | 91 +++++---- .../Models/PlayerLocationDetails.cs | 78 ------- OpenDiablo2.Common/OpenDiablo2.Common.csproj | 6 +- .../Services/MobMovementService.cs | 104 ++++++++++ OpenDiablo2.Core/AutofacModule.cs | 4 +- OpenDiablo2.Core/GameState/GameState.cs | 59 +++--- OpenDiablo2.Core/Map Engine/MapEngine.cs | 184 ----------------- OpenDiablo2.Core/Map Engine/MapRenderer.cs | 193 ++++++++++++++++++ OpenDiablo2.Core/OpenDiablo2.Core.csproj | 2 +- OpenDiablo2.Core/UI/GameHUD.cs | 20 +- OpenDiablo2.Core/UI/MiniPanel.cs | 13 +- OpenDiablo2.Core/UI/Panels/InventoryPanel.cs | 54 +++-- OpenDiablo2.GameServer/GameServer.cs | 34 +-- .../OpenDiablo2.GameServer.csproj | 1 + OpenDiablo2.MapGenerators/BloodMoor.cs | 1 + OpenDiablo2.SDL2/SDL2CharacterRenderer.cs | 58 ++++-- OpenDiablo2.SDL2/SDL2RenderWindow.cs | 8 +- OpenDiablo2.Scenes/Game.cs | 56 ++++- .../Message Frames/Client/MFJoinGame.cs | 38 +--- .../Message Frames/Client/MFMoveRequest.cs | 38 ++-- .../Message Frames/Server/MFFocusOnPlayer.cs | 21 +- .../Message Frames/Server/MFLocatePlayers.cs | 35 ++-- .../Message Frames/Server/MFPlayerInfo.cs | 37 ++-- .../Message Frames/Server/MFSetSeed.cs | 11 + .../OpenDiablo2.ServiceBus.csproj | 1 + OpenDiablo2.ServiceBus/SessionManager.cs | 35 ++-- OpenDiablo2.ServiceBus/SessionServer.cs | 86 ++++++-- 38 files changed, 923 insertions(+), 554 deletions(-) delete mode 100644 OpenDiablo2.Common/Interfaces/IMapEngine.cs create mode 100644 OpenDiablo2.Common/Interfaces/IMapRenderer.cs create mode 100644 OpenDiablo2.Common/Interfaces/IMobLocation.cs create mode 100644 OpenDiablo2.Common/Models/LocationDetails.cs delete mode 100644 OpenDiablo2.Common/Models/PlayerLocationDetails.cs create mode 100644 OpenDiablo2.Common/Services/MobMovementService.cs delete mode 100644 OpenDiablo2.Core/Map Engine/MapEngine.cs create mode 100644 OpenDiablo2.Core/Map Engine/MapRenderer.cs diff --git a/OpenDiablo2.Common/Interfaces/Drawing/ICharacterRenderer.cs b/OpenDiablo2.Common/Interfaces/Drawing/ICharacterRenderer.cs index f431f8ba..38aeaef2 100644 --- a/OpenDiablo2.Common/Interfaces/Drawing/ICharacterRenderer.cs +++ b/OpenDiablo2.Common/Interfaces/Drawing/ICharacterRenderer.cs @@ -8,10 +8,6 @@ namespace OpenDiablo2.Common.Interfaces.Drawing public interface ICharacterRenderer : IDisposable { Guid UID { get; set; } - PlayerLocationDetails LocationDetails { get; set; } - eHero Hero { get; set; } - PlayerEquipment Equipment { get; set; } - eMobMode MobMode { get; set; } void Update(long ms); void Render(int pixelOffsetX, int pixelOffsetY); diff --git a/OpenDiablo2.Common/Interfaces/IGameState.cs b/OpenDiablo2.Common/Interfaces/IGameState.cs index 4beddbe4..d446fdb6 100644 --- a/OpenDiablo2.Common/Interfaces/IGameState.cs +++ b/OpenDiablo2.Common/Interfaces/IGameState.cs @@ -19,8 +19,6 @@ namespace OpenDiablo2.Common.Interfaces ItemInstance SelectedItem { get; } void SelectItem(ItemInstance item); - int CameraOffset { get; set; } - void Initialize(string characterName, eHero hero, eSessionType sessionType); void Update(long ms); IEnumerable GetMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType); diff --git a/OpenDiablo2.Common/Interfaces/IMapEngine.cs b/OpenDiablo2.Common/Interfaces/IMapEngine.cs deleted file mode 100644 index 40cc8c6e..00000000 --- a/OpenDiablo2.Common/Interfaces/IMapEngine.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Drawing; - -namespace OpenDiablo2.Common.Interfaces -{ - public interface IMapEngine - { - int FocusedPlayerId { get; set; } - PointF CameraLocation { get; set; } - void Update(long ms); - void Render(); - } -} diff --git a/OpenDiablo2.Common/Interfaces/IMapRenderer.cs b/OpenDiablo2.Common/Interfaces/IMapRenderer.cs new file mode 100644 index 00000000..a51fb76d --- /dev/null +++ b/OpenDiablo2.Common/Interfaces/IMapRenderer.cs @@ -0,0 +1,15 @@ +using System; +using System.Drawing; + +namespace OpenDiablo2.Common.Interfaces +{ + public interface IMapRenderer + { + Guid FocusedPlayerId { get; set; } + int CameraOffset { get; set; } + PointF CameraLocation { get; set; } + void Update(long ms); + void Render(); + PointF GetCellPositionAt(int x, int y); + } +} diff --git a/OpenDiablo2.Common/Interfaces/IMobLocation.cs b/OpenDiablo2.Common/Interfaces/IMobLocation.cs new file mode 100644 index 00000000..97205b80 --- /dev/null +++ b/OpenDiablo2.Common/Interfaces/IMobLocation.cs @@ -0,0 +1,46 @@ +/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System.Collections.Generic; +using System.Drawing; +using OpenDiablo2.Common.Enums; + +namespace OpenDiablo2.Common.Interfaces +{ + public interface IMobLocation + { + float X { get; set; } + float Y { get; set; } + float MovementSpeed { get; set; } + int MovementDirection { get; set; } + List Waypoints { get; set; } + eMovementType MovementType { get; set; } + } + + public static class MobLocationHelper + { + public static void CopyMobLocationDetailsTo(this IMobLocation source, IMobLocation dest) + { + dest.X = source.X; + dest.Y = source.Y; + dest.MovementSpeed = source.MovementSpeed; + dest.MovementDirection = source.MovementDirection; + dest.Waypoints = source.Waypoints; // TODO: do we need to do a literaly copy here? + dest.MovementType = source.MovementType; + } + } + +} diff --git a/OpenDiablo2.Common/Interfaces/MessageBus/IMessageFrame.cs b/OpenDiablo2.Common/Interfaces/MessageBus/IMessageFrame.cs index 1f5318c6..9427b527 100644 --- a/OpenDiablo2.Common/Interfaces/MessageBus/IMessageFrame.cs +++ b/OpenDiablo2.Common/Interfaces/MessageBus/IMessageFrame.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,7 +9,8 @@ namespace OpenDiablo2.Common.Interfaces { public interface IMessageFrame { - byte[] Data { get; set; } + void LoadFrom(BinaryReader br); + void WriteTo(BinaryWriter bw); void Process(int clientHash, ISessionEventProvider sessionEventProvider); } } diff --git a/OpenDiablo2.Common/Interfaces/MessageBus/ISessionEventProvider.cs b/OpenDiablo2.Common/Interfaces/MessageBus/ISessionEventProvider.cs index 50eecdc6..89d3aef7 100644 --- a/OpenDiablo2.Common/Interfaces/MessageBus/ISessionEventProvider.cs +++ b/OpenDiablo2.Common/Interfaces/MessageBus/ISessionEventProvider.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Drawing; using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Models; @@ -6,10 +8,10 @@ namespace OpenDiablo2.Common.Interfaces { public delegate void OnSetSeedEvent(int clientHash, int seed); public delegate void OnJoinGameEvent(int clientHash, eHero heroType, string playerName); - public delegate void OnLocatePlayersEvent(int clientHash, IEnumerable playerLocationDetails); + public delegate void OnLocatePlayersEvent(int clientHash, IEnumerable playerLocationDetails); public delegate void OnPlayerInfoEvent(int clientHash, IEnumerable playerInfo); - public delegate void OnFocusOnPlayer(int clientHash, int playerId); - public delegate void OnMoveRequest(int clientHash, byte direction, eMovementType movementType); + public delegate void OnFocusOnPlayer(int clientHash, Guid playerId); + public delegate void OnMoveRequest(int clientHash, PointF targetCell, eMovementType movementType); public interface ISessionEventProvider { diff --git a/OpenDiablo2.Common/Interfaces/MessageBus/ISessionManager.cs b/OpenDiablo2.Common/Interfaces/MessageBus/ISessionManager.cs index fcf3b85e..b04ea54d 100644 --- a/OpenDiablo2.Common/Interfaces/MessageBus/ISessionManager.cs +++ b/OpenDiablo2.Common/Interfaces/MessageBus/ISessionManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -14,6 +15,6 @@ namespace OpenDiablo2.Common.Interfaces void Stop(); void JoinGame(string playerName, eHero heroType); - void MoveRequest(byte direction, eMovementType movementType); + void MoveRequest(PointF targetCell, eMovementType movementType); } } diff --git a/OpenDiablo2.Common/Models/LocationDetails.cs b/OpenDiablo2.Common/Models/LocationDetails.cs new file mode 100644 index 00000000..6f4066da --- /dev/null +++ b/OpenDiablo2.Common/Models/LocationDetails.cs @@ -0,0 +1,96 @@ +/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using OpenDiablo2.Common.Enums; +using OpenDiablo2.Common.Interfaces; +using OpenDiablo2.Common.Models.Mobs; + +namespace OpenDiablo2.Common.Models +{ + public sealed class LocationDetails : IMobLocation + { + public Guid UID { get; set; } + public float X { get; set; } + public float Y { get; set; } + public eMovementType MovementType { get; set; } + public int MovementDirection { get; set; } + public float MovementSpeed { get; set; } + public List Waypoints { get; set; } = new List(); + // TODO: They may not be on the same 'anchor map'... + + public void WriteBytes(BinaryWriter bw) + { + bw.Write(UID.ToByteArray()); + bw.Write((Single)X); + bw.Write((Single)Y); + bw.Write((Int32)MovementDirection); + bw.Write((byte)MovementType); + bw.Write((Single)MovementSpeed); + bw.Write((Int16)(Waypoints?.Count ?? 0)); + for (var i = 0; i < Waypoints.Count; i++) + { + bw.Write((Single)Waypoints[i].X); + bw.Write((Single)Waypoints[i].Y); + } + } + + public static LocationDetails FromBytes(BinaryReader br) + { + var result = new LocationDetails + { + UID = new Guid(br.ReadBytes(16)), + X = br.ReadSingle(), + Y = br.ReadSingle(), + MovementDirection = br.ReadInt32(), + MovementType = (eMovementType)br.ReadByte(), + MovementSpeed = br.ReadSingle() + }; + var numWaypoints = br.ReadInt16(); + result.Waypoints = new List(numWaypoints); + for(var i = 0; i < numWaypoints; i++) + { + result.Waypoints.Add(new PointF + { + X = br.ReadSingle(), + Y = br.ReadSingle() + }); + } + + return result; + } + } + + public static class PlayerLocationDetailsExtensions + { + public static LocationDetails ToPlayerLocationDetails(this PlayerState source) + { + var result = new LocationDetails + { + UID = source.UID, + X = source.GetPosition().X, + Y = source.GetPosition().Y, + Waypoints = source.Waypoints, + MovementType = source.MovementType, + MovementSpeed = (source.MovementType == eMovementType.Running ? source.GetRunVelocity() : source.GetWalkVeloicty()) / 4f + }; + return result; + } + } +} diff --git a/OpenDiablo2.Common/Models/Mobs/MobState.cs b/OpenDiablo2.Common/Models/Mobs/MobState.cs index cea83a1b..1bab3904 100644 --- a/OpenDiablo2.Common/Models/Mobs/MobState.cs +++ b/OpenDiablo2.Common/Models/Mobs/MobState.cs @@ -11,9 +11,18 @@ namespace OpenDiablo2.Common.Models.Mobs public readonly int Id; public bool Alive { get; protected set; } = true; + /// The X tile location of the mob public float X { get; set; } = 0; + + /// The Y tile location of the mob public float Y { get; set; } = 0; + /// The speed of the mob (in units per second) + public float MovementSpeed { get; set; } + + /// Represents the movement direction of the mob (16 angular segments) + public int MovementDirection { get; set; } + protected Stat Health; protected Dictionary Resistances = new Dictionary(); diff --git a/OpenDiablo2.Common/Models/Mobs/PlayerState.cs b/OpenDiablo2.Common/Models/Mobs/PlayerState.cs index 4d117bfc..eab1d896 100644 --- a/OpenDiablo2.Common/Models/Mobs/PlayerState.cs +++ b/OpenDiablo2.Common/Models/Mobs/PlayerState.cs @@ -16,13 +16,15 @@ using System; using System.Collections.Generic; +using System.Drawing; using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Enums.Mobs; +using OpenDiablo2.Common.Interfaces; using OpenDiablo2.Common.Interfaces.Mobs; namespace OpenDiablo2.Common.Models.Mobs { - public class PlayerState : MobState + public class PlayerState : MobState, IMobLocation { private readonly IHeroTypeConfig HeroTypeConfig; private readonly ILevelExperienceConfig ExperienceConfig; @@ -30,8 +32,8 @@ namespace OpenDiablo2.Common.Models.Mobs public Guid UID { get; protected set; } = Guid.NewGuid(); public eHero HeroType { get; protected set; } public int ClientHash { get; protected set; } - public byte MovementDirection { get; set; } = 0; - public eMovementType MovementType { get; set; } = eMovementType.Stopped; // TODO: This needs to mess with MobMode somehow + public List Waypoints { get; set; } = new List(); + public eMovementType MovementType { get; set; } = eMovementType.Stopped; public eMobMode MobMode { get; set; } = eMobMode.PlayerTownWalk; public PlayerEquipment Equipment { get; set; } = new PlayerEquipment(); diff --git a/OpenDiablo2.Common/Models/PlayerInfo.cs b/OpenDiablo2.Common/Models/PlayerInfo.cs index 74d0c3f7..337afe28 100644 --- a/OpenDiablo2.Common/Models/PlayerInfo.cs +++ b/OpenDiablo2.Common/Models/PlayerInfo.cs @@ -16,60 +16,75 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; -using System.Text; using OpenDiablo2.Common.Enums; +using OpenDiablo2.Common.Interfaces; using OpenDiablo2.Common.Models.Mobs; namespace OpenDiablo2.Common.Models { - public sealed class PlayerInfo + public sealed class PlayerInfo : IMobLocation { public Guid UID { get; set; } public string Name { get; set; } public eHero Hero { get; set; } public eMobMode MobMode { get; set; } - public PlayerLocationDetails LocationDetails { get; set; } public PlayerEquipment Equipment { get; set; } + public float X { get; set; } + public float Y { get; set; } + public float MovementSpeed { get; set; } + public int MovementDirection { get; set; } + public List Waypoints { get; set; } + public eMovementType MovementType { get; set; } - public byte[] GetBytes() + public void WriteBytes(BinaryWriter writer) { - using (var stream = new MemoryStream()) - using (var writer = new BinaryWriter(stream)) + writer.Write(UID.ToByteArray()); + writer.Write(Name); + writer.Write((byte)Hero); + writer.Write((byte)MobMode); + writer.Write(Equipment); + writer.Write((Single)X); + writer.Write((Single)Y); + writer.Write((Single)MovementSpeed); + writer.Write((byte)MovementDirection); + writer.Write((byte)MovementType); + writer.Write((UInt16)Waypoints.Count); + foreach (var waypoint in Waypoints) { - writer.Write((byte)Hero); - writer.Write((byte)MobMode); - writer.Write(Name); - writer.Write(Equipment); - writer.Write(LocationDetails.GetBytes()); - writer.Write(UID.ToByteArray()); - - return stream.ToArray(); + writer.Write((Single)waypoint.X); + writer.Write((Single)waypoint.Y); } } - public static PlayerInfo FromBytes(byte[] data, int offset = 0) + public static PlayerInfo FromBytes(BinaryReader br) { - using (var stream = new MemoryStream(data)) - using (var reader = new BinaryReader(stream)) + var result = new PlayerInfo { - reader.ReadBytes(offset); // Skip + UID = new Guid(br.ReadBytes(16)), + Name = br.ReadString(), + Hero = (eHero)br.ReadByte(), + MobMode = (eMobMode)br.ReadByte(), + Equipment = br.ReadPlayerEquipment(), + X = br.ReadSingle(), + Y = br.ReadSingle(), + MovementSpeed = br.ReadSingle(), + MovementDirection = br.ReadByte(), + MovementType = (eMovementType)br.ReadByte() + }; - var result = new PlayerInfo + var numWaypoints = br.ReadUInt16(); + result.Waypoints = new List(); + for (var i = 0; i < numWaypoints; i++) + result.Waypoints.Add(new PointF { - Hero = (eHero)reader.ReadByte(), - MobMode = (eMobMode)reader.ReadByte(), - Name = reader.ReadString(), - Equipment = reader.ReadPlayerEquipment(), - LocationDetails = PlayerLocationDetails.FromBytes(reader.ReadBytes(PlayerLocationDetails.SizeInBytes)), - UID = new Guid(reader.ReadBytes(16)) - }; + X = br.ReadSingle(), + Y= br.ReadSingle() + }); - return result; - } + return result; } - - public int SizeInBytes => 8 + Encoding.UTF8.GetByteCount(Name) + PlayerLocationDetails.SizeInBytes + 16; } @@ -80,16 +95,16 @@ namespace OpenDiablo2.Common.Models => new PlayerInfo { UID = source.UID, - Hero = source.HeroType, - LocationDetails = new PlayerLocationDetails - { - PlayerId = source.Id, - PlayerX = source.GetPosition().X, - PlayerY = source.GetPosition().Y, - }, Name = source.Name, + Hero = source.HeroType, MobMode = source.MobMode, - Equipment = source.Equipment + Equipment = source.Equipment, + X = source.X, + Y = source.Y, + MovementSpeed = source.MovementSpeed, + MovementDirection = source.MovementDirection, + MovementType = source.MovementType, + Waypoints = source.Waypoints }; } } diff --git a/OpenDiablo2.Common/Models/PlayerLocationDetails.cs b/OpenDiablo2.Common/Models/PlayerLocationDetails.cs deleted file mode 100644 index 03a7412d..00000000 --- a/OpenDiablo2.Common/Models/PlayerLocationDetails.cs +++ /dev/null @@ -1,78 +0,0 @@ -/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using System; -using System.Collections.Generic; -using OpenDiablo2.Common.Enums; -using OpenDiablo2.Common.Models.Mobs; - -namespace OpenDiablo2.Common.Models -{ - public sealed class PlayerLocationDetails - { - public int PlayerId { get; set; } - public float PlayerX { get; set; } - public float PlayerY { get; set; } - public eMovementType MovementType { get; set; } - public int MovementDirection { get; set; } - public float MovementSpeed { get; set; } - // TODO: They may not be on the same 'anchor map'... - - public byte[] GetBytes() - { - var result = new List(); - result.AddRange(BitConverter.GetBytes(PlayerId)); - result.AddRange(BitConverter.GetBytes(PlayerX)); - result.AddRange(BitConverter.GetBytes(PlayerY)); - result.AddRange(BitConverter.GetBytes(MovementDirection)); - result.AddRange(BitConverter.GetBytes((byte)MovementType)); - result.AddRange(BitConverter.GetBytes(MovementSpeed)); - return result.ToArray(); - } - - public static PlayerLocationDetails FromBytes(byte[] data, int offset = 0) - { - var result = new PlayerLocationDetails - { - PlayerId = BitConverter.ToInt32(data, offset + 0), - PlayerX = BitConverter.ToSingle(data, offset + 4), - PlayerY = BitConverter.ToSingle(data, offset + 8), - MovementDirection = BitConverter.ToInt32(data, offset + 12), - MovementType = (eMovementType)data[offset + 16], - MovementSpeed = BitConverter.ToSingle(data, offset + 18) - }; - return result; - } - public static int SizeInBytes => 22; - } - - public static class PlayerLocationDetailsExtensions - { - public static PlayerLocationDetails ToPlayerLocationDetails(this PlayerState source) - { - var result = new PlayerLocationDetails - { - PlayerId = source.Id, - PlayerX = source.GetPosition().X, - PlayerY = source.GetPosition().Y, - MovementType = source.MovementType, - MovementDirection = source.MovementDirection, - MovementSpeed = (source.MovementType == eMovementType.Running ? source.GetRunVelocity() : source.GetWalkVeloicty()) / 4f - }; - return result; - } - } -} diff --git a/OpenDiablo2.Common/OpenDiablo2.Common.csproj b/OpenDiablo2.Common/OpenDiablo2.Common.csproj index 47fc1aa0..dd85d7d6 100644 --- a/OpenDiablo2.Common/OpenDiablo2.Common.csproj +++ b/OpenDiablo2.Common/OpenDiablo2.Common.csproj @@ -94,6 +94,7 @@ + @@ -123,7 +124,7 @@ - + @@ -134,7 +135,7 @@ - + @@ -187,6 +188,7 @@ + diff --git a/OpenDiablo2.Common/Services/MobMovementService.cs b/OpenDiablo2.Common/Services/MobMovementService.cs new file mode 100644 index 00000000..ff27537f --- /dev/null +++ b/OpenDiablo2.Common/Services/MobMovementService.cs @@ -0,0 +1,104 @@ +/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Linq; +using OpenDiablo2.Common.Enums; +using OpenDiablo2.Common.Interfaces; + +namespace OpenDiablo2.Common.Services +{ + public sealed class MobMovementService + { + const double Rad2Deg = 180.0 / Math.PI; + IMobLocation mobLocation; + + public MobMovementService(IMobLocation mobLocation) + { + this.mobLocation = mobLocation; + } + + /// Moves the mob along the set of waypoints + /// The amount of time to process + public void CalculateMovement(float seconds) + { + if (mobLocation.Waypoints.Count == 0) + return; + + if (!mobLocation.Waypoints.Any()) + return; + + // Determine how far we can move this round + var moveRate = seconds * mobLocation.MovementSpeed; + + // Keep iterating waypoints while we can + while (moveRate > 0 && mobLocation.Waypoints.Any()) + { + // Determine the next target location + var targetX = mobLocation.Waypoints.First().X; + var targetY = mobLocation.Waypoints.First().Y; + + // If we're already on this spot, we're done + if (targetX == mobLocation.X && targetY == mobLocation.Y) + { + mobLocation.Waypoints.RemoveAt(0); + continue; + } + + // Calculate our direction + var cursorDirection = Math.Round(Math.Atan2(targetY - mobLocation.Y, targetX - mobLocation.X) * Rad2Deg); + if (cursorDirection < 0) + cursorDirection += 360; + var actualDirection = (byte)(cursorDirection / 22); + if (actualDirection >= 16) + actualDirection -= 16; + mobLocation.MovementDirection = actualDirection; + + // Determine how far we are away from the waypoint + var maxDistance = (float)Math.Sqrt(Math.Pow((targetX - mobLocation.X), 2) + Math.Pow((targetY - mobLocation.Y), 2)); + + // If we can move beyond the distance we need to to hit the final waypoint, we are doing moving + if (moveRate >= maxDistance && mobLocation.Waypoints.Count == 1) + { + mobLocation.X = mobLocation.Waypoints.First().X; + mobLocation.Y = mobLocation.Waypoints.First().Y; + mobLocation.Waypoints.Clear(); + break; + } + + // Calculate how far we're actually going to move + var distance = (float)Math.Min(maxDistance, moveRate); + + // Calculate the point to land on between source and target locations + float tx = targetX - mobLocation.X; + float ty = targetY - mobLocation.Y; + mobLocation.X = (mobLocation.X + distance * tx / maxDistance); + mobLocation.Y = (mobLocation.Y + distance * ty / maxDistance); + + // Subtract out the distance we moved (in case we need to move through another node) + moveRate = (float)Math.Max(0, moveRate - distance); + + // If we are on the waypoint, remove it from the list + if (targetX == mobLocation.X && targetY == mobLocation.Y) + mobLocation.Waypoints.RemoveAt(0); + } + + // Stop walking if we have run out of waypoints + if (mobLocation.Waypoints.Count == 0) + mobLocation.MovementType = eMovementType.Stopped; + } + } +} diff --git a/OpenDiablo2.Core/AutofacModule.cs b/OpenDiablo2.Core/AutofacModule.cs index 0c028c39..abd8f635 100644 --- a/OpenDiablo2.Core/AutofacModule.cs +++ b/OpenDiablo2.Core/AutofacModule.cs @@ -37,8 +37,8 @@ namespace OpenDiablo2.Core builder.RegisterType().As().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().SingleInstance(); builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); - builder.RegisterType().As().InstancePerDependency(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().InstancePerDependency(); builder.RegisterType().As().InstancePerDependency(); builder.RegisterType().AsImplementedInterfaces().InstancePerDependency(); diff --git a/OpenDiablo2.Core/GameState/GameState.cs b/OpenDiablo2.Core/GameState/GameState.cs index 2bd685cb..931c0cfe 100644 --- a/OpenDiablo2.Core/GameState/GameState.cs +++ b/OpenDiablo2.Core/GameState/GameState.cs @@ -1,4 +1,20 @@ -using System; +/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -7,6 +23,7 @@ using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Exceptions; using OpenDiablo2.Common.Interfaces; using OpenDiablo2.Common.Models; +using OpenDiablo2.Common.Services; using OpenDiablo2.Core.Map_Engine; namespace OpenDiablo2.Core.GameState_ @@ -22,10 +39,13 @@ namespace OpenDiablo2.Core.GameState_ private readonly IRenderWindow renderWindow; private readonly ISoundProvider soundProvider; private readonly IMPQProvider mpqProvider; - private readonly Func getMapEngine; + private readonly Func getMapEngine; private readonly Func getSessionManager; private readonly Func getRandomizedMapGenerator; + readonly private IMouseCursor originalMouseCursor; + + private float animationTime; private List mapInfo; private readonly List mapDataLookup; @@ -35,14 +55,9 @@ namespace OpenDiablo2.Core.GameState_ public string MapName { get; private set; } public Palette CurrentPalette => paletteProvider.PaletteTable[$"ACT{Act}"]; public List PlayerInfos { get; private set; } - - readonly private IMouseCursor originalMouseCursor; - public int Seed { get; internal set; } - public ItemInstance SelectedItem { get; internal set; } public object ThreadLocker { get; } = new object(); - public int CameraOffset { get; set; } = 0; IEnumerable IGameState.PlayerInfos => PlayerInfos; @@ -57,7 +72,7 @@ namespace OpenDiablo2.Core.GameState_ IRenderWindow renderWindow, ISoundProvider soundProvider, IMPQProvider mpqProvider, - Func getMapEngine, + Func getMapEngine, Func getSessionManager, Func getRandomizedMapGenerator ) @@ -89,26 +104,28 @@ namespace OpenDiablo2.Core.GameState_ sessionManager.OnFocusOnPlayer += OnFocusOnPlayer; mapInfo = new List(); - + sceneManager.ChangeScene(eSceneType.Game); sessionManager.JoinGame(characterName, hero); } - private void OnFocusOnPlayer(int clientHash, int playerId) + private void OnFocusOnPlayer(int clientHash, Guid playerId) => getMapEngine().FocusedPlayerId = playerId; private void OnPlayerInfo(int clientHash, IEnumerable playerInfo) => PlayerInfos = playerInfo.ToList(); - private void OnLocatePlayers(int clientHash, IEnumerable playerLocationDetails) + private void OnLocatePlayers(int clientHash, IEnumerable playerLocationDetails) { foreach (var player in PlayerInfos) { - var details = playerLocationDetails.FirstOrDefault(x => x.PlayerId == player.LocationDetails.PlayerId); + + var details = playerLocationDetails.FirstOrDefault(x => x.UID == player.UID); + if (details == null) continue; - player.LocationDetails = details; + details.CopyMobLocationDetailsTo(player); } } @@ -120,7 +137,7 @@ namespace OpenDiablo2.Core.GameState_ } public int HasMap(int cellX, int cellY) - => mapInfo.Count(z => (cellX >= z.TileLocation.Left) && (cellX < z.TileLocation.Right) + => mapInfo.Count(z => (cellX >= z.TileLocation.Left) && (cellX < z.TileLocation.Right) && (cellY >= z.TileLocation.Top) && (cellY < z.TileLocation.Bottom)); public IEnumerable GetMapSizes(int cellX, int cellY) @@ -157,7 +174,7 @@ namespace OpenDiablo2.Core.GameState_ CellInfo = new Dictionary(), TileLocation = new Rectangle(origin, new Size(fileData.Width - 1, fileData.Height - 1)) }; - + return result; } @@ -283,7 +300,7 @@ namespace OpenDiablo2.Core.GameState_ mi = mapInfo[i]; break; } - + } if (mi == null) return null; @@ -471,16 +488,8 @@ namespace OpenDiablo2.Core.GameState_ private void UpdatePlayer(PlayerInfo player, float seconds) { - if (player.LocationDetails.MovementType == eMovementType.Stopped) - return; + (new MobMovementService(player)).CalculateMovement(seconds); - var rads = (float)player.LocationDetails.MovementDirection * 22 * (float)Deg2Rad; - - var moveX = (float)Math.Cos(rads) * seconds * player.LocationDetails.MovementSpeed; - var moveY = (float)Math.Sin(rads) * seconds * player.LocationDetails.MovementSpeed; - - player.LocationDetails.PlayerX += moveX; - player.LocationDetails.PlayerY += moveY; } public void Dispose() diff --git a/OpenDiablo2.Core/Map Engine/MapEngine.cs b/OpenDiablo2.Core/Map Engine/MapEngine.cs deleted file mode 100644 index 309f4bee..00000000 --- a/OpenDiablo2.Core/Map Engine/MapEngine.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using OpenDiablo2.Common.Enums; -using OpenDiablo2.Common.Interfaces; -using OpenDiablo2.Common.Interfaces.Drawing; -using OpenDiablo2.Common.Models; - -namespace OpenDiablo2.Core.Map_Engine -{ - public sealed class MapEngine : IMapEngine - { - private readonly IGameState _gameState; - private readonly IRenderWindow _renderWindow; - - private readonly List _characterRenderers = new List(); - - public int FocusedPlayerId { get; set; } = 0; - - private PointF _cameraLocation = new PointF(); - public PointF CameraLocation - { - get => _cameraLocation; - set - { - // ReSharper disable once RedundantCheckBeforeAssignment (This is a false positive) - if (_cameraLocation == value) - return; - - _cameraLocation = value; - } - } - - private const int - CellSizeX = 160, - CellSizeY = 80, - CellSizeXHalf = 80, - CellSizeYHalf = 40; - - public MapEngine( - IGameState gameState, - IRenderWindow renderWindow, - ISessionManager sessionManager - ) - { - _gameState = gameState; - _renderWindow = renderWindow; - - sessionManager.OnPlayerInfo += OnPlayerInfo; - sessionManager.OnLocatePlayers += OnLocatePlayers; - } - - private void OnLocatePlayers(int clientHash, IEnumerable playerLocationDetails) - { - foreach (var loc in playerLocationDetails) - { - var cr = _characterRenderers.FirstOrDefault(x => x.LocationDetails.PlayerId == loc.PlayerId); - if (cr == null) - { - // TODO: Should we log this? - continue; - } - var newDirection = loc.MovementDirection != cr.LocationDetails.MovementDirection; - var stanceChanged = loc.MovementType != cr.LocationDetails.MovementType; - cr.LocationDetails = loc; - if (newDirection || stanceChanged) - cr.ResetAnimationData(); - } - } - - private void OnPlayerInfo(int clientHash, IEnumerable playerInfo) - { - // Remove character renderers for players that no longer exist... - _characterRenderers.RemoveAll(x => playerInfo.Any(z => z.UID == x.UID)); - - // Update existing character renderers - foreach (var cr in _characterRenderers) - { - var info = playerInfo.FirstOrDefault(x => x.UID == cr.UID); - if (info == null) - continue; - - // TODO: This shouldn't be necessary... - cr.LocationDetails = info.LocationDetails; - cr.MobMode = info.MobMode; - cr.Hero = info.Hero; - cr.Equipment = info.Equipment; - - } - - // Add character renderers for characters that now exist - foreach (var info in playerInfo.Where(x => _characterRenderers.All(z => x.UID != z.UID)).ToArray()) - { - var cr = _renderWindow.CreateCharacterRenderer(); - cr.UID = info.UID; - cr.LocationDetails = info.LocationDetails; - cr.MobMode = info.MobMode; - cr.Equipment = info.Equipment; - cr.Hero = info.Hero; - _characterRenderers.Add(cr); - cr.ResetAnimationData(); - } - } - - - private const int SkewX = 400; - private const int SkewY = 300; - - public void Render() - { - var xOffset = _gameState.CameraOffset; - - var cx = -(_cameraLocation.X - Math.Truncate(_cameraLocation.X)); - var cy = -(_cameraLocation.Y - Math.Truncate(_cameraLocation.Y)); - - for (var ty = -7; ty <= 9; ty++) - { - for (var tx = -8; tx <= 8; tx++) - { - var ax = (int)(tx + Math.Truncate(_cameraLocation.X)); - var ay = (int)(ty + Math.Truncate(_cameraLocation.Y)); - - var px = (tx - ty) * CellSizeXHalf; - var py = (tx + ty) * CellSizeYHalf; - - var ox = (cx - cy) * CellSizeXHalf; - var oy = (cx + cy) * CellSizeYHalf; - - - foreach (var cellInfo in _gameState.GetMapCellInfo(ax, ay, eRenderCellType.WallLower)) - _renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy + 80); - - foreach (var cellInfo in _gameState.GetMapCellInfo(ax, ay, eRenderCellType.Floor)) - _renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy); - - - foreach (var cellInfo in _gameState.GetMapCellInfo(ax, ay, eRenderCellType.WallNormal)) - _renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy + 80); - - foreach (var character in _characterRenderers.Where(x => - (int)Math.Truncate(x.LocationDetails.PlayerX) == ax && - (int)Math.Truncate(x.LocationDetails.PlayerY) == ay) - ) - { - var ptx = character.LocationDetails.PlayerX - Math.Truncate(_cameraLocation.X); - var pty = character.LocationDetails.PlayerY - Math.Truncate(_cameraLocation.Y); - - var ppx = (int)((ptx - pty) * CellSizeXHalf); - var ppy = (int)((ptx + pty) * CellSizeYHalf); - - character.Render(SkewX + (int)ppx + (int)ox + xOffset, SkewY + (int)ppy + (int)oy); - } - - foreach (var cellInfo in _gameState.GetMapCellInfo(ax, ay, eRenderCellType.Roof)) - _renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy - 80); - } - } - - - - - } - - - public void Update(long ms) - { - foreach (var character in _characterRenderers) - character.Update(ms); - - if (FocusedPlayerId == 0) - return; - - var player = _gameState.PlayerInfos.FirstOrDefault(x => x.LocationDetails.PlayerId == FocusedPlayerId); - if (player == null) - return; - - CameraLocation = new PointF(player.LocationDetails.PlayerX, player.LocationDetails.PlayerY); - - } - - - } -} diff --git a/OpenDiablo2.Core/Map Engine/MapRenderer.cs b/OpenDiablo2.Core/Map Engine/MapRenderer.cs new file mode 100644 index 00000000..77fea462 --- /dev/null +++ b/OpenDiablo2.Core/Map Engine/MapRenderer.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using OpenDiablo2.Common.Enums; +using OpenDiablo2.Common.Interfaces; +using OpenDiablo2.Common.Interfaces.Drawing; +using OpenDiablo2.Common.Models; + +namespace OpenDiablo2.Core.Map_Engine +{ + public sealed class MapRenderer : IMapRenderer + { + private readonly IGameState gameState; + private readonly IRenderWindow renderWindow; + private readonly Func getGameHud; + + private readonly List _characterRenderers = new List(); + + public Guid FocusedPlayerId { get; set; } = Guid.Empty; + public int CameraOffset { get; set; } + + private PointF _cameraLocation = new PointF(); + public PointF CameraLocation + { + get => _cameraLocation; + set + { + // ReSharper disable once RedundantCheckBeforeAssignment (This is a false positive) + if (_cameraLocation == value) + return; + + _cameraLocation = value; + } + } + + private const int + CellSizeX = 160, + CellSizeY = 80, + CellSizeXHalf = CellSizeX / 2, + CellSizeYHalf = CellSizeY / 2; + + public MapRenderer( + IGameState gameState, + IRenderWindow renderWindow, + ISessionManager sessionManager, + Func getGameHud + ) + { + this.gameState = gameState; + this.renderWindow = renderWindow; + this.getGameHud = getGameHud; + + sessionManager.OnPlayerInfo += OnPlayerInfo; + sessionManager.OnLocatePlayers += OnLocatePlayers; + } + + private void OnLocatePlayers(int clientHash, IEnumerable locationDetails) + { + foreach (var locationDetail in locationDetails) + { + var characterRenderer = _characterRenderers.FirstOrDefault(x => x.UID == locationDetail.UID); + var player = gameState.PlayerInfos.FirstOrDefault(x => x.UID == locationDetail.UID); + if (characterRenderer == null) + { + // TODO: Should we log this? + continue; + } + var newDirection = locationDetail.MovementDirection != player.MovementDirection; + var stanceChanged = locationDetail.MovementType != player.MovementType; + + locationDetail.CopyMobLocationDetailsTo(player); + + if (newDirection || stanceChanged) + characterRenderer.ResetAnimationData(); + } + } + + private void OnPlayerInfo(int clientHash, IEnumerable playerInfo) + { + // Remove character renderers for players that no longer exist... + _characterRenderers.RemoveAll(x => playerInfo.Any(z => z.UID == x.UID)); + + // Add character renderers for characters that now exist + foreach (var info in playerInfo.Where(x => _characterRenderers.All(z => x.UID != z.UID)).ToArray()) + { + var cr = renderWindow.CreateCharacterRenderer(); + cr.UID = info.UID; + _characterRenderers.Add(cr); + cr.ResetAnimationData(); + } + } + + + private const int SkewX = 400; + private const int SkewY = 300; + + public void Render() + { + var xOffset = (getGameHud().IsRightPanelVisible ? -200 : 0) + (getGameHud().IsLeftPanelVisible ? 200 : 0); + + var cx = -(_cameraLocation.X - Math.Truncate(_cameraLocation.X)); + var cy = -(_cameraLocation.Y - Math.Truncate(_cameraLocation.Y)); + + for (var ty = -7; ty <= 9; ty++) + { + for (var tx = -8; tx <= 8; tx++) + { + var ax = (int)(tx + Math.Truncate(_cameraLocation.X)); + var ay = (int)(ty + Math.Truncate(_cameraLocation.Y)); + + var px = (tx - ty) * CellSizeXHalf; + var py = (tx + ty) * CellSizeYHalf; + + var ox = (cx - cy) * CellSizeXHalf; + var oy = (cx + cy) * CellSizeYHalf; + + + foreach (var cellInfo in gameState.GetMapCellInfo(ax, ay, eRenderCellType.WallLower)) + renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy + 80); + + foreach (var cellInfo in gameState.GetMapCellInfo(ax, ay, eRenderCellType.Floor)) + renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy); + + + foreach (var cellInfo in gameState.GetMapCellInfo(ax, ay, eRenderCellType.WallNormal)) + renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy + 80); + + foreach (var player in gameState.PlayerInfos.Where(x => + (int)Math.Truncate(x.X) == ax && + (int)Math.Truncate(x.Y) == ay) + ) + { + var ptx = player.X - Math.Truncate(_cameraLocation.X); + var pty = player.Y - Math.Truncate(_cameraLocation.Y); + + var ppx = (int)((ptx - pty) * CellSizeXHalf); + var ppy = (int)((ptx + pty) * CellSizeYHalf); + + _characterRenderers.FirstOrDefault(x => x.UID == player.UID) + .Render(SkewX + (int)ppx + (int)ox + xOffset, SkewY + (int)ppy + (int)oy); + } + + foreach (var cellInfo in gameState.GetMapCellInfo(ax, ay, eRenderCellType.Roof)) + renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy - 80); + } + } + + + + + } + + + public void Update(long ms) + { + foreach (var character in _characterRenderers) + character.Update(ms); + + if (FocusedPlayerId == Guid.Empty) + return; + + var player = gameState.PlayerInfos.FirstOrDefault(x => x.UID == FocusedPlayerId); + if (player == null) + return; + + CameraLocation = new PointF(player.X, player.Y); + + } + + public PointF GetCellPositionAt(int x, int y) + { + var xOffset = (getGameHud().IsRightPanelVisible ? -200 : 0) + (getGameHud().IsLeftPanelVisible ? 200 : 0); + var mx = x - 400 - xOffset; + var my = y - 300; + return new PointF + { + X = (float) Math.Round((mx / (float) CellSizeXHalf) + (my / (float) CellSizeYHalf) / 2f, 1) + _cameraLocation.X, + Y = (float) Math.Round((my / (float) CellSizeYHalf) - (mx / (float) CellSizeXHalf) / 2f, 1) + _cameraLocation.Y + }; + } + /* + var mx = x - 400 - gameState.CameraOffset; + var my = y - 300; + + var tx = (mx / 60f + my / 40f) / 2f; + var ty = (my / 40f - (mx / 60f)) / 2f; + */ + + + + } +} diff --git a/OpenDiablo2.Core/OpenDiablo2.Core.csproj b/OpenDiablo2.Core/OpenDiablo2.Core.csproj index 5374f016..99efda36 100644 --- a/OpenDiablo2.Core/OpenDiablo2.Core.csproj +++ b/OpenDiablo2.Core/OpenDiablo2.Core.csproj @@ -66,7 +66,7 @@ - + diff --git a/OpenDiablo2.Core/UI/GameHUD.cs b/OpenDiablo2.Core/UI/GameHUD.cs index a2540690..2a1bd4cb 100644 --- a/OpenDiablo2.Core/UI/GameHUD.cs +++ b/OpenDiablo2.Core/UI/GameHUD.cs @@ -28,7 +28,6 @@ namespace OpenDiablo2.Core.UI private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private readonly IRenderWindow renderWindow; - private readonly IGameState gameState; private readonly IMouseInfoProvider mouseInfoProvider; private readonly IMiniPanel minipanel; @@ -42,14 +41,12 @@ namespace OpenDiablo2.Core.UI public GameHUD( IRenderWindow renderWindow, - IGameState gameState, IMouseInfoProvider mouseInfoProvider, Func createMiniPanel, Func createButton, Func createPanelFrame) { this.renderWindow = renderWindow; - this.gameState = gameState; this.mouseInfoProvider = mouseInfoProvider; minipanel = createMiniPanel(); minipanel.OnPanelToggled += TogglePanel; @@ -89,6 +86,8 @@ namespace OpenDiablo2.Core.UI public bool IsRightPanelVisible => RightPanel != null; public bool IsRunningEnabled => runButton.Toggled; + private bool needsCameraUpdate = true; + public void TogglePanel(ePanelType panelType) { TogglePanel(minipanel.GetPanel(panelType)); @@ -193,6 +192,12 @@ namespace OpenDiablo2.Core.UI public void Update() { + if (needsCameraUpdate) + { + needsCameraUpdate = false; + UpdateCameraOffset(); + } + runButton.Update(); menuButton.Update(); addStatButton.Update(); @@ -215,14 +220,9 @@ namespace OpenDiablo2.Core.UI } private void UpdateCameraOffset() - { - gameState.CameraOffset = (IsRightPanelVisible ? -200 : 0) + (IsLeftPanelVisible ? 200 : 0); - minipanel.UpdatePanelLocation(); - } + => minipanel.UpdatePanelLocation(); private void OnRunToggle(bool isToggled) - { - log.Debug("Run Toggle: " + isToggled); - } + => log.Debug("Run Toggle: " + isToggled); } } diff --git a/OpenDiablo2.Core/UI/MiniPanel.cs b/OpenDiablo2.Core/UI/MiniPanel.cs index b0981097..4e5e942f 100644 --- a/OpenDiablo2.Core/UI/MiniPanel.cs +++ b/OpenDiablo2.Core/UI/MiniPanel.cs @@ -32,24 +32,24 @@ namespace OpenDiablo2.Core.UI private readonly IRenderWindow renderWindow; private readonly IMouseInfoProvider mouseInfoProvider; - private readonly IGameState gameState; private readonly ISprite sprite; private readonly IReadOnlyList buttons; private readonly IEnumerable panels; + private readonly Func _getGameHud; private bool isPanelVisible; public event OnPanelToggledEvent OnPanelToggled; - public MiniPanel(IRenderWindow renderWindow, - IGameState gameState, + public MiniPanel(IRenderWindow renderWindow, + Func getGameHud, IMouseInfoProvider mouseInfoProvider, IEnumerable panels, Func createButton) { this.renderWindow = renderWindow; this.mouseInfoProvider = mouseInfoProvider; - this.gameState = gameState; + this._getGameHud = getGameHud; this.panels = panels; sprite = renderWindow.LoadSprite(ResourcePaths.MinipanelSmall, Palettes.Units, true); @@ -66,8 +66,6 @@ namespace OpenDiablo2.Core.UI } return newBtn; }).ToList().AsReadOnly(); - - UpdatePanelLocation(); } public void OnMenuToggle(bool isToggled) => isPanelVisible = isToggled; @@ -117,7 +115,8 @@ namespace OpenDiablo2.Core.UI public void UpdatePanelLocation() { - sprite.Location = new Point((800 - sprite.LocalFrameSize.Width + (int)(gameState.CameraOffset * 1.3f)) / 2, + var cameraOffset = (_getGameHud().IsRightPanelVisible ? -200 : 0) + (_getGameHud().IsLeftPanelVisible ? 200 : 0); + sprite.Location = new Point((800 - sprite.LocalFrameSize.Width + (int)(cameraOffset * 1.3f)) / 2, 526 + sprite.LocalFrameSize.Height); for (int i = 0; i < buttons.Count; i++) diff --git a/OpenDiablo2.Core/UI/Panels/InventoryPanel.cs b/OpenDiablo2.Core/UI/Panels/InventoryPanel.cs index d64b712f..764e904a 100644 --- a/OpenDiablo2.Core/UI/Panels/InventoryPanel.cs +++ b/OpenDiablo2.Core/UI/Panels/InventoryPanel.cs @@ -32,8 +32,9 @@ namespace OpenDiablo2.Core.UI public sealed class InventoryPanel : IInventoryPanel { private readonly IRenderWindow renderWindow; - private readonly IMapEngine mapEngine; + private readonly IMapRenderer mapRenderer; private readonly ISprite panelSprite; + private readonly IGameState gameState; public IItemContainer headContainer, torsoContainer, beltContainer, gloveContainer, bootsContainer, leftHandContainer, rightHandContainer, secondaryLeftHandContainer, secondaryRightHandContainer, @@ -45,14 +46,17 @@ namespace OpenDiablo2.Core.UI public InventoryPanel(IRenderWindow renderWindow, IItemManager itemManager, - IMapEngine mapEngine, + IMapRenderer mapRenderer, ISessionManager sessionManager, Func createItemContainer, + IGameState gameState, Func createButton) { this.renderWindow = renderWindow; - this.mapEngine = mapEngine; + this.mapRenderer = mapRenderer; + this.gameState = gameState; + sessionManager.OnFocusOnPlayer += OnFocusOnPlayer; sessionManager.OnPlayerInfo += OnPlayerInfo; panelSprite = renderWindow.LoadSprite(ResourcePaths.InventoryCharacterPanel, Palettes.Units, FrameType.GetOffset(), true); @@ -110,28 +114,38 @@ namespace OpenDiablo2.Core.UI bootsContainer.Location = panelSprite.Location + new Size(251, 178); } + private void OnPlayerInfo(int clientHash, IEnumerable playerInfo) + { + var currentPlayer = gameState.PlayerInfos.FirstOrDefault(x => x.UID == mapRenderer.FocusedPlayerId); + if (currentPlayer != null) + UpdateInventoryPanel(currentPlayer); + } + + private void OnFocusOnPlayer(int clientHash, Guid playerId) + { + var currentPlayer = gameState.PlayerInfos.FirstOrDefault(x => x.UID == playerId); + if (currentPlayer != null) + UpdateInventoryPanel(currentPlayer); + } + + private void UpdateInventoryPanel(PlayerInfo currentPlayer) + { + leftHandContainer.SetContainedItem(currentPlayer.Equipment.LeftArm); + rightHandContainer.SetContainedItem(currentPlayer.Equipment.RightArm); + torsoContainer.SetContainedItem(currentPlayer.Equipment.Torso); + headContainer.SetContainedItem(currentPlayer.Equipment.Head); + ringLeftContainer.SetContainedItem(currentPlayer.Equipment.LeftRing); + ringRightContainer.SetContainedItem(currentPlayer.Equipment.RightRing); + beltContainer.SetContainedItem(currentPlayer.Equipment.Belt); + neckContainer.SetContainedItem(currentPlayer.Equipment.Neck); + gloveContainer.SetContainedItem(currentPlayer.Equipment.Gloves); + } + public ePanelType PanelType => ePanelType.Inventory; public ePanelFrameType FrameType => ePanelFrameType.Right; public bool IsSecondaryEquipped { get; private set; } - public void OnPlayerInfo(int clientHash, IEnumerable playerInfos) - { - // TODO: Ugly hack. Update when we can look up by GUID - var currentPLayer = playerInfos.ToArray()[mapEngine.FocusedPlayerId]; - - leftHandContainer.SetContainedItem(currentPLayer.Equipment.LeftArm); - rightHandContainer.SetContainedItem(currentPLayer.Equipment.RightArm); - torsoContainer.SetContainedItem(currentPLayer.Equipment.Torso); - headContainer.SetContainedItem(currentPLayer.Equipment.Head); - ringLeftContainer.SetContainedItem(currentPLayer.Equipment.LeftRing); - ringRightContainer.SetContainedItem(currentPLayer.Equipment.RightRing); - beltContainer.SetContainedItem(currentPLayer.Equipment.Belt); - neckContainer.SetContainedItem(currentPLayer.Equipment.Neck); - gloveContainer.SetContainedItem(currentPLayer.Equipment.Gloves); - - } - public void Update() { if (IsSecondaryEquipped) diff --git a/OpenDiablo2.GameServer/GameServer.cs b/OpenDiablo2.GameServer/GameServer.cs index 7f333cba..2e76e911 100644 --- a/OpenDiablo2.GameServer/GameServer.cs +++ b/OpenDiablo2.GameServer/GameServer.cs @@ -1,9 +1,26 @@ -using System; +/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; using System.Collections.Generic; using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Interfaces; using OpenDiablo2.Common.Interfaces.Mobs; using OpenDiablo2.Common.Models.Mobs; +using OpenDiablo2.Common.Services; namespace OpenDiablo2.GameServer_ { @@ -63,7 +80,7 @@ namespace OpenDiablo2.GameServer_ // ... we should probably just fail here } - var newPlayer = new PlayerState(clientHash, playerName, mobManager.GetNextAvailableMobId(), 1, 20.0f, 20.0f, 10, 10, 10, 10, 0, heroType, + var newPlayer = new PlayerState(clientHash, playerName, mobManager.GetNextAvailableMobId(), 1, 20.5f, 20.5f, 10, 10, 10, 10, 0, heroType, heroConfig, expConfig); // This is probably not the right place to do this. @@ -92,19 +109,10 @@ namespace OpenDiablo2.GameServer_ private void UpdatePlayerMovement(PlayerState player, float seconds) { - // TODO: We need to do collision detection here... - if (player.MovementType == eMovementType.Stopped) + if (player.Waypoints.Count == 0) return; - var rads = (float)player.MovementDirection * 22 * (float)Deg2Rad; - - var speed = (player.MovementType == eMovementType.Running ? player.GetRunVelocity() : player.GetWalkVeloicty()) / 4f; - - var moveX = (float)Math.Cos(rads) * seconds * speed; - var moveY = (float)Math.Sin(rads) * seconds * speed; - - player.X += moveX; - player.Y += moveY; + (new MobMovementService(player)).CalculateMovement(seconds); } public void Dispose() diff --git a/OpenDiablo2.GameServer/OpenDiablo2.GameServer.csproj b/OpenDiablo2.GameServer/OpenDiablo2.GameServer.csproj index afba024c..1ab2ded2 100644 --- a/OpenDiablo2.GameServer/OpenDiablo2.GameServer.csproj +++ b/OpenDiablo2.GameServer/OpenDiablo2.GameServer.csproj @@ -43,6 +43,7 @@ ..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll + diff --git a/OpenDiablo2.MapGenerators/BloodMoor.cs b/OpenDiablo2.MapGenerators/BloodMoor.cs index cb607877..d69cddf9 100644 --- a/OpenDiablo2.MapGenerators/BloodMoor.cs +++ b/OpenDiablo2.MapGenerators/BloodMoor.cs @@ -8,6 +8,7 @@ using System.Linq; namespace OpenDiablo2.MapGenerators { + // TODO: Different difficulties have different sizes. We need to read this from levels.txt [RandomizedMap("Blood Moor")] public sealed class BloodMoor : IRandomizedMapGenerator { diff --git a/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs b/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs index 997aebf4..087b7f91 100644 --- a/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs +++ b/OpenDiablo2.SDL2/SDL2CharacterRenderer.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenDiablo2.Common; using OpenDiablo2.Common.Enums; @@ -44,13 +45,29 @@ namespace OpenDiablo2.SDL2_ static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); public Guid UID { get; set; } - public PlayerLocationDetails LocationDetails { get; set; } - public eHero Hero { get; set; } - public PlayerEquipment Equipment { get; set; } - public eMobMode MobMode { get; set; } public string ShieldCode { get; set; } public string WeaponCode { get; set; } + private PlayerInfo currentPlayer = null; + private PlayerInfo GetThisMob() + { + if (currentPlayer == null) + currentPlayer = gameState.PlayerInfos.First(x => x.UID == UID); + return currentPlayer; + } + + + private eMobMode MobMode + { + get => GetThisMob().MobMode; + set => GetThisMob().MobMode = value; + } + private IMobLocation MobLocation => GetThisMob(); + private eHero Hero => GetThisMob().Hero; + private PlayerEquipment Equipment => GetThisMob().Equipment; + + + private readonly IntPtr renderer; private readonly List directionCache = new List(); @@ -60,16 +77,20 @@ namespace OpenDiablo2.SDL2_ private readonly IResourceManager resourceManager; private readonly IPaletteProvider paletteProvider; + private readonly IGameState gameState; + private int lastDirection = -1; + private eMovementType lastMovementType = eMovementType.Stopped; private MPQCOF animationData; static readonly byte[] directionConversion = new byte[] { 3, 15, 4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14 }; - public SDL2CharacterRenderer(IntPtr renderer, IResourceManager resourceManager, IPaletteProvider paletteProvider) + public SDL2CharacterRenderer(IntPtr renderer, IResourceManager resourceManager, IPaletteProvider paletteProvider, IGameState gameState) { this.resourceManager = resourceManager; this.paletteProvider = paletteProvider; this.renderer = renderer; + this.gameState = gameState; } public void Render(int pixelOffsetX, int pixelOffsetY) @@ -93,6 +114,14 @@ namespace OpenDiablo2.SDL2_ if (currentDirectionCache == null) return; + + if ((lastDirection != MobLocation.MovementDirection) || (lastMovementType != MobLocation.MovementType)) + { + lastMovementType = MobLocation.MovementType; + lastDirection = MobLocation.MovementDirection; + ResetAnimationData(); + } + seconds += ms / 1000f; var animationSeg = 15f / currentDirectionCache.AnimationSpeed; while (seconds >= animationSeg) @@ -111,8 +140,9 @@ namespace OpenDiablo2.SDL2_ public void ResetAnimationData() { + var lastMobMode = MobMode; - switch (LocationDetails.MovementType) + switch (MobLocation.MovementType) { case eMovementType.Stopped: MobMode = eMobMode.PlayerTownNeutral; @@ -130,7 +160,7 @@ namespace OpenDiablo2.SDL2_ if (lastMobMode != MobMode) renderFrameIndex = 0; - currentDirectionCache = directionCache.FirstOrDefault(x => x.MobMode == MobMode && x.Direction == directionConversion[LocationDetails.MovementDirection]); + currentDirectionCache = directionCache.FirstOrDefault(x => x.MobMode == MobMode && x.Direction == directionConversion[MobLocation.MovementDirection]); if (currentDirectionCache != null) return; @@ -147,7 +177,7 @@ namespace OpenDiablo2.SDL2_ var directionCache = new DirectionCacheItem { MobMode = MobMode, - Direction = directionConversion[LocationDetails.MovementDirection] + Direction = directionConversion[MobLocation.MovementDirection] }; var palette = paletteProvider.PaletteTable[Palettes.Units]; @@ -170,10 +200,10 @@ namespace OpenDiablo2.SDL2_ continue; } - minX = Math.Min(minX, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Left); - minY = Math.Min(minY, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Top); - maxX = Math.Max(maxX, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Right); - maxY = Math.Max(maxY, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Bottom); + minX = Math.Min(minX, layer.Directions[directionConversion[MobLocation.MovementDirection]].Box.Left); + minY = Math.Min(minY, layer.Directions[directionConversion[MobLocation.MovementDirection]].Box.Top); + maxX = Math.Max(maxX, layer.Directions[directionConversion[MobLocation.MovementDirection]].Box.Right); + maxY = Math.Max(maxY, layer.Directions[directionConversion[MobLocation.MovementDirection]].Box.Bottom); } if (layersIgnored > 0) @@ -192,7 +222,7 @@ namespace OpenDiablo2.SDL2_ SDL.SDL_LockTexture(texture, IntPtr.Zero, out var pixels, out var pitch); var data = (UInt32*)pixels; - var priorityBase = (directionConversion[LocationDetails.MovementDirection] * animationData.FramesPerDirection * animationData.NumberOfLayers) + var priorityBase = (directionConversion[MobLocation.MovementDirection] * animationData.FramesPerDirection * animationData.NumberOfLayers) + (frameIndex * animationData.NumberOfLayers); for (var i = 0; i < animationData.NumberOfLayers; i++) { @@ -205,7 +235,7 @@ namespace OpenDiablo2.SDL2_ if (layer == null) continue; // TODO: This is most likely not ok - var direction = layer.Directions[directionConversion[LocationDetails.MovementDirection]]; + var direction = layer.Directions[directionConversion[MobLocation.MovementDirection]]; var frame = direction.Frames[frameIndex]; diff --git a/OpenDiablo2.SDL2/SDL2RenderWindow.cs b/OpenDiablo2.SDL2/SDL2RenderWindow.cs index be25b9b0..b7bf4098 100644 --- a/OpenDiablo2.SDL2/SDL2RenderWindow.cs +++ b/OpenDiablo2.SDL2/SDL2RenderWindow.cs @@ -61,7 +61,7 @@ namespace OpenDiablo2.SDL2_ private readonly IResourceManager resourceManager; private readonly GlobalConfiguration globalConfig; private readonly Func getGameState; - private readonly Func getMapEngine; + private readonly Func getMapRenderer; public SDL2RenderWindow( GlobalConfiguration globalConfig, @@ -69,7 +69,7 @@ namespace OpenDiablo2.SDL2_ IPaletteProvider paletteProvider, IResourceManager resourceManager, Func getGameState, - Func getMapEngine + Func getMapEngine ) { this.globalConfig = globalConfig; @@ -77,7 +77,7 @@ namespace OpenDiablo2.SDL2_ this.paletteProvider = paletteProvider; this.resourceManager = resourceManager; this.getGameState = getGameState; - this.getMapEngine = getMapEngine; + this.getMapRenderer = getMapEngine; this.fullscreen = globalConfig.FullScreen; SDL.SDL_Init(SDL.SDL_INIT_EVERYTHING); @@ -482,6 +482,6 @@ namespace OpenDiablo2.SDL2_ public uint GetTicks() => SDL.SDL_GetTicks(); public ICharacterRenderer CreateCharacterRenderer() - => new SDL2CharacterRenderer(this.renderer, resourceManager, paletteProvider); + => new SDL2CharacterRenderer(this.renderer, resourceManager, paletteProvider, getGameState()); } } diff --git a/OpenDiablo2.Scenes/Game.cs b/OpenDiablo2.Scenes/Game.cs index ccc2377c..f75c0cb6 100644 --- a/OpenDiablo2.Scenes/Game.cs +++ b/OpenDiablo2.Scenes/Game.cs @@ -19,14 +19,17 @@ using OpenDiablo2.Common.Attributes; using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Interfaces; using System; +using System.Linq; namespace OpenDiablo2.Scenes { [Scene(eSceneType.Game)] public sealed class Game : IScene { + private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + private readonly IRenderWindow renderWindow; - private readonly IMapEngine mapEngine; + private readonly IMapRenderer _mapRenderer; private readonly IMouseInfoProvider mouseInfoProvider; private readonly IGameState gameState; private readonly ISessionManager sessionManager; @@ -35,12 +38,12 @@ namespace OpenDiablo2.Scenes private eMovementType lastMovementType = eMovementType.Stopped; private byte lastDirection = 255; private bool clickedOnHud = false; - - const double Rad2Deg = 180.0 / Math.PI; + private float lastMoveSend = 0f; + private float holdMoveTime = 0f; public Game( IRenderWindow renderWindow, - IMapEngine mapEngine, + IMapRenderer mapRenderer, IGameState gameState, IMouseInfoProvider mouseInfoProvider, IItemManager itemManager, @@ -51,7 +54,7 @@ namespace OpenDiablo2.Scenes ) { this.renderWindow = renderWindow; - this.mapEngine = mapEngine; + this._mapRenderer = mapRenderer; this.gameState = gameState; this.mouseInfoProvider = mouseInfoProvider; this.sessionManager = sessionManager; @@ -63,23 +66,26 @@ namespace OpenDiablo2.Scenes public void Render() { // TODO: Maybe show some sort of connecting/loading message? - if (mapEngine.FocusedPlayerId == 0) + if (_mapRenderer.FocusedPlayerId == Guid.Empty) return; - mapEngine.Render(); + _mapRenderer.Render(); gameHUD.Render(); } public void Update(long ms) { - HandleMovement(); + HandleMovement(ms); - mapEngine.Update(ms); + _mapRenderer.Update(ms); gameHUD.Update(); } - private void HandleMovement() + private void HandleMovement(long ms) { + if (mouseInfoProvider.ReserveMouse) + return; + if(gameHUD.IsMouseOver() && lastMovementType == eMovementType.Stopped) clickedOnHud = true; else if (!mouseInfoProvider.LeftMouseDown) @@ -88,11 +94,40 @@ namespace OpenDiablo2.Scenes if (clickedOnHud) return; + /* var mx = mouseInfoProvider.MouseX - 400 - gameState.CameraOffset; var my = mouseInfoProvider.MouseY - 300; var tx = (mx / 60f + my / 40f) / 2f; var ty = (my / 40f - (mx / 60f)) / 2f; + */ + + if (mouseInfoProvider.LeftMouseDown) + { + lastMoveSend += (ms / 1000f); + holdMoveTime += (ms / 1000f); + if (lastMoveSend < .25f) + return; + lastMoveSend = 0f; + var selectedCell = _mapRenderer.GetCellPositionAt(mouseInfoProvider.MouseX, mouseInfoProvider.MouseY); +#if DEBUG + log.Debug($"Move to cell: ({selectedCell.X}, {selectedCell.Y})"); +#endif + sessionManager.MoveRequest(selectedCell, gameHUD.IsRunningEnabled ? eMovementType.Running : eMovementType.Walking); + } + else + { + lastMoveSend = 1f; // Next click will always send a mouse move request (TODO: Should this be limited as well?) + + if (holdMoveTime > 0.2f) + { + var player = gameState.PlayerInfos.First(x => x.UID == _mapRenderer.FocusedPlayerId); + sessionManager.MoveRequest(new System.Drawing.PointF(player.X, player.Y), eMovementType.Stopped); + } + holdMoveTime = 0f; + } + + /* var cursorDirection = (int)Math.Round(Math.Atan2(ty, tx) * Rad2Deg); if (cursorDirection < 0) cursorDirection += 360; @@ -112,6 +147,7 @@ namespace OpenDiablo2.Scenes lastMovementType = eMovementType.Stopped; sessionManager.MoveRequest(actualDirection, lastMovementType); } + */ } public void Dispose() diff --git a/OpenDiablo2.ServiceBus/Message Frames/Client/MFJoinGame.cs b/OpenDiablo2.ServiceBus/Message Frames/Client/MFJoinGame.cs index 7b7102db..ab31811d 100644 --- a/OpenDiablo2.ServiceBus/Message Frames/Client/MFJoinGame.cs +++ b/OpenDiablo2.ServiceBus/Message Frames/Client/MFJoinGame.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Formatters.Binary; -using System.Text; -using OpenDiablo2.Common.Attributes; +using OpenDiablo2.Common.Attributes; using OpenDiablo2.Common.Enums; -using OpenDiablo2.Common.Exceptions; using OpenDiablo2.Common.Interfaces; +using System.IO; namespace OpenDiablo2.ServiceBus.Message_Frames.Client { @@ -17,28 +11,16 @@ namespace OpenDiablo2.ServiceBus.Message_Frames.Client public string PlayerName { get; set; } public eHero HeroType { get; set; } - public byte[] Data + public void WriteTo(BinaryWriter bw) { - get - { - using (var stream = new MemoryStream()) - using (var writer = new BinaryWriter(stream)) { - writer.Write((byte)HeroType); - writer.Write(PlayerName); + bw.Write((byte)HeroType); + bw.Write(PlayerName); + } - return stream.ToArray(); - } - } - - set - { - using(var stream = new MemoryStream(value)) - using(var reader = new BinaryReader(stream)) - { - HeroType = (eHero)reader.ReadByte(); - PlayerName = reader.ReadString(); - } - } + public void LoadFrom(BinaryReader br) + { + HeroType = (eHero)br.ReadByte(); + PlayerName = br.ReadString(); } public MFJoinGame() { } diff --git a/OpenDiablo2.ServiceBus/Message Frames/Client/MFMoveRequest.cs b/OpenDiablo2.ServiceBus/Message Frames/Client/MFMoveRequest.cs index 7a4d0cef..47df38c8 100644 --- a/OpenDiablo2.ServiceBus/Message Frames/Client/MFMoveRequest.cs +++ b/OpenDiablo2.ServiceBus/Message Frames/Client/MFMoveRequest.cs @@ -1,34 +1,44 @@ using OpenDiablo2.Common.Attributes; using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Interfaces; +using System; +using System.Drawing; +using System.IO; namespace OpenDiablo2.ServiceBus.Message_Frames.Client { [MessageFrame(eMessageFrameType.MoveRequest)] public sealed class MFMoveRequest : IMessageFrame { - public byte Direction { get; set; } = 0; + public PointF Target { get; set; } public eMovementType MovementType { get; set; } = eMovementType.Stopped; - public byte[] Data - { - get => new byte[] { Direction, (byte)MovementType }; - set - { - Direction = value[0]; - MovementType = (eMovementType)value[1]; - } - } - public MFMoveRequest() { } - public MFMoveRequest(byte direction, eMovementType movementType) + public MFMoveRequest(PointF targetCell, eMovementType movementType) { - this.Direction = direction; + this.Target = targetCell; this.MovementType = movementType; } + public void LoadFrom(BinaryReader br) + { + MovementType = (eMovementType)br.ReadByte(); + Target = new PointF + { + X = br.ReadSingle(), + Y = br.ReadSingle() + }; + } + + public void WriteTo(BinaryWriter bw) + { + bw.Write((byte)MovementType); + bw.Write((Single)Target.X); + bw.Write((Single)Target.Y); + } + public void Process(int clientHash, ISessionEventProvider sessionEventProvider) - => sessionEventProvider.OnMoveRequest(clientHash, Direction, MovementType); + => sessionEventProvider.OnMoveRequest(clientHash, Target, MovementType); } } diff --git a/OpenDiablo2.ServiceBus/Message Frames/Server/MFFocusOnPlayer.cs b/OpenDiablo2.ServiceBus/Message Frames/Server/MFFocusOnPlayer.cs index 0a342ee5..a0b32429 100644 --- a/OpenDiablo2.ServiceBus/Message Frames/Server/MFFocusOnPlayer.cs +++ b/OpenDiablo2.ServiceBus/Message Frames/Server/MFFocusOnPlayer.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using OpenDiablo2.Common.Attributes; using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Interfaces; @@ -8,20 +9,24 @@ namespace OpenDiablo2.ServiceBus.Message_Frames.Server [MessageFrame(eMessageFrameType.FocusOnPlayer)] public sealed class MFFocusOnPlayer : IMessageFrame { - public int PlayerToFocusOn { get; set; } = 0; - - public byte[] Data - { - get => BitConverter.GetBytes(PlayerToFocusOn); - set => PlayerToFocusOn = BitConverter.ToInt32(value, 0); - } + public Guid PlayerToFocusOn { get; set; } = Guid.Empty; public MFFocusOnPlayer() { } - public MFFocusOnPlayer(int playerId) + public MFFocusOnPlayer(Guid playerId) { this.PlayerToFocusOn = playerId; } + public void LoadFrom(BinaryReader br) + { + PlayerToFocusOn = new Guid(br.ReadBytes(16)); + } + + public void WriteTo(BinaryWriter bw) + { + bw.Write(PlayerToFocusOn.ToByteArray()); + } + public void Process(int clientHash, ISessionEventProvider sessionEventProvider) => sessionEventProvider.OnFocusOnPlayer(clientHash, PlayerToFocusOn); } diff --git a/OpenDiablo2.ServiceBus/Message Frames/Server/MFLocatePlayers.cs b/OpenDiablo2.ServiceBus/Message Frames/Server/MFLocatePlayers.cs index 74fac986..53fdf565 100644 --- a/OpenDiablo2.ServiceBus/Message Frames/Server/MFLocatePlayers.cs +++ b/OpenDiablo2.ServiceBus/Message Frames/Server/MFLocatePlayers.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using OpenDiablo2.Common.Attributes; using OpenDiablo2.Common.Enums; @@ -11,34 +12,30 @@ namespace OpenDiablo2.ServiceBus.Message_Frames.Server [MessageFrame(eMessageFrameType.LocatePlayers)] public sealed class MFLocatePlayers : IMessageFrame { - public IEnumerable LocationDetails { get; set; } + public IEnumerable LocationDetails { get; set; } - public byte[] Data + public void LoadFrom(BinaryReader br) { - get + var count = br.ReadUInt16(); + var result = new List(); + + for (var i = 0; i < count; i++) { - var result = new List(); - result.AddRange(BitConverter.GetBytes((UInt16)LocationDetails.Count())); - result.AddRange(LocationDetails.SelectMany(x => x.GetBytes())); - return result.ToArray(); + result.Add(Common.Models.LocationDetails.FromBytes(br)); } - set - { - var count = BitConverter.ToUInt16(value, 0); - var result = new List(); - - for(var i = 0; i < count; i++) - { - result.Add(PlayerLocationDetails.FromBytes(value, 2 + (i * PlayerLocationDetails.SizeInBytes))); - } + LocationDetails = result; + } - LocationDetails = result; - } + public void WriteTo(BinaryWriter bw) + { + bw.Write((UInt16)LocationDetails.Count()); + foreach (var locationDetail in LocationDetails) + locationDetail.WriteBytes(bw); } public MFLocatePlayers() { } - public MFLocatePlayers(IEnumerable locationDetails) + public MFLocatePlayers(IEnumerable locationDetails) { this.LocationDetails = locationDetails; } diff --git a/OpenDiablo2.ServiceBus/Message Frames/Server/MFPlayerInfo.cs b/OpenDiablo2.ServiceBus/Message Frames/Server/MFPlayerInfo.cs index 2717c4b0..528b57bd 100644 --- a/OpenDiablo2.ServiceBus/Message Frames/Server/MFPlayerInfo.cs +++ b/OpenDiablo2.ServiceBus/Message Frames/Server/MFPlayerInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using OpenDiablo2.Common.Attributes; using OpenDiablo2.Common.Enums; @@ -13,32 +14,24 @@ namespace OpenDiablo2.ServiceBus.Message_Frames.Server { public IEnumerable PlayerInfos { get; set; } = new List(); - public byte[] Data + public void LoadFrom(BinaryReader br) { - get - { - var result = BitConverter.GetBytes(PlayerInfos.Count()) - .Concat(PlayerInfos.SelectMany(x => x.GetBytes())) - .ToArray(); - return result; - } + var count = br.ReadUInt16(); + var playerInfos = new PlayerInfo[count]; + for (var i = 0; i < count; i++) + playerInfos[i] = PlayerInfo.FromBytes(br); - set - { - var count = BitConverter.ToInt32(value, 0); - var playerInfos = new List(); - var offset = 4; - for (var i = 0; i < count; i++) - { - var playerInfo = PlayerInfo.FromBytes(value, offset); - playerInfos.Add(playerInfo); - offset += playerInfo.SizeInBytes; - } - - PlayerInfos = playerInfos; - } + PlayerInfos = playerInfos; } + public void WriteTo(BinaryWriter bw) + { + bw.Write((UInt16)PlayerInfos.Count()); + foreach (var playerInfo in PlayerInfos) + playerInfo.WriteBytes(bw); + } + + public MFPlayerInfo() { } public MFPlayerInfo(IEnumerable playerInfo) { diff --git a/OpenDiablo2.ServiceBus/Message Frames/Server/MFSetSeed.cs b/OpenDiablo2.ServiceBus/Message Frames/Server/MFSetSeed.cs index ed9fe38e..32dd77ed 100644 --- a/OpenDiablo2.ServiceBus/Message Frames/Server/MFSetSeed.cs +++ b/OpenDiablo2.ServiceBus/Message Frames/Server/MFSetSeed.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using OpenDiablo2.Common.Attributes; using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Interfaces; @@ -15,6 +16,16 @@ namespace OpenDiablo2.ServiceBus.Message_Frames.Server set => Seed = BitConverter.ToInt32(value, 0); } + public void LoadFrom(BinaryReader br) + { + Seed = br.ReadInt32(); + } + + public void WriteTo(BinaryWriter bw) + { + bw.Write((Int32)Seed); + } + public Int32 Seed { get; private set; } public MFSetSeed() diff --git a/OpenDiablo2.ServiceBus/OpenDiablo2.ServiceBus.csproj b/OpenDiablo2.ServiceBus/OpenDiablo2.ServiceBus.csproj index 0a3d0d36..525417cb 100644 --- a/OpenDiablo2.ServiceBus/OpenDiablo2.ServiceBus.csproj +++ b/OpenDiablo2.ServiceBus/OpenDiablo2.ServiceBus.csproj @@ -48,6 +48,7 @@ + diff --git a/OpenDiablo2.ServiceBus/SessionManager.cs b/OpenDiablo2.ServiceBus/SessionManager.cs index 3b4e0f64..7b00d7d0 100644 --- a/OpenDiablo2.ServiceBus/SessionManager.cs +++ b/OpenDiablo2.ServiceBus/SessionManager.cs @@ -15,6 +15,8 @@ */ using System; +using System.Drawing; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -120,7 +122,13 @@ namespace OpenDiablo2.ServiceBus public void Send(IMessageFrame messageFrame, bool more = false) { var attr = messageFrame.GetType().GetCustomAttributes(true).First(x => (x is MessageFrameAttribute)) as MessageFrameAttribute; - requestSocket.SendFrame(new byte[] { (byte)attr.FrameType }.Concat(messageFrame.Data).ToArray(), more); + using (var ms = new MemoryStream()) + using (var bw = new BinaryWriter(ms)) + { + bw.Write((byte)attr.FrameType); + messageFrame.WriteTo(bw); + requestSocket.SendFrame(ms.ToArray(), more); + } } private void ProcessMessageFrame() where T : IMessageFrame, new() @@ -128,17 +136,18 @@ namespace OpenDiablo2.ServiceBus if (!running) throw new OpenDiablo2Exception("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 OpenDiablo2Exception("Recieved unexpected message frame!"); - messageFrame.Data = frameData; - lock (getGameState().ThreadLocker) + using (var ms = new MemoryStream(requestSocket.ReceiveFrameBytes())) + using (var br = new BinaryReader(ms)) { - messageFrame.Process(requestSocket.GetHashCode(), this); + var messageFrame = getMessageFrame((eMessageFrameType)br.ReadByte()); + + if (messageFrame.GetType() != typeof(T)) + throw new OpenDiablo2Exception("Recieved unexpected message frame!"); + + messageFrame.LoadFrom(br); + + lock (getGameState().ThreadLocker) + messageFrame.Process(requestSocket.GetHashCode(), this); } } @@ -161,10 +170,10 @@ namespace OpenDiablo2.ServiceBus }); } - public void MoveRequest(byte direction, eMovementType movementType) + public void MoveRequest(PointF targetCell, eMovementType movementType) => Task.Run(() => { - Send(new MFMoveRequest(direction, movementType)); + Send(new MFMoveRequest(targetCell, movementType)); ProcessMessageFrame(); }); } diff --git a/OpenDiablo2.ServiceBus/SessionServer.cs b/OpenDiablo2.ServiceBus/SessionServer.cs index 42495142..eda1cf77 100644 --- a/OpenDiablo2.ServiceBus/SessionServer.cs +++ b/OpenDiablo2.ServiceBus/SessionServer.cs @@ -15,6 +15,9 @@ */ using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -25,6 +28,7 @@ using OpenDiablo2.Common.Enums; using OpenDiablo2.Common.Exceptions; using OpenDiablo2.Common.Interfaces; using OpenDiablo2.Common.Models; +using OpenDiablo2.Common.Models.Mobs; using OpenDiablo2.ServiceBus.Message_Frames.Server; namespace OpenDiablo2.ServiceBus @@ -54,7 +58,7 @@ namespace OpenDiablo2.ServiceBus const int serverUpdateRate = 30; public SessionServer( - eSessionType sessionType, + eSessionType sessionType, IGameServer gameServer, Func getMessageFrame ) @@ -93,19 +97,23 @@ namespace OpenDiablo2.ServiceBus 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.GetHashCode(), this); + foreach (var msg in message) + { + using (var ms = new MemoryStream(msg.ToByteArray())) + using (var br = new BinaryReader(ms)) + { + var messageFrame = getMessageFrame((eMessageFrameType)br.ReadByte()); + messageFrame.LoadFrom(br); + messageFrame.Process(socket.GetHashCode(), this); + } + } }); running = true; WaitServerStartEvent.Set(); Task.Run(() => { var lastRun = DateTime.Now; - while(running) + while (running) { var newTime = DateTime.Now; var timeDiff = (newTime - lastRun).TotalMilliseconds; @@ -122,22 +130,64 @@ namespace OpenDiablo2.ServiceBus responseSocket.Dispose(); log.Info("Session server has stopped."); } - - private void OnMovementRequestHandler(int clientHash, byte direction, eMovementType movementType) + + private void OnMovementRequestHandler(int clientHash, PointF targetCell, eMovementType movementType) { var player = gameServer.Players.FirstOrDefault(x => x.ClientHash == clientHash); if (player == null) return; - player.MovementDirection = direction; player.MovementType = movementType; - player.MovementDirection = direction; - + player.MovementSpeed = (player.MovementType == eMovementType.Running ? player.GetRunVelocity() : player.GetWalkVeloicty()) / 4f; + player.Waypoints = CalculateWaypoints(player, targetCell); Send(new MFLocatePlayers(gameServer.Players.Select(x => x.ToPlayerLocationDetails()))); } + private List CalculateWaypoints(PlayerState player, PointF targetCell) + { + // TODO: Move this somewhere else... + var result = new List(); + result.Add(targetCell); + /* + // Ensure they aren't sending crazy coordinates.. + var targetX = Math.Round(targetCell.X, 1); + var targetY = Math.Round(targetCell.Y, 1); + + // TODO: Legit Pathfind here... + result.Add(new PointF(player.X, player.Y)); + int maxTries = 50; + var curX = player.X; + var curY = player.Y; + var nextX = curX; + var nextY = curY; + while (--maxTries > 0) + { + if (curX < targetX) + nextX += .1f; + else if (curX > targetX) + nextX -= .1f; + + if (curY < targetY) + nextY += .1f; + else if (curY > targetY) + nextY -= .1f; + + result.Add(new PointF((float)Math.Round(nextX, 1), (float)Math.Round(nextY, 1))); + + curX = nextX; + curY = nextY; + + // If we reached our target, stop here + if (Math.Abs(curX - targetX) < 0.1f && Math.Abs(curY - targetY) < 0.1f) + break; + } + + */ + return result; + } + public void Stop() { if (!running) @@ -159,7 +209,13 @@ namespace OpenDiablo2.ServiceBus private void Send(IMessageFrame messageFrame, bool more = false) { var attr = messageFrame.GetType().GetCustomAttributes(true).First(x => (x is MessageFrameAttribute)) as MessageFrameAttribute; - responseSocket.SendFrame(new byte[] { (byte)attr.FrameType }.Concat(messageFrame.Data).ToArray(), more); + using (var ms = new MemoryStream()) + using (var br = new BinaryWriter(ms)) + { + br.Write((byte)attr.FrameType); + messageFrame.WriteTo(br); + responseSocket.SendFrame(ms.ToArray(), more); + } } private void OnJoinGameHandler(int clientHash, eHero heroType, string playerName) @@ -168,7 +224,7 @@ namespace OpenDiablo2.ServiceBus Send(new MFSetSeed(gameServer.Seed), true); Send(new MFPlayerInfo(gameServer.Players.Select(x => x.ToPlayerInfo())), true); Send(new MFLocatePlayers(gameServer.Players.Select(x => x.ToPlayerLocationDetails())), true); - Send(new MFFocusOnPlayer(gameServer.Players.First(x => x.ClientHash == clientHash).Id)); + Send(new MFFocusOnPlayer(gameServer.Players.First(x => x.ClientHash == clientHash).UID)); } } }