1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-20 14:15:23 +00:00

Minor cleanup. Added object and object type data dictionaries. Map loader now includes object and tag data

This commit is contained in:
Tim Sarbin 2018-12-14 14:48:36 -05:00
parent f56273a4a2
commit c85b2bc605
12 changed files with 277 additions and 171 deletions

View File

@ -1,8 +1,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Interfaces.Mobs;
using OpenDiablo2.Common.Models;
using System.Collections.Immutable;
namespace OpenDiablo2.Common.Interfaces
{
@ -16,5 +15,6 @@ namespace OpenDiablo2.Common.Interfaces
ImmutableDictionary<eHero, IHeroTypeConfig> HeroTypeConfigs { get; }
ImmutableList<IEnemyTypeConfig> EnemyTypeConfigs { get; }
ImmutableList<ObjectInfo> Objects { get; }
ImmutableList<ObjectTypeInfo> ObjectTypes { get; }
}
}

View File

@ -2,7 +2,9 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using OpenDiablo2.Common.Exceptions;
using OpenDiablo2.Common.Interfaces;
namespace OpenDiablo2.Common.Models
@ -40,6 +42,11 @@ namespace OpenDiablo2.Common.Models
public MPQDS1TileProps[] Props { get; internal set; }
}
public sealed class MPQDS1TagLayer
{
public Int32 Number { get; internal set; }
}
public sealed class MPQDS1Object
{
public Int32 Type { get; internal set; }
@ -47,6 +54,7 @@ namespace OpenDiablo2.Common.Models
public Int32 X { get; internal set; }
public Int32 Y { get; internal set; }
public Int32 DS1Flags { get; internal set; }
public ObjectInfo Info { get; internal set; }
}
public sealed class MPQDS1Group
@ -92,6 +100,7 @@ namespace OpenDiablo2.Common.Models
public MPQDS1WallLayer[] WallLayers { get; internal set; }
public MPQDS1FloorLayer[] FloorLayers { get; internal set; }
public MPQDS1ShadowLayer[] ShadowLayers { get; internal set; }
public MPQDS1TagLayer[] TagLayers { get; internal set; }
public MPQDS1Object[] Objects { get; internal set; }
public MPQDS1Group[] Groups { get; internal set; }
@ -125,7 +134,7 @@ namespace OpenDiablo2.Common.Models
fn.Append((char)b);
}
var fnStr = fn.ToString();
if (fnStr.StartsWith("\\d2\\"))
if (fnStr.StartsWith(@"\d2\"))
fnStr = fnStr.Substring(4);
FileNames.Add(fnStr);
}
@ -139,12 +148,7 @@ namespace OpenDiablo2.Common.Models
if (Version >= 4)
{
NumberOfWalls = br.ReadInt32();
if (Version >= 16)
{
NumberOfFloors = br.ReadInt32();
}
else NumberOfFloors = 1;
NumberOfFloors = Version >= 16 ? br.ReadInt32() : 1;
}
else
{
@ -159,7 +163,7 @@ namespace OpenDiablo2.Common.Models
if (Version < 4)
{
layoutStream.AddRange(new int[] { 1, 9, 5, 12, 11 });
layoutStream.AddRange(new[] { 1, 9, 5, 12, 11 });
}
else
{
@ -205,21 +209,27 @@ namespace OpenDiablo2.Common.Models
};
}
TagLayers = new MPQDS1TagLayer[NumberOfTags];
for (var l = 0; l < NumberOfTags; l++)
{
TagLayers[l] = new MPQDS1TagLayer { Number = -1 };
}
for (int n = 0; n < layoutStream.Count; n++)
foreach (var idx in layoutStream)
{
for (var y = 0; y < Height; y++)
{
for (var x = 0; x < Width; x++)
{
switch (layoutStream[n])
switch (idx)
{
// Walls
case 1:
case 2:
case 3:
case 4:
WallLayers[layoutStream[n] - 1].Props[x + (y * Width)] = new MPQDS1TileProps
WallLayers[idx - 1].Props[x + (y * Width)] = new MPQDS1TileProps
{
Prop1 = br.ReadByte(),
Prop2 = br.ReadByte(),
@ -240,7 +250,7 @@ namespace OpenDiablo2.Common.Models
}
else
{
WallLayers[layoutStream[n] - 5].Orientations[x + (y * Width)] = new MPQDS1WallOrientationTileProps
WallLayers[idx - 5].Orientations[x + (y * Width)] = new MPQDS1WallOrientationTileProps
{
Orientation1 = br.ReadByte(),
Orientation2 = br.ReadByte(),
@ -253,7 +263,7 @@ namespace OpenDiablo2.Common.Models
// Floors
case 9:
case 10:
FloorLayers[layoutStream[n] - 9].Props[x + (y * Width)] = new MPQDS1TileProps
FloorLayers[idx - 9].Props[x + (y * Width)] = new MPQDS1TileProps
{
Prop1 = br.ReadByte(),
Prop2 = br.ReadByte(),
@ -263,7 +273,7 @@ namespace OpenDiablo2.Common.Models
break;
// Shadow
case 11:
ShadowLayers[layoutStream[n] - 11].Props[x + (y * Width)] = new MPQDS1TileProps
ShadowLayers[idx - 11].Props[x + (y * Width)] = new MPQDS1TileProps
{
Prop1 = br.ReadByte(),
Prop2 = br.ReadByte(),
@ -273,32 +283,80 @@ namespace OpenDiablo2.Common.Models
break;
// Tags
case 12:
// TODO: Tags
br.ReadBytes(4);
TagLayers[idx - 12].Number = br.ReadInt32();
break;
default:
throw new OpenDiablo2Exception($"Unknown layer {idx} encountered.");
}
}
}
}
// Load the objects
NumberOfObjects = br.ReadInt32();
Objects = new MPQDS1Object[NumberOfObjects];
for (var i = 0; i < NumberOfObjects; i++)
{
Objects[i] = new MPQDS1Object
{
Type = br.ReadInt32(),
Id = br.ReadInt32(),
X = br.ReadInt32(),
Y = br.ReadInt32(),
};
if (Version > 5)
Objects[i].DS1Flags = br.ReadInt32();
Objects[i].Info = engineDataManager.Objects.First(x => x.Id == Objects[i].Id);
}
if (Version >= 12 && (TagType == 1 || TagType == 2))
{
if (Version >= 18)
br.ReadInt32(); // Skip a byte (but why?)
NumberOfGroups = br.ReadInt32();
Groups = new MPQDS1Group[NumberOfGroups];
for (var i = 0; i < NumberOfGroups; i++)
{
Groups[i] = new MPQDS1Group
{
TileX = br.ReadInt32(),
TileY = br.ReadInt32(),
Width = br.ReadInt32(),
Height = br.ReadInt32()
};
if (Version >= 13)
br.ReadInt32(); // Unknown group property value (what is this???)
}
}
else
Groups = new MPQDS1Group[0];
if (Version >= 14)
{
// TODO: NPC Paths
}
var dt1Mask = level.Dt1Mask;
for (int i = 0; i < 32; i++)
for (var i = 0; i < 32; i++)
{
var isMasked = ((dt1Mask >> i) & 1) == 1;
if (!isMasked || levelType.File[i] == "0")
continue;
DT1s[i] = resourceManager.GetMPQDT1("data\\global\\tiles\\" + levelType.File[i].Replace("/", "\\"));
DT1s[i] = resourceManager.GetMPQDT1(@"data\global\tiles\" + levelType.File[i].Replace("/", "\\"));
}
LookupTable = new List<DS1LookupTable>();
foreach(var dt1 in DT1s.Where(x => x != null))
foreach (var dt1 in DT1s.Where(x => x != null))
{
foreach(var tile in dt1.Tiles)
foreach (var tile in dt1.Tiles)
{
LookupTable.Add(new DS1LookupTable
{
@ -311,7 +369,8 @@ namespace OpenDiablo2.Common.Models
}
}
}
}
}

View File

@ -46,7 +46,7 @@ namespace OpenDiablo2.Common.Models
public Int32 X2 { get; private set; }
public Int32 NumberOfTiles { get; private set; }
public MPQDT1Tile[] Tiles { get; private set; }
private readonly Int32 tileHeaderOffset;
private readonly Int32 _tileHeaderOffset;
public MPQDT1(Stream stream)
{
@ -58,19 +58,19 @@ namespace OpenDiablo2.Common.Models
stream.Seek(268, SeekOrigin.Begin); // Skip useless header info
NumberOfTiles = br.ReadInt32();
tileHeaderOffset = br.ReadInt32();
_tileHeaderOffset = br.ReadInt32();
ReadTiles(br);
ReadBlockHeaders(br);
ReadBlockGraphics(br);
}
private void ReadTiles(BinaryReader br)
{
Tiles = new MPQDT1Tile[NumberOfTiles];
for (int tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++)
for (var tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++)
{
br.BaseStream.Seek(tileHeaderOffset + (tileIndex * 96), SeekOrigin.Begin);
br.BaseStream.Seek(_tileHeaderOffset + (tileIndex * 96), SeekOrigin.Begin);
Tiles[tileIndex] = new MPQDT1Tile();
var tile = Tiles[tileIndex];
@ -86,7 +86,7 @@ namespace OpenDiablo2.Common.Models
tile.SubIndex = br.ReadInt32();
tile.RarityOrFrameIndex = br.ReadInt32();
br.ReadBytes(4);
for (int i = 0; i < 25; i++)
for (var i = 0; i < 25; i++)
tile.SubTileFlags[i] = br.ReadByte();
br.ReadBytes(7);
tile.BlockHeadersPointer = br.ReadInt32();
@ -99,12 +99,12 @@ namespace OpenDiablo2.Common.Models
private void ReadBlockHeaders(BinaryReader br)
{
for (int tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++)
for (var tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++)
{
var tile = Tiles[tileIndex];
tile.Blocks = new MPQDT1Block[tile.NumberOfBlocks];
for (int blockIndex = 0; blockIndex < tile.NumberOfBlocks; blockIndex++)
for (var blockIndex = 0; blockIndex < tile.NumberOfBlocks; blockIndex++)
{
br.BaseStream.Seek(tile.BlockHeadersPointer + (blockIndex * 20), SeekOrigin.Begin);
@ -126,9 +126,9 @@ namespace OpenDiablo2.Common.Models
private void ReadBlockGraphics(BinaryReader br)
{
for (int tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++)
for (var tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++)
{
for (int blockIndex = 0; blockIndex < Tiles[tileIndex].NumberOfBlocks; blockIndex++)
for (var blockIndex = 0; blockIndex < Tiles[tileIndex].NumberOfBlocks; blockIndex++)
{
var block = Tiles[tileIndex].Blocks[blockIndex];
br.BaseStream.Seek(Tiles[tileIndex].BlockHeadersPointer + block.FileOffset, SeekOrigin.Begin);
@ -139,17 +139,13 @@ namespace OpenDiablo2.Common.Models
if (block.Length != 256)
throw new OpenDiablo2Exception($"Expected exactly 256 bytes of data, but got {block.Length} instead!");
int x = 0;
int y = 0;
int n = 0;
int[] xjump = { 14, 12, 10, 8, 6, 4, 2, 0, 2, 4, 6, 8, 10, 12, 14 };
int[] nbpix = { 4, 8, 12, 16, 20, 24, 28, 32, 28, 24, 20, 16, 12, 8, 4 };
var y = 0;
var length = 256;
block.PixelData = new Int16[32 * 32];
while (length > 0)
{
x = xjump[y];
n = nbpix[y];
var x = new[] { 14, 12, 10, 8, 6, 4, 2, 0, 2, 4, 6, 8, 10, 12, 14 }[y];
var n = new[] { 4, 8, 12, 16, 20, 24, 28, 32, 28, 24, 20, 16, 12, 8, 4 }[y];
length -= n;
while (n > 0)
{
@ -165,15 +161,13 @@ namespace OpenDiablo2.Common.Models
{
// RLE block
var length = block.Length;
byte b1;
byte b2;
int x = 0;
int y = 0;
var x = 0;
var y = 0;
block.PixelData = new Int16[32 * 32];
while (length > 0)
{
b1 = br.ReadByte();
b2 = br.ReadByte();
var b1 = br.ReadByte();
var b2 = br.ReadByte();
length -= 2;
if (b1 + b2 == 0)
{

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Models
{
public sealed class ObjectTypeInfo
{
public string Name { get; internal set; }
public string Token { get; internal set; }
public bool Beta { get; internal set; }
}
public static class ObjectTypeInfoHelper
{
public static ObjectTypeInfo ToObjectTypeInfo(this string[] row)
=> new ObjectTypeInfo { Name = row[0], Token = row[1], Beta = Convert.ToInt32(row[2]) == 1 };
}
}

View File

@ -121,6 +121,7 @@
<Compile Include="Models\MPQDCC.cs" />
<Compile Include="Models\ItemContainerLayout.cs" />
<Compile Include="Models\ObjectInfo.cs" />
<Compile Include="Models\ObjectTypeInfo.cs" />
<Compile Include="Models\PlayerInfo.cs" />
<Compile Include="Models\PlayerLocationDetails.cs" />
<Compile Include="Interfaces\UI\IButton.cs" />

View File

@ -45,7 +45,7 @@ namespace OpenDiablo2.Core
builder.RegisterType<InventoryPanel>().AsImplementedInterfaces().InstancePerDependency();
builder.RegisterType<SkillsPanel>().AsImplementedInterfaces().InstancePerDependency();
builder.RegisterType<ItemContainer>().As<IItemContainer>().InstancePerDependency();
builder.RegisterType<MPQProvider>().As<IMPQProvider>().SingleInstance();
builder.RegisterType<MpqProvider>().As<IMPQProvider>().SingleInstance();
builder.RegisterType<ResourceManager>().As<IResourceManager>().SingleInstance();
builder.RegisterType<TextDictionary>().As<ITextDictionary>().SingleInstance();
builder.RegisterType<TextBox>().As<ITextBox>().InstancePerDependency();

View File

@ -23,6 +23,7 @@ namespace OpenDiablo2.Core
public ImmutableList<LevelPreset> LevelPresets { get; internal set; }
public ImmutableList<LevelType> LevelTypes { get; internal set; }
public ImmutableList<ObjectInfo> Objects { get; internal set; }
public ImmutableList<ObjectTypeInfo> ObjectTypes { get; internal set; }
public ImmutableList<Item> Items { get; internal set; }
public ImmutableDictionary<eHero, ILevelExperienceConfig> ExperienceConfigs { get; internal set; }
@ -83,6 +84,18 @@ namespace OpenDiablo2.Core
.Where(x => x.Count() > 150 && x[0] != "Expansion")
.Select(x => x.ToObjectInfo())
.ToImmutableList();
log.Info("Loading object types");
ObjectTypes = mpqProvider
.GetTextFile(ResourcePaths.ObjectDetails)
.Skip(1)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.Where(x => x.Count() == 3 && x[0] != "Expansion")
.Select(x => x.ToObjectTypeInfo())
.ToImmutableList();
}
private ImmutableList<Item> LoadItemData()

View File

@ -26,15 +26,15 @@ namespace OpenDiablo2.Core.GameState_
private readonly Func<eSessionType, ISessionManager> getSessionManager;
private readonly Func<string, IRandomizedMapGenerator> getRandomizedMapGenerator;
private float animationTime = 0f;
private float animationTime;
private List<IMapInfo> mapInfo;
private readonly List<MapCellInfo> mapDataLookup = new List<MapCellInfo>();
private readonly List<MapCellInfo> mapDataLookup;
private ISessionManager sessionManager;
public int Act { get; private set; }
public string MapName { get; private set; }
public Palette CurrentPalette => paletteProvider.PaletteTable[$"ACT{Act}"];
public List<PlayerInfo> PlayerInfos { get; private set; } = new List<PlayerInfo>();
public List<PlayerInfo> PlayerInfos { get; private set; }
readonly private IMouseCursor originalMouseCursor;
@ -74,7 +74,8 @@ namespace OpenDiablo2.Core.GameState_
this.getRandomizedMapGenerator = getRandomizedMapGenerator;
originalMouseCursor = renderWindow.MouseCursor;
PlayerInfos = new List<PlayerInfo>();
mapDataLookup = new List<MapCellInfo>();
}
public void Initialize(string characterName, eHero hero, eSessionType sessionType)
@ -88,8 +89,8 @@ namespace OpenDiablo2.Core.GameState_
sessionManager.OnFocusOnPlayer += OnFocusOnPlayer;
mapInfo = new List<IMapInfo>();
sceneManager.ChangeScene(eSceneType.Game);
sessionManager.JoinGame(characterName, hero);
}
@ -97,7 +98,7 @@ namespace OpenDiablo2.Core.GameState_
=> getMapEngine().FocusedPlayerId = playerId;
private void OnPlayerInfo(int clientHash, IEnumerable<PlayerInfo> playerInfo)
=> this.PlayerInfos = playerInfo.ToList();
=> PlayerInfos = playerInfo.ToList();
private void OnLocatePlayers(int clientHash, IEnumerable<PlayerLocationDetails> playerLocationDetails)
{
@ -203,9 +204,12 @@ namespace OpenDiablo2.Core.GameState_
var random = new Random(Seed);
//var mapName = "data\\global\\tiles\\" + mapNames[random.Next(mapNames.Count)].Replace("/", "\\");
// -------------------------------------------------------------------------------------
// var mapName = "data\\global\\tiles\\" + mapNames[random.Next(mapNames.Count)].Replace("/", "\\");
// -------------------------------------------------------------------------------------
// TODO: ***TEMP FOR TESTING
var mapName = "data\\global\\tiles\\" + mapNames[0].Replace("/", "\\");
// -------------------------------------------------------------------------------------
MapName = levelDetails.LevelPreset.Name;
Act = levelDetails.LevelType.Act;
@ -270,15 +274,23 @@ namespace OpenDiablo2.Core.GameState_
private IMapInfo GetMap(ref int cellX, ref int cellY)
{
var p = new Point(cellX, cellY);
var map = mapInfo.LastOrDefault(z => z.TileLocation.Contains(p));
if (map == null)
{
return null;
}
cellX -= map.TileLocation.X;
cellY -= map.TileLocation.Y;
return map;
IMapInfo mi = null;
for (var i = mapInfo.Count - 1; i >= 0; i--)
{
if (mapInfo[i].TileLocation.Contains(p))
{
mi = mapInfo[i];
break;
}
}
if (mi == null)
return null;
cellX -= mi.TileLocation.X;
cellY -= mi.TileLocation.Y;
return mi;
}
public void UpdateMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType, IEnumerable<MapCellInfo> mapCellInfo)
@ -294,9 +306,8 @@ namespace OpenDiablo2.Core.GameState_
}
else
{
var cursorsprite = renderWindow.LoadSprite(ResourcePaths.GeneratePathForItem(item.Item.InvFile), Palettes.Units);
renderWindow.MouseCursor = renderWindow.LoadCursor(cursorsprite, 0, new Point(cursorsprite.FrameSize.Width / 2, cursorsprite.FrameSize.Height / 2));
var cursorSprite = renderWindow.LoadSprite(ResourcePaths.GeneratePathForItem(item.Item.InvFile), Palettes.Units);
renderWindow.MouseCursor = renderWindow.LoadCursor(cursorSprite, 0, new Point(cursorSprite.FrameSize.Width / 2, cursorSprite.FrameSize.Height / 2));
}
this.SelectedItem = item;
@ -317,8 +328,8 @@ namespace OpenDiablo2.Core.GameState_
if (cellInfo != null && (cellInfo.Ignore || !cellInfo.Tile.Animated))
return cellInfo.Ignore ? null : cellInfo;
var main_index = (props.Prop3 >> 4) + ((props.Prop4 & 0x03) << 4);
var sub_index = props.Prop2;
var mainIndex = (props.Prop3 >> 4) + ((props.Prop4 & 0x03) << 4);
var subIndex = props.Prop2;
if (orientation == 0)
{
@ -375,15 +386,14 @@ namespace OpenDiablo2.Core.GameState_
}
}
int frame = 0;
IEnumerable<MPQDT1Tile> tiles = Enumerable.Empty<MPQDT1Tile>();
tiles = map.FileData.LookupTable
.Where(x => x.MainIndex == main_index && x.SubIndex == sub_index && x.Orientation == orientation)
.Where(x => x.MainIndex == mainIndex && x.SubIndex == subIndex && x.Orientation == orientation)
.Select(x => x.TileRef);
if (tiles == null || !tiles.Any())
{
log.Error($"Could not find tile [{main_index}:{sub_index}:{orientation}]!");
log.Error($"Could not find tile [{mainIndex}:{subIndex}:{orientation}]!");
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
@ -438,7 +448,7 @@ namespace OpenDiablo2.Core.GameState_
}
var mapCellInfo = mapDataLookup.FirstOrDefault(x => x.Tile.Id == tile.Id && x.AnimationId == frame);
var mapCellInfo = mapDataLookup.FirstOrDefault(x => x.Tile.Id == tile.Id);
if (mapCellInfo == null)
{
mapCellInfo = renderWindow.CacheMapCell(tile, cellType);

View File

@ -27,45 +27,52 @@ using System.Linq;
namespace OpenDiablo2.Core
{
public sealed class MPQProvider : IMPQProvider
public sealed class MpqProvider : IMPQProvider
{
private readonly IList<MPQ> mpqs;
private readonly Dictionary<string, int> mpqLookup = new Dictionary<string, int>();
private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private readonly IList<MPQ> _mpqs;
private readonly Dictionary<string, int> _mpqLookup = new Dictionary<string, int>();
public MPQProvider(GlobalConfiguration globalConfiguration)
public MpqProvider(GlobalConfiguration globalConfiguration)
{
// TODO: Make this less dumb. We need to an external file to configure mpq load order.
this.mpqs = Directory
_mpqs = Directory
.EnumerateFiles(globalConfiguration.BaseDataPath, "*.mpq")
.Where(x => !Path.GetFileName(x).StartsWith("patch"))
.Where(x => !(Path.GetFileName(x)?.StartsWith("patch") ?? false))
.Select(file => new MPQ(file))
.ToArray();
if (!_mpqs.Any())
{
_log.Fatal("No data files were found! Are you specifying the correct data path?");
throw new OpenDiablo2Exception("No data files were found.");
}
// Load the base game files
for(var i = 0; i < mpqs.Count(); i++)
for(var i = 0; i < _mpqs.Count; i++)
{
if (Path.GetFileName(mpqs[i].Path).StartsWith("d2exp") || Path.GetFileName(mpqs[i].Path).StartsWith("d2x"))
var path = Path.GetFileName(_mpqs[i].Path) ?? string.Empty;
if (path.StartsWith("d2exp") || path.StartsWith("d2x"))
continue;
foreach(var file in mpqs[i].Files)
{
mpqLookup[file.ToLower()] = i;
}
foreach(var file in _mpqs[i].Files)
_mpqLookup[file.ToLower()] = i;
}
// Load the expansion game files
for (var i = 0; i < mpqs.Count(); i++)
for (var i = 0; i < _mpqs.Count; i++)
{
if (!Path.GetFileName(mpqs[i].Path).StartsWith("d2exp") && !Path.GetFileName(mpqs[i].Path).StartsWith("d2x"))
var path = Path.GetFileName(_mpqs[i].Path) ?? string.Empty;
if (!path.StartsWith("d2exp") && !path.StartsWith("d2x"))
continue;
foreach (var file in mpqs[i].Files)
{
mpqLookup[file.ToLower()] = i;
}
foreach (var file in _mpqs[i].Files)
_mpqLookup[file.ToLower()] = i;
}
}
@ -77,36 +84,26 @@ namespace OpenDiablo2.Core
return result;
}
public IEnumerator<MPQ> GetEnumerator()
{
return mpqs.GetEnumerator();
}
public IEnumerator<MPQ> GetEnumerator() => _mpqs.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public Stream GetStream(string fileName)
{
if (!mpqLookup.ContainsKey(fileName.ToLower()))
return null;
public Stream GetStream(string fileName) => !_mpqLookup.ContainsKey(fileName.ToLower())
? null
: _mpqs[_mpqLookup[fileName.ToLower()]].OpenFile(fileName);
return mpqs[mpqLookup[fileName.ToLower()]].OpenFile(fileName);
}
public IEnumerable<string> GetTextFile(string fileName)
=> new StreamReader(mpqs[mpqLookup[fileName.ToLower()]].OpenFile(fileName)).ReadToEnd().Split('\n');
=> new StreamReader(_mpqs[_mpqLookup[fileName.ToLower()]].OpenFile(fileName)).ReadToEnd().Split('\n');
public string GetCharacterDccPath(eHero hero, eMobMode mobMode, eCompositType compositType, PlayerEquipment equipment)
{
var fileName = ($@"{ResourcePaths.PlayerAnimationBase}\{hero.ToToken()}\{compositType.ToToken()}\{hero.ToToken()}{compositType.ToToken()}").ToLower();
var fileName = $@"{ResourcePaths.PlayerAnimationBase}\{hero.ToToken()}\{compositType.ToToken()}\{hero.ToToken()}{compositType.ToToken()}".ToLower();
switch (compositType)
{
case eCompositType.Head:
fileName += $"{equipment.Head?.Item.Code ?? eArmorType.Lite.ToToken()}{mobMode.ToToken()}";
return mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower())
return _mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower())
? $"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower()
: $"{fileName}{eWeaponClass.HandToHand.ToToken()}.dcc".ToLower();
case eCompositType.Torso:
@ -114,7 +111,7 @@ namespace OpenDiablo2.Core
case eCompositType.RightArm:
case eCompositType.LeftArm:
fileName += $"{equipment.ArmorType.ToToken()}{mobMode.ToToken()}";
return mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower())
return _mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower())
? $"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower()
: $"{fileName}{eWeaponClass.HandToHand.ToToken()}.dcc".ToLower();
case eCompositType.RightHand:
@ -131,14 +128,14 @@ namespace OpenDiablo2.Core
if (!(equipment.LeftArm?.Item is Armor))
return null;
fileName += $"{equipment.LeftArm.Item.Code}{mobMode.ToToken()}";
return mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower())
return _mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower())
? $"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower()
: $"{fileName}{eWeaponClass.HandToHand.ToToken()}.dcc".ToLower();
// TODO: Figure these out...
case eCompositType.Special1:
case eCompositType.Special2:
fileName += $"{equipment.ArmorType.ToToken()}{mobMode.ToToken()}{equipment.WeaponClass}.dcc".ToLower();
return mpqLookup.ContainsKey(fileName)
return _mpqLookup.ContainsKey(fileName)
? fileName
: null; // TODO: Should we silence this?
case eCompositType.Special3:

View File

@ -11,29 +11,32 @@ namespace OpenDiablo2.Core.Map_Engine
{
public sealed class MapEngine : IMapEngine
{
private readonly IGameState gameState;
private readonly IRenderWindow renderWindow;
private readonly IGameState _gameState;
private readonly IRenderWindow _renderWindow;
private readonly List<ICharacterRenderer> characterRenderers = new List<ICharacterRenderer>();
private readonly List<ICharacterRenderer> _characterRenderers = new List<ICharacterRenderer>();
public int FocusedPlayerId { get; set; } = 0;
private PointF cameraLocation = new PointF();
private PointF _cameraLocation = new PointF();
public PointF CameraLocation
{
get => cameraLocation;
get => _cameraLocation;
set
{
if (cameraLocation == value)
// ReSharper disable once RedundantCheckBeforeAssignment (This is a false positive)
if (_cameraLocation == value)
return;
cameraLocation = value;
_cameraLocation = value;
}
}
private const int
cellSizeX = 160,
cellSizeY = 80;
CellSizeX = 160,
CellSizeY = 80,
CellSizeXHalf = 80,
CellSizeYHalf = 40;
public MapEngine(
IGameState gameState,
@ -41,8 +44,8 @@ namespace OpenDiablo2.Core.Map_Engine
ISessionManager sessionManager
)
{
this.gameState = gameState;
this.renderWindow = renderWindow;
_gameState = gameState;
_renderWindow = renderWindow;
sessionManager.OnPlayerInfo += OnPlayerInfo;
sessionManager.OnLocatePlayers += OnLocatePlayers;
@ -52,7 +55,12 @@ namespace OpenDiablo2.Core.Map_Engine
{
foreach (var loc in playerLocationDetails)
{
var cr = characterRenderers.FirstOrDefault(x => x.LocationDetails.PlayerId == loc.PlayerId);
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;
@ -64,10 +72,10 @@ namespace OpenDiablo2.Core.Map_Engine
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));
_characterRenderers.RemoveAll(x => playerInfo.Any(z => z.UID == x.UID));
// Update existing character renderers
foreach (var cr in characterRenderers)
foreach (var cr in _characterRenderers)
{
var info = playerInfo.FirstOrDefault(x => x.UID == cr.UID);
if (info == null)
@ -82,67 +90,70 @@ namespace OpenDiablo2.Core.Map_Engine
}
// Add character renderers for characters that now exist
foreach (var info in playerInfo.Where(x => !characterRenderers.Any(z => x.UID == z.UID)))
foreach (var info in playerInfo.Where(x => _characterRenderers.All(z => x.UID != z.UID)).ToArray())
{
var cr = renderWindow.CreateCharacterRenderer();
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);
_characterRenderers.Add(cr);
cr.ResetAnimationData();
}
}
const int skewX = 400;
const int skewY = 300;
private const int SkewX = 400;
private const int SkewY = 300;
public void Render()
{
var xOffset = gameState.CameraOffset;
var xOffset = _gameState.CameraOffset;
var cx = -(cameraLocation.X - Math.Truncate(cameraLocation.X));
var cy = -(cameraLocation.Y - Math.Truncate(cameraLocation.Y));
var cx = -(_cameraLocation.X - Math.Truncate(_cameraLocation.X));
var cy = -(_cameraLocation.Y - Math.Truncate(_cameraLocation.Y));
for (int ty = -7; ty <= 9; ty++)
for (var ty = -7; ty <= 9; ty++)
{
for (int tx = -8; tx <= 8; tx++)
for (var tx = -8; tx <= 8; tx++)
{
var ax = tx + Math.Truncate(cameraLocation.X);
var ay = ty + Math.Truncate(cameraLocation.Y);
var ax = (int)(tx + Math.Truncate(_cameraLocation.X));
var ay = (int)(ty + Math.Truncate(_cameraLocation.Y));
var px = (tx - ty) * (cellSizeX / 2);
var py = (tx + ty) * (cellSizeY / 2);
var px = (tx - ty) * CellSizeXHalf;
var py = (tx + ty) * CellSizeYHalf;
var ox = (cx - cy) * (cellSizeX / 2);
var oy = (cx + cy) * (cellSizeY / 2);
var ox = (cx - cy) * CellSizeXHalf;
var oy = (cx + cy) * CellSizeYHalf;
foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)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.WallLower))
_renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy + 80);
foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.Floor))
renderWindow.DrawMapCell(cellInfo, skewX + px + (int)ox + xOffset, skewY + py + (int)oy);
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((int)ax, (int)ay, eRenderCellType.WallNormal))
renderWindow.DrawMapCell(cellInfo, skewX + px + (int)ox + xOffset, skewY + py + (int)oy + 80);
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 => Math.Truncate(x.LocationDetails.PlayerX) == ax && Math.Truncate(x.LocationDetails.PlayerY) == ay))
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 ptx = character.LocationDetails.PlayerX - Math.Truncate(_cameraLocation.X);
var pty = character.LocationDetails.PlayerY - Math.Truncate(_cameraLocation.Y);
var ppx = (ptx - pty) * (cellSizeX / 2);
var ppy = (ptx + pty) * (cellSizeY / 2);
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);
character.Render(SkewX + (int)ppx + (int)ox + xOffset, SkewY + (int)ppy + (int)oy);
}
foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.Roof))
renderWindow.DrawMapCell(cellInfo, skewX + px + (int)ox + xOffset, skewY + py + (int)oy - 80);
foreach (var cellInfo in _gameState.GetMapCellInfo(ax, ay, eRenderCellType.Roof))
_renderWindow.DrawMapCell(cellInfo, SkewX + px + (int)ox + xOffset, SkewY + py + (int)oy - 80);
}
}
@ -154,18 +165,18 @@ namespace OpenDiablo2.Core.Map_Engine
public void Update(long ms)
{
foreach (var character in characterRenderers)
foreach (var character in _characterRenderers)
character.Update(ms);
if (FocusedPlayerId != 0)
{
var player = gameState.PlayerInfos.FirstOrDefault(x => x.LocationDetails.PlayerId == FocusedPlayerId);
if (player != null)
{
// TODO: Maybe smooth movement? Maybe not?
CameraLocation = new PointF(player.LocationDetails.PlayerX, player.LocationDetails.PlayerY);
}
}
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

@ -189,8 +189,8 @@ namespace OpenDiablo2.SDL2_
{
var texture = SDL.SDL_CreateTexture(renderer, SDL.SDL_PIXELFORMAT_ARGB8888, (int)SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, frameW, frameH);
SDL.SDL_LockTexture(texture, IntPtr.Zero, out IntPtr pixels, out int pitch);
UInt32* data = (UInt32*)pixels;
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)
+ (frameIndex * animationData.NumberOfLayers);

View File

@ -11,7 +11,7 @@ namespace OpenDiablo2.TestConsole
static class Program
{
private static GlobalConfiguration GlobalConfig = null;
private static MPQProvider MPQProv = null;
private static MpqProvider MPQProv = null;
private static EngineDataManager EngineDataMan = null;
static void Main(string[] args)
@ -126,7 +126,7 @@ namespace OpenDiablo2.TestConsole
BaseDataPath = Path.GetFullPath(path)
};
MPQProv = new MPQProvider(GlobalConfig);
MPQProv = new MpqProvider(GlobalConfig);
EngineDataMan = new EngineDataManager(MPQProv);
}