1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-12-26 12:06:24 -05:00

Started writing map data logic.

This commit is contained in:
Tim Sarbin 2018-11-24 03:07:41 -05:00
parent c4fac82bc3
commit 6c2847e4b3
14 changed files with 643 additions and 7 deletions

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenDiablo2.Common.Models;
namespace OpenDiablo2.Common.Interfaces
{
public interface IEngineDataManager
{
List<LevelPreset> LevelPresets { get; }
List<LevelType> LevelTypes { get; }
}
}

View File

@ -11,6 +11,8 @@ namespace OpenDiablo2.Common.Interfaces
{
ImageSet GetImageSet(string resourcePath);
MPQFont GetMPQFont(string resourcePath);
MPQDS1 GetMPQDS1(string resourcePath, int definition, int act);
MPQDT1 GetMPQDT1(string resourcePath);
Palette GetPalette(string paletteName);
}
}

View File

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Models
{
//
public sealed class LevelPreset
{
public string Name { get; set; }
public int Def { get; set; }
public int LevelId { get; set; }
public bool Populate { get; set; }
public int Logicals { get; set; }
public int Outdoors { get; set; }
public int Animate { get; set; }
public int KillEdge { get; set; }
public int FillBlanks { get; set; }
public int SizeX { get; set; }
public int SizeY { get; set; }
public int AutoMap { get; set; }
public int Scan { get; set; }
public int Pops { get; set; }
public int PopPad { get; set; }
public int Files { get; set; }
public string File1 { get; set; }
public string File2 { get; set; }
public string File3 { get; set; }
public string File4 { get; set; }
public string File5 { get; set; }
public string File6 { get; set; }
public UInt32 Dt1Mask { get; set; }
public bool Beta { get; set; }
}
public static class LevelPresetHelper
{
public static LevelPreset ToLevelPreset(this string[] row)
=> new LevelPreset
{
Name = row[0],
Def = Convert.ToInt32(row[1]),
LevelId = row[2] == "#REF!" ? 0 : Convert.ToInt32(row[2]), // TODO: Why is this a thing?
Populate = Convert.ToInt32(row[3]) == 1,
Logicals = Convert.ToInt32(row[4]),
Outdoors = Convert.ToInt32(row[5]),
Animate = Convert.ToInt32(row[6]),
KillEdge = Convert.ToInt32(row[7]),
FillBlanks = Convert.ToInt32(row[8]),
SizeX = Convert.ToInt32(row[9]),
SizeY = Convert.ToInt32(row[10]),
AutoMap = Convert.ToInt32(row[11]),
Scan = Convert.ToInt32(row[12]),
Pops = Convert.ToInt32(row[13]),
PopPad = Convert.ToInt32(row[14]),
Files = Convert.ToInt32(row[15]),
File1 = row[16],
File2 = row[17],
File3 = row[18],
File4 = row[19],
File5 = row[20],
File6 = row[21],
Dt1Mask = Convert.ToUInt32(row[22]),
Beta = Convert.ToInt32(row[23]) == 1,
};
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Models
{
public sealed class LevelType
{
public string Name { get; set; }
public int Id { get; set; }
public string[] File { get; set; } = new string[32];
public bool Beta { get; set; }
public int Act { get; set; }
}
public static class LevelTypeHelper
{
public static LevelType ToLevelType(this string[] row)
{
var result = new LevelType
{
Name = row[0],
Id = Convert.ToInt32(row[1]),
Beta = Convert.ToInt32(row[34]) == 1,
Act = Convert.ToInt32(row[35])
};
for (int i = 0; i < 32; i++)
{
result.File[i] = row[i + 2];
}
return result;
}
}
}

View File

@ -0,0 +1,241 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenDiablo2.Common.Interfaces;
namespace OpenDiablo2.Common.Models
{
public sealed class MPQDS1TileProps
{
public byte Prop1 { get; internal set; }
public byte Prop2 { get; internal set; }
public byte Prop3 { get; internal set; }
public byte Prop4 { get; internal set; }
}
public sealed class MPQDS1WallOrientationTileProps
{
public byte Orientation1 { get; internal set; }
public byte Orientation2 { get; internal set; }
public byte Orientation3 { get; internal set; }
public byte Orientation4 { get; internal set; }
}
public sealed class MPQDS1WallLayer
{
public MPQDS1TileProps[] Props { get; internal set; }
public MPQDS1WallOrientationTileProps[] Orientations { get; internal set; }
}
public sealed class MPQDS1FloorLayer
{
public MPQDS1TileProps[] Props { get; internal set; }
}
public sealed class MPQDS1ShadowLayer
{
public MPQDS1TileProps[] Props { get; internal set; }
}
public sealed class MPQDS1Object
{
public UInt32 Type { get; internal set; }
public UInt32 Id { get; internal set; }
public UInt32 X { get; internal set; }
public UInt32 Y { get; internal set; }
public UInt32 DS1Flags { get; internal set; }
}
public sealed class MPQDS1
{
static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public UInt32 Version { get; internal set; }
public UInt32 Width { get; internal set; }
public UInt32 Height { get; internal set; }
public UInt32 Act { get; internal set; } // ???
public UInt32 TagType { get; internal set; } // ???
public UInt32 FileCount { get; internal set; }
public UInt32 NumberOfWalls { get; internal set; }
public UInt32 NumberOfFloors { get; internal set; }
public UInt32 NumberOfObjects { get; internal set; }
public UInt32 NumberOfNPCs { get; internal set; }
public MPQDT1[] DT1s = new MPQDT1[32];
public List<string> FileNames { get; internal set; } = new List<string>();
public List<MPQDS1WallLayer> WallLayers { get; internal set; } = new List<MPQDS1WallLayer>();
public List<MPQDS1FloorLayer> FloorLayers { get; internal set; } = new List<MPQDS1FloorLayer>();
public MPQDS1ShadowLayer ShadowLayer { get; internal set; } = new MPQDS1ShadowLayer();
public List<MPQDS1Object> Objects { get; internal set; } = new List<MPQDS1Object>();
// TODO: DI magic please
public MPQDS1(Stream stream, string fileName, int definition, int act, IEngineDataManager engineDataManager, IResourceManager resourceManager)
{
log.Debug($"Loading {fileName} (Act {act}) Def {definition}");
var br = new BinaryReader(stream);
Version = br.ReadUInt32();
Width = br.ReadUInt32() + 1;
Height = br.ReadUInt32() + 1;
Act = br.ReadUInt32();
TagType = br.ReadUInt32();
FileCount = br.ReadUInt32();
if (TagType != 0)
throw new ApplicationException("We don't currently handle those tag things...");
for (int i = 0; i < FileCount; i++)
{
var fn = "";
while (true)
{
var b = br.ReadByte();
if (b == 0)
break;
fn += (char)b;
}
if (fn.StartsWith("\\d2\\"))
fn = fn.Substring(4);
FileNames.Add(fn);
}
NumberOfWalls = br.ReadUInt32();
NumberOfFloors = br.ReadUInt32();
for (int i = 0; i < NumberOfWalls; i++)
{
var wallLayer = new MPQDS1WallLayer
{
Props = new MPQDS1TileProps[Width * Height],
Orientations = new MPQDS1WallOrientationTileProps[Width * Height]
};
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
wallLayer.Props[x + (y * Width)] = new MPQDS1TileProps
{
Prop1 = br.ReadByte(),
Prop2 = br.ReadByte(),
Prop3 = br.ReadByte(),
Prop4 = br.ReadByte()
};
}
}
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
wallLayer.Orientations[x + (y * Width)] = new MPQDS1WallOrientationTileProps
{
Orientation1 = br.ReadByte(),
Orientation2 = br.ReadByte(),
Orientation3 = br.ReadByte(),
Orientation4 = br.ReadByte()
};
}
}
WallLayers.Add(wallLayer);
}
for (int i = 0; i < NumberOfFloors; i++)
{
var floorLayer = new MPQDS1FloorLayer
{
Props = new MPQDS1TileProps[Width * Height]
};
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
floorLayer.Props[x + (y * Width)] = new MPQDS1TileProps
{
Prop1 = br.ReadByte(),
Prop2 = br.ReadByte(),
Prop3 = br.ReadByte(),
Prop4 = br.ReadByte()
};
}
}
FloorLayers.Add(floorLayer);
}
ShadowLayer.Props = new MPQDS1TileProps[Width * Height];
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
ShadowLayer.Props[x + (y * Width)] = new MPQDS1TileProps
{
Prop1 = br.ReadByte(),
Prop2 = br.ReadByte(),
Prop3 = br.ReadByte(),
Prop4 = br.ReadByte()
};
}
}
// TODO: Tag layer goes here (tag = 1)
NumberOfObjects = br.ReadUInt32();
for (int i = 0; i < NumberOfObjects; i++)
{
Objects.Add(new MPQDS1Object
{
Type = br.ReadUInt32(),
Id = br.ReadUInt32(),
X = br.ReadUInt32(),
Y = br.ReadUInt32(),
DS1Flags = br.ReadUInt32()
});
}
// TODO: Option groups go here (tag = 1)
NumberOfNPCs = br.ReadUInt32();
// TODO: WalkPaths
LevelPreset levelPreset;
if (definition == -1)
{
levelPreset = engineDataManager.LevelPresets.First(x =>
x.File1.ToLower() == fileName.ToLower()
|| x.File2.ToLower() == fileName.ToLower()
|| x.File3.ToLower() == fileName.ToLower()
|| x.File4.ToLower() == fileName.ToLower()
|| x.File5.ToLower() == fileName.ToLower()
|| x.File6.ToLower() == fileName.ToLower());
}
else
{
levelPreset = engineDataManager.LevelPresets.First(x => x.Def == definition);
}
var dt1Mask = levelPreset.Dt1Mask;
var levelType = engineDataManager.LevelTypes.First(x => x.Id == levelPreset.LevelId && x.Act == act);
for (int i = 0; i < 32; i++)
{
var tilePath = levelType.File[i];
var isMasked = ((dt1Mask >> i) & 1) == 1;
if (!isMasked)
continue;
log.Debug($"Loading DT resource {levelType.File[i]}");
DT1s[i] = resourceManager.GetMPQDT1("data\\global\\tiles\\" + levelType.File[i].Replace("/", "\\"));
}
}
}
}

