1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-16 04:25:23 +00:00

Restructured how movement works. Restructured network protocols. Player ID is now GUID based.

This commit is contained in:
Tim Sarbin 2018-12-15 17:42:36 -05:00
parent c85b2bc605
commit d911a76cad
38 changed files with 923 additions and 554 deletions

View File

@ -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);

View File

@ -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<MapCellInfo> GetMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType);

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<PointF> 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;
}
}
}

View File

@ -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);
}
}

View File

@ -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> playerLocationDetails);
public delegate void OnLocatePlayersEvent(int clientHash, IEnumerable<LocationDetails> playerLocationDetails);
public delegate void OnPlayerInfoEvent(int clientHash, IEnumerable<PlayerInfo> 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
{

View File

@ -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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<PointF> Waypoints { get; set; } = new List<PointF>();
// 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<PointF>(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;
}
}
}

View File

@ -11,9 +11,18 @@ namespace OpenDiablo2.Common.Models.Mobs
public readonly int Id;
public bool Alive { get; protected set; } = true;
/// <summary>The X tile location of the mob</summary>
public float X { get; set; } = 0;
/// <summary>The Y tile location of the mob</summary>
public float Y { get; set; } = 0;
/// <summary>The speed of the mob (in units per second)</summary>
public float MovementSpeed { get; set; }
/// <summary>Represents the movement direction of the mob (16 angular segments)</summary>
public int MovementDirection { get; set; }
protected Stat Health;
protected Dictionary<eDamageTypes, StatDouble> Resistances = new Dictionary<eDamageTypes, StatDouble>();

View File

@ -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<PointF> Waypoints { get; set; } = new List<PointF>();
public eMovementType MovementType { get; set; } = eMovementType.Stopped;
public eMobMode MobMode { get; set; } = eMobMode.PlayerTownWalk;
public PlayerEquipment Equipment { get; set; } = new PlayerEquipment();

View File

@ -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<PointF> 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<PointF>();
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
};
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<byte>();
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;
}
}
}

View File

@ -94,6 +94,7 @@
<Compile Include="Interfaces\IItemManager.cs" />
<Compile Include="Extensions\MobManagerExtensions.cs" />
<Compile Include="Interfaces\IGameServer.cs" />
<Compile Include="Interfaces\IMobLocation.cs" />
<Compile Include="Interfaces\IRandomizedMapGenerator.cs" />
<Compile Include="Interfaces\MessageBus\ISessionEventProvider.cs" />
<Compile Include="Interfaces\MessageBus\IMessageFrame.cs" />
@ -123,7 +124,7 @@
<Compile Include="Models\ObjectInfo.cs" />
<Compile Include="Models\ObjectTypeInfo.cs" />
<Compile Include="Models\PlayerInfo.cs" />
<Compile Include="Models\PlayerLocationDetails.cs" />
<Compile Include="Models\LocationDetails.cs" />
<Compile Include="Interfaces\UI\IButton.cs" />
<Compile Include="Interfaces\UI\IItemContainer.cs" />
<Compile Include="Interfaces\UI\IPanelFrame.cs" />
@ -134,7 +135,7 @@
<Compile Include="Interfaces\IGameState.cs" />
<Compile Include="Interfaces\System\IKeyboardInfoProvider.cs" />
<Compile Include="Interfaces\UI\ILabel.cs" />
<Compile Include="Interfaces\IMapEngine.cs" />
<Compile Include="Interfaces\IMapRenderer.cs" />
<Compile Include="Interfaces\UI\IMiniPanel.cs" />
<Compile Include="Interfaces\UI\ICharacterPanel.cs" />
<Compile Include="Interfaces\System\IMouseCursor.cs" />
@ -187,6 +188,7 @@
<Compile Include="Palettes.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ResourcePaths.cs" />
<Compile Include="Services\MobMovementService.cs" />
<Compile Include="StringUtils.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
/// <summary>Moves the mob along the set of waypoints</summary>
/// <param name="seconds">The amount of time to process</param>
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;
}
}
}

View File