View File

@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Models
{
public sealed class MPQDT1Block
{
public UInt16 PositionX { get; internal set; }
public UInt16 PositionY { get; internal set; }
public byte GridX { get; internal set; }
public byte GridY { get; internal set; }
public UInt16 Format { get; internal set; }
public UInt32 Length { get; internal set; }
public Int32 FileOffset { get; internal set; }
public Int16[] PixelData { get; internal set; }
}
public sealed class MPQDT1Tile
{
public UInt32 Direction { get; internal set; }
public UInt16 RoofHeight { get; internal set; }
public byte SoundIndex { get; internal set; }
public bool Animated { get; internal set; }
public int Height { get; internal set; }
public int Width { get; internal set; }
public UInt32 Orientation { get; internal set; }
public UInt32 MainIndex { get; internal set; }
public UInt32 SubIndex { get; internal set; }
public UInt32 RarityOrFrameIndex { get; internal set; }// Frame index if animated floor tile
public byte[] SubTileFlags { get; internal set; } = new byte[25];
public UInt32 BlockHeadersPointer { get; internal set; } // Pointer to block headers for this tile
public UInt32 BlockDataLength { get; internal set; } // Block headers + block data of this tile
public UInt32 NumberOfBlocks { get; internal set; }
public MPQDT1Block[] Blocks { get; internal set; }
}
public sealed class MPQDT1
{
public UInt32 NumberOfTiles { get; private set; }
public MPQDT1Tile[] Tiles { get; private set; }
public MPQDT1(Stream stream)
{
var br = new BinaryReader(stream);
stream.Seek(268, SeekOrigin.Begin); // Skip useless header info
NumberOfTiles = br.ReadUInt32();
var tileHeaderOffset = br.ReadUInt32();
stream.Seek(tileHeaderOffset, SeekOrigin.Begin);
Tiles = new MPQDT1Tile[NumberOfTiles];
for (int tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++)
{
Tiles[tileIndex] = new MPQDT1Tile();
Tiles[tileIndex].Direction = br.ReadUInt32();
Tiles[tileIndex].RoofHeight = br.ReadUInt16();
Tiles[tileIndex].SoundIndex = br.ReadByte();
Tiles[tileIndex].Animated = br.ReadByte() == 1;
Tiles[tileIndex].Height = br.ReadInt32();
Tiles[tileIndex].Width = br.ReadInt32();
br.ReadBytes(4);
Tiles[tileIndex].Orientation = br.ReadUInt32();
Tiles[tileIndex].MainIndex = br.ReadUInt32();
Tiles[tileIndex].SubIndex = br.ReadUInt32();
Tiles[tileIndex].RarityOrFrameIndex = br.ReadUInt32();
br.ReadBytes(4);
for (int i = 0; i < 25; i++)
Tiles[tileIndex].SubTileFlags[i] = br.ReadByte();
br.ReadBytes(7);
Tiles[tileIndex].BlockHeadersPointer = br.ReadUInt32();
Tiles[tileIndex].BlockDataLength = br.ReadUInt32();
Tiles[tileIndex].NumberOfBlocks = br.ReadUInt32();
br.ReadBytes(12);
}
for (int tileIndex = 0; tileIndex < NumberOfTiles; tileIndex++)
{
var tile = Tiles[tileIndex];
if (tile.BlockHeadersPointer == 0)
{
tile.Blocks = new MPQDT1Block[0];
continue;
}
stream.Seek(tile.BlockHeadersPointer, SeekOrigin.Begin);
tile.Blocks = new MPQDT1Block[tile.NumberOfBlocks];
for (int blockIndex = 0; blockIndex < tile.NumberOfBlocks; blockIndex++)
{
tile.Blocks[blockIndex] = new MPQDT1Block();
tile.Blocks[blockIndex].PositionX = br.ReadUInt16();
tile.Blocks[blockIndex].PositionY = br.ReadUInt16();
br.ReadBytes(2);
tile.Blocks[blockIndex].GridX = br.ReadByte();
tile.Blocks[blockIndex].GridX = br.ReadByte();
tile.Blocks[blockIndex].Format = br.ReadUInt16();
tile.Blocks[blockIndex].Length = br.ReadUInt32();
br.ReadBytes(2);
tile.Blocks[blockIndex].FileOffset = br.ReadInt32();
}
for (int blockIndex = 0; blockIndex < tile.NumberOfBlocks; blockIndex++)
{
var block = tile.Blocks[blockIndex];
stream.Seek(tile.BlockHeadersPointer + block.FileOffset, SeekOrigin.Begin);
if (block.Format == 1)
{
// 3D isometric block
if (block.Length != 256)
throw new ApplicationException($"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 length = 256;
block.PixelData = new Int16[32 * 16];
while (length > 0)
{
x = xjump[y];
n = nbpix[y];
length -= n;
while (n > 0)
{
block.PixelData[x + (y * 32)] = br.ReadByte();
x++;
n--;
}
y++;
}
}
else
{
// TODO: This doesn't work.. memory pointer issues?
continue;
// RLE block
var length = block.Length;
byte b1;
byte b2;
int x = 0;
int y = 0;
block.PixelData = new Int16[32 * 16];
while (length > 0)
{
b1 = br.ReadByte();
b2 = br.ReadByte();
length -= 2;
if (b1 != 0 || b2 != 0)
{
x += b1;
length -= b2;
while(b2 > 0)
{
block.PixelData[x + (y * 32)] = br.ReadByte();
br.ReadByte();
x++;
b2--;
}
}
else
{
x = 0;
y++;
}
}
}
}
}
}
}
}

View File

@ -73,6 +73,7 @@
<Compile Include="Enums\eButtonType.cs" />
<Compile Include="Enums\eHero.cs" />
<Compile Include="Enums\eMPQFormatVersion.cs" />
<Compile Include="Interfaces\IEngineDataManager.cs" />
<Compile Include="Interfaces\IFont.cs" />
<Compile Include="Interfaces\IGameEngine.cs" />
<Compile Include="Interfaces\IKeyboardInfoProvider.cs" />
@ -91,6 +92,10 @@
<Compile Include="Interfaces\ITextLabel.cs" />
<Compile Include="Models\BitStream.cs" />
<Compile Include="Models\ButtonLayout.cs" />
<Compile Include="Models\LevelPreset.cs" />
<Compile Include="Models\LevelType.cs" />
<Compile Include="Models\MPQDS1.cs" />
<Compile Include="Models\MPQDT1.cs" />
<Compile Include="Models\MPQFont.cs" />
<Compile Include="Models\GlobalConfiguration.cs" />
<Compile Include="Models\ImageSet.cs" />

View File

@ -106,6 +106,11 @@ namespace OpenDiablo2.Common
// TODO: Doesn't sound right :)
public static string EnglishTable = "data\\local\\lng\\eng\\English.txt";
public static string ExpansionStringTable = "data\\local\\lng\\eng\\expansionstring.tbl";
public static string LevelPreset = "data\\global\\excel\\LvlPrest.txt";
public static string LevelType = "data\\global\\excel\\LvlTypes.txt";
// --- Maps ---
public static string MapAct1TownE1 = "data\\global\\tiles\\ACT1\\Town\\townE1.ds1";
}
}