@ -37,8 +37,8 @@ namespace OpenDiablo2.Core
builder.RegisterType<ItemManager>().As<IItemManager>().SingleInstance();
builder.RegisterType<GameEngine>().AsImplementedInterfaces().SingleInstance();
builder.RegisterType<GameState>().As<IGameState>().SingleInstance();
builder.RegisterType<MapEngine>().As<IMapEngine>().SingleInstance();
builder.RegisterType<GameHUD>().As<IGameHUD>().InstancePerDependency();
builder.RegisterType<MapRenderer>().As<IMapRenderer>().SingleInstance();
builder.RegisterType<GameHUD>().As<IGameHUD>().SingleInstance();
builder.RegisterType<MiniPanel>().As<IMiniPanel>().InstancePerDependency();
builder.RegisterType<PanelFrame>().As<IPanelFrame>().InstancePerDependency();
builder.RegisterType<CharacterPanel>().AsImplementedInterfaces().InstancePerDependency();

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<IMapEngine> getMapEngine;
private readonly Func<IMapRenderer> getMapEngine;
private readonly Func<eSessionType, ISessionManager> getSessionManager;
private readonly Func<string, IRandomizedMapGenerator> getRandomizedMapGenerator;
readonly private IMouseCursor originalMouseCursor;
private float animationTime;
private List<IMapInfo> mapInfo;
private readonly List<MapCellInfo> mapDataLookup;
@ -35,14 +55,9 @@ namespace OpenDiablo2.Core.GameState_
public string MapName { get; private set; }
public Palette CurrentPalette => paletteProvider.PaletteTable[$"ACT{Act}"];
public List<PlayerInfo> 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<PlayerInfo> IGameState.PlayerInfos => PlayerInfos;
@ -57,7 +72,7 @@ namespace OpenDiablo2.Core.GameState_
IRenderWindow renderWindow,
ISoundProvider soundProvider,
IMPQProvider mpqProvider,
Func<IMapEngine> getMapEngine,
Func<IMapRenderer> getMapEngine,
Func<eSessionType, ISessionManager> getSessionManager,
Func<string, IRandomizedMapGenerator> getRandomizedMapGenerator
)
@ -89,26 +104,28 @@ namespace OpenDiablo2.Core.GameState_
sessionManager.OnFocusOnPlayer += OnFocusOnPlayer;
mapInfo = new List<IMapInfo>();
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> playerInfo)
=> PlayerInfos = playerInfo.ToList();
private void OnLocatePlayers(int clientHash, IEnumerable<PlayerLocationDetails> playerLocationDetails)
private void OnLocatePlayers(int clientHash, IEnumerable<LocationDetails> 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<Size> GetMapSizes(int cellX, int cellY)
@ -157,7 +174,7 @@ namespace OpenDiablo2.Core.GameState_
CellInfo = new Dictionary<eRenderCellType, MapCellInfo[]>(),
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()

View File

@ -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<ICharacterRenderer> _characterRenderers = new List<ICharacterRenderer>();
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> 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> 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);
}
}
}

View File

@ -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<IGameHUD> getGameHud;
private readonly List<ICharacterRenderer> _characterRenderers = new List<ICharacterRenderer>();
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<IGameHUD> getGameHud
)
{
this.gameState = gameState;
this.renderWindow = renderWindow;
this.getGameHud = getGameHud;
sessionManager.OnPlayerInfo += OnPlayerInfo;
sessionManager.OnLocatePlayers += OnLocatePlayers;
}
private void OnLocatePlayers(int clientHash, IEnumerable<LocationDetails> 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> 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;
*/
}
}

View File

@ -66,7 +66,7 @@
<Compile Include="GameEngine.cs" />
<Compile Include="GameState\GameState.cs" />
<Compile Include="GameState\MobManager.cs" />
<Compile Include="Map Engine\MapEngine.cs" />
<Compile Include="Map Engine\MapRenderer.cs" />
<Compile Include="Map Engine\MapGenerator.cs" />
<Compile Include="MPQProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View File

@ -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<IMiniPanel> createMiniPanel,
Func<eButtonType, IButton> createButton,
Func<ePanelFrameType, IPanelFrame> 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);
}
}

View File

@ -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<IButton> buttons;
private readonly IEnumerable<IPanel> panels;
private readonly Func<IGameHUD> _getGameHud;
private bool isPanelVisible;
public event OnPanelToggledEvent OnPanelToggled;
public MiniPanel(IRenderWindow renderWindow,
IGameState gameState,
public MiniPanel(IRenderWindow renderWindow,
Func<IGameHUD> getGameHud,
IMouseInfoProvider mouseInfoProvider,
IEnumerable<IPanel> panels,
Func<eButtonType, IButton> 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++)

View File

@ -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<eItemContainerType, IItemContainer> createItemContainer,
IGameState gameState,
Func<eButtonType, IButton> 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> 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<PlayerInfo> 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)

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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()

View File

@ -43,6 +43,7 @@
<HintPath>..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />

View File

@ -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
{

View File

@ -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<DirectionCacheItem> directionCache = new List<DirectionCacheItem>();
@ -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];

View File

@ -61,7 +61,7 @@ namespace OpenDiablo2.SDL2_
private readonly IResourceManager resourceManager;
private readonly GlobalConfiguration globalConfig;
private readonly Func<IGameState> getGameState;
private readonly Func<IMapEngine> getMapEngine;
private readonly Func<IMapRenderer> getMapRenderer;
public SDL2RenderWindow(
GlobalConfiguration globalConfig,
@ -69,7 +69,7 @@ namespace OpenDiablo2.SDL2_
IPaletteProvider paletteProvider,
IResourceManager resourceManager,
Func<IGameState> getGameState,
Func<IMapEngine> getMapEngine
Func<IMapRenderer> 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());
}
}

View File

@ -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()

View File

@ -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() { }

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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<PlayerLocationDetails> LocationDetails { get; set; }
public IEnumerable<LocationDetails> LocationDetails { get; set; }
public byte[] Data
public void LoadFrom(BinaryReader br)
{
get
var count = br.ReadUInt16();
var result = new List<LocationDetails>();
for (var i = 0; i < count; i++)
{
var result = new List<byte>();
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<PlayerLocationDetails>();
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<PlayerLocationDetails> locationDetails)
public MFLocatePlayers(IEnumerable<LocationDetails> locationDetails)
{
this.LocationDetails = locationDetails;
}

View File

@ -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<PlayerInfo> PlayerInfos { get; set; } = new List<PlayerInfo>();
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<PlayerInfo>();
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> playerInfo)
{

View File

@ -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()

View File

@ -48,6 +48,7 @@
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />

View File

@ -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<T>() 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<MFLocatePlayers>();
});
}

View File

@ -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<eMessageFrameType, IMessageFrame> 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<PointF> CalculateWaypoints(PlayerState player, PointF targetCell)
{
// TODO: Move this somewhere else...
var result = new List<PointF>();
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));
}
}
}