View File

@ -22,9 +22,10 @@ namespace OpenDiablo2.Core
builder.RegisterType<MPQProvider>().As<IMPQProvider>().SingleInstance();
builder.RegisterType<ResourceManager>().As<IResourceManager>().SingleInstance();
builder.RegisterType<TextDictionary>().As<ITextDictionary>().SingleInstance();
builder.RegisterType<Button>().AsSelf().InstancePerDependency();
builder.RegisterType<TextBox>().AsSelf().InstancePerDependency();
builder.RegisterType<GameState>().AsSelf().SingleInstance();
builder.RegisterType<Button>().AsSelf().InstancePerDependency(); // TODO: Never register as Self() if we aren't in common...
builder.RegisterType<TextBox>().AsSelf().InstancePerDependency(); // TODO: Never register as Self() if we aren't in common...
builder.RegisterType<GameState>().AsSelf().SingleInstance(); // TODO: Never register as Self() if we aren't in common...
builder.RegisterType<EngineDataManager>().As<IEngineDataManager>().SingleInstance();
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenDiablo2.Common;
using OpenDiablo2.Common.Interfaces;
using OpenDiablo2.Common.Models;
namespace OpenDiablo2.Core
{
public sealed class EngineDataManager : IEngineDataManager
{
private readonly IMPQProvider mpqProvider;
public List<LevelPreset> LevelPresets { get; internal set; }
public List<LevelType> LevelTypes { get; internal set; }
public EngineDataManager(IMPQProvider mpqProvider)
{
this.mpqProvider = mpqProvider;
LoadLevelPresets();
LoadLevelTypes();
}
private void LoadLevelTypes()
{
var data = mpqProvider
.GetTextFile(ResourcePaths.LevelType)
.First()
.Skip(1)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.ToArray()
.Select(x => x.ToLevelType());
LevelTypes = new List<LevelType>(data);
}
private void LoadLevelPresets()
{
var data = mpqProvider
.GetTextFile(ResourcePaths.LevelPreset)
.First()
.Skip(1)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.ToArray()
.Select(x => x.ToLevelPreset());
LevelPresets = new List<LevelPreset>(data);
}
}
}

View File

@ -3,24 +3,29 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenDiablo2.Common;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Interfaces;
using OpenDiablo2.Common.Models;
namespace OpenDiablo2.Core.GameState_
{
public sealed class GameState
{
private readonly ISceneManager sceneManager;
private readonly IResourceManager resourceManager;
MPQDS1 mapData;
public GameState(ISceneManager sceneManager)
public GameState(ISceneManager sceneManager, IResourceManager resourceManager)
{
this.sceneManager = sceneManager;
this.resourceManager = resourceManager;
}
public void Initialize(string characterName, eHero hero)
{
sceneManager.ChangeScene("Game");
mapData = resourceManager.GetMPQDS1(ResourcePaths.MapAct1TownE1, -1, 1);
}
}
}

View File

@ -73,6 +73,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AutofacModule.cs" />
<Compile Include="EngineDataManager.cs" />
<Compile Include="GameEngine.cs" />
<Compile Include="GameState\GameState.cs" />
<Compile Include="MPQProvider.cs" />

View File

@ -11,13 +11,17 @@ namespace OpenDiablo2.Core
public sealed class ResourceManager : IResourceManager
{
private readonly IMPQProvider mpqProvider;
private readonly IEngineDataManager engineDataManager;
private Dictionary<string, ImageSet> ImageSets = new Dictionary<string, ImageSet>();
private Dictionary<string, MPQFont> MPQFonts = new Dictionary<string, MPQFont>();
private Dictionary<string, Palette> Palettes = new Dictionary<string, Palette>();
private Dictionary<string, MPQDT1> DTs = new Dictionary<string, MPQDT1>();
public ResourceManager(IMPQProvider mpqProvider)
public ResourceManager(IMPQProvider mpqProvider, IEngineDataManager engineDataManager)
{
this.mpqProvider = mpqProvider;
this.engineDataManager = engineDataManager;
}
public ImageSet GetImageSet(string resourcePath)
@ -36,6 +40,12 @@ namespace OpenDiablo2.Core
return MPQFonts[resourcePath];
}
public MPQDS1 GetMPQDS1(string resourcePath, int definition, int act)
{
var mapName = resourcePath.Replace("data\\global\\tiles\\", "").Replace("\\", "/");
return new MPQDS1(mpqProvider.GetStream(resourcePath), mapName, definition, act, engineDataManager, this);
}
public Palette GetPalette(string paletteFile)
{
if (!Palettes.ContainsKey(paletteFile))
@ -49,5 +59,13 @@ namespace OpenDiablo2.Core
}
public MPQDT1 GetMPQDT1(string resourcePath)
{
if (!DTs.ContainsKey(resourcePath))
DTs[resourcePath] = new MPQDT1(mpqProvider.GetStream(resourcePath));
return DTs[resourcePath];
}
}
}

View File

@ -39,6 +39,7 @@ namespace OpenDiablo2.Scenes
IMouseInfoProvider mouseInfoProvider,
IMusicProvider musicProvider,
ISceneManager sceneManager,
IResourceManager resourceManager,
Func<eButtonType, Button> createButton,
Func<string, IScene> getScene // Temporary until SDL load functions are sped up
)