1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-12-25 19:46:50 -05:00

Added support for multiple maps. And cached the caches.

This commit is contained in:
Tim Sarbin 2018-11-28 19:28:41 -05:00
parent 5c7bba87d1
commit 8e603f987e
13 changed files with 522 additions and 309 deletions

View File

@ -11,6 +11,7 @@ namespace OpenDiablo2.Common.Enums
Floor,
WallUpper,
WallLower,
Roof
Roof,
MAX
}
}

View File

@ -1,16 +1,19 @@
using OpenDiablo2.Common.Enums;
using System.Collections.Generic;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Models;
namespace OpenDiablo2.Common.Interfaces
{
public interface IGameState
{
MPQDS1 MapData { get; }
int Act { get; }
int Seed { get; }
string MapName { get; }
Palette CurrentPalette { get; }
void Initialize(string text, eHero value);
void Update(long ms);
IEnumerable<MapCellInfo> GetMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType);
void UpdateMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType, IEnumerable<MapCellInfo> mapCellInfo);
}
}

View File

@ -8,79 +8,184 @@ namespace OpenDiablo2.Common.Models
{
public sealed class LevelDetail
{
/// <summary>Internal level name</summary>
public string Name { get; set; }
/// <summary>Level ID (Used in columns like VIS0-1)</summary>
public int Id { get; set; }
/// <summary>Palette</summary>
public int Pal { get; set; }
/// <summary>Act</summary>
public int Act { get; set; }
/// <summary>What layer the level is on (surface levels are always 0)</summary>
public int Layer { get; set; }
/// <summary>Horizontal size of the level</summary>
public int SizeX { get; set; }
/// <summary>Vertical size of the level</summary>
public int SizeY { get; set; }
/// <summary>Horizontal Placement Offset</summary>
public int OffsetX { get; set; }
/// <summary>Vertical placement offset</summary>
public int OffsetY { get; set; }
/// <summary>Special setting for levels that aren't random or preset (like Outer Cloister and Arcane Sancturary)</summary>
public int Depend { get; set; }
/// <summary>If true, it rains (or snows)</summary>
public int Rain { get; set; }
/// <summary>Unused</summary>
public int Mud { get; set; }
/// <summary>Perspective mode forced to off if set to 1</summary>
public int NoPer { get; set; }
/// <summary>Level of sight drawing</summary>
public int LOSDraw { get; set; }
/// <summary>Unknown</summary>
public int FloorFilter { get; set; }
/// <summary>Unknown</summary>
public int BlankScreen { get; set; }
/// <summary>For levels bordered with mountains or walls</summary>
public int DrawEdges { get; set; }
/// <summary>Set to 1 if this is underground or inside</summary>
public int IsInside { get; set; }
/// <summary> Setting for Level Generation: 1=Random Size, amount of rooms defined by LVLMAZE.TXT, 2=pre set map (example: catacombs lvl4) and 3=Random Area with preset size (wildernesses)</summary>
public int DrlgType { get; set; }
/// <summary>The level id to reference in lvltypes.txt</summary>
public int LevelType { get; set; }
/// <summary>Setting Regarding Level Type for lvlsub.txt (6=wilderness, 9=desert etc, -1=no subtype)</summary>
public int SubType { get; set; }
/// <summary></summary>
public int SubTheme { get; set; }
/// <summary></summary>
public int SubWaypoint { get; set; }
/// <summary></summary>
public int SubShrine { get; set; }
public int Vis0 { get; set; }
public int Vis1 { get; set; }
public int Vis2 { get; set; }
public int Vis3 { get; set; }
public int Vis4 { get; set; }
public int Vis5 { get; set; }
public int Vis6 { get; set; }
public int Vis7 { get; set; }
public int Warp0 { get; set; }
public int Warp1 { get; set; }
public int Warp2 { get; set; }
public int Warp3 { get; set; }
public int Warp4 { get; set; }
public int Warp5 { get; set; }
public int Warp6 { get; set; }
public int Warp7 { get; set; }
/// <summary>Entry/Exit to level #1-#8</summary>
public int[] Vis0_7 { get; set; }
/// <summary>ID into lvlwarp.txt</summary>
public int[] Warp0_7 { get; set; }
/// <summary>Light intensity (0-255)</summary>
public int Intensity { get; set; }
/// <summary></summary>
public int Red { get; set; }
/// <summary></summary>
public int Green { get; set; }
/// <summary></summary>
public int Blue { get; set; }
/// <summary>Unknown</summary>
public int Portal { get; set; }
/// <summary>Settings for preset levels</summary>
public int Position { get; set; }
/// <summary>If true, monster/creatures get saved/loaded instead of despawning.</summary>
public int SaveMonsters { get; set; }
/// <summary>Quest flags</summary>
public int Quest { get; set; }
/// <summary>Usually 2025, unknown</summary>
public int WarpDist { get; set; }
/// <summary>Level on Normal (controls the item level of items that drop from chests etc)</summary>
public int MonLvl1 { get; set; }
/// <summary>Level on Nightmare (controls the item level of items that drop from chests etc)</summary>
public int MonLvl2 { get; set; }
/// <summary> Level on Hell (controls the item level of items that drop from chests etc)</summary>
public int MonLvl3 { get; set; }
/// <summary>The Density of Monsters</summary>
public int MonDen { get; set; }
/// <summary>Minimum Unique and Champion Monsters Spawned in this Level</summary>
public int MonUMin { get; set; }
/// <summary>Maximum Unique and Champion Monsters Spawned in this Level</summary>
public int MonUMax { get; set; }
/// <summary></summary>
public int MonWndr { get; set; }
/// <summary></summary>
public int MonSpcWalk { get; set; }
/// <summary>How many different Species of Monsters can occur in this area (example: if you use M1-25 then set Mtot to 25 etc)</summary>
public int Mtot { get; set; }
/// <summary>Monster Species 1-25 (use ID from MonStats.txt)</summary>
public int[] M1_25 { get; set; }
/// <summary>Spawned Species 1-25 (use ID from MonStats for Monsters which have eSpawnCol set to 2, related to M1-25, eg: if M1 Spawns S1 will Spawn)</summary>
public int[] S1_25 { get; set; }
/// <summary>How many different Species of Monsters can spawn as Uniques and Champions in this Area (works like Mtot)</summary>
public int Utot { get; set; }
/// <summary> Unique Species 1-25 (same as M1-M25 just for Monsters that you want to appear as Unique/Champions)</summary>
public int[] U1_25 { get; set; }
/// <summary>Critter Species 1-5 (For monsters set to 1 in the IsCritter Column in MonStats.txt)</summary>
public int[] C1_5 { get; set; }
/// <summary>Related to C1-5, eg: if you spawn a critter thru C1 then set this column to 30 etc. (function unknown)</summary>
public int[] CA1_5 { get; set; }
/// <summary>Unknown</summary>
public int[] CD1_5 { get; set; }
/// <summary>unknown</summary>
public int Themes { get; set; }
/// <summary>Referes to a entry in SoundEnviron.txt (for the Levels Music)</summary>
public int SoundEnv { get; set; }
/// <summary>255=No way Point, other #'s Waypoint ID</summary>
public int Waypoint { get; set; }
/// <summary>String Code for the Display name of the Level</summary>
public string LevelName { get; set; }
/// <summary>String Code for the Display name of a entrance to this Level</summary>
public string LevelWarp { get; set; }
/// <summary>Which *.DC6 Title Image is loaded when you enter this area</summary>
public string EntryFile { get; set; }
/// <summary>Use the ID of the ObjectGroup you want to Spawn in this Area (from ObjectGroups.txt)</summary>
public int[] ObjGrp0_7 { get; set; }
/// <summary>Object Spawn Possibility: the Chance for this object to occur (if you use ObjGrp0 then set ObjPrb0 to a value below 100)</summary>
public int[] ObjPrb0_7 { get; set; }
/// <summary>Unused</summary>
public bool Beta { get; set; }
}
@ -114,22 +219,10 @@ namespace OpenDiablo2.Common.Models
result.SubTheme = Convert.ToInt32(v[i++]);
result.SubWaypoint = Convert.ToInt32(v[i++]);
result.SubShrine = Convert.ToInt32(v[i++]);
result.Vis0 = Convert.ToInt32(v[i++]);
result.Vis1 = Convert.ToInt32(v[i++]);
result.Vis2 = Convert.ToInt32(v[i++]);
result.Vis3 = Convert.ToInt32(v[i++]);
result.Vis4 = Convert.ToInt32(v[i++]);
result.Vis5 = Convert.ToInt32(v[i++]);
result.Vis6 = Convert.ToInt32(v[i++]);
result.Vis7 = Convert.ToInt32(v[i++]);
result.Warp0 = Convert.ToInt32(v[i++]);
result.Warp1 = Convert.ToInt32(v[i++]);
result.Warp2 = Convert.ToInt32(v[i++]);
result.Warp3 = Convert.ToInt32(v[i++]);
result.Warp4 = Convert.ToInt32(v[i++]);
result.Warp5 = Convert.ToInt32(v[i++]);
result.Warp6 = Convert.ToInt32(v[i++]);
result.Warp7 = Convert.ToInt32(v[i++]);
result.Vis0_7 = new int[8];
for (int j = 0; j < 8; j++) result.Vis0_7[j] = Convert.ToInt32(v[i++]);
result.Warp0_7 = new int[8];
for (int j = 0; j < 8; j++) result.Warp0_7[j] = Convert.ToInt32(v[i++]);
result.Intensity = Convert.ToInt32(v[i++]);
result.Red = Convert.ToInt32(v[i++]);
result.Green = Convert.ToInt32(v[i++]);

View File

@ -73,7 +73,6 @@ namespace OpenDiablo2.Common.Models
{
static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public Guid Id { get; } = Guid.NewGuid();
public Int32 Version { get; internal set; }
public Int32 Width { get; internal set; }
public Int32 Height { get; internal set; }
@ -292,7 +291,7 @@ namespace OpenDiablo2.Common.Models
{
var tilePath = levelType.File[i];
var isMasked = ((dt1Mask >> i) & 1) == 1;
if (!isMasked)
if (!isMasked || levelType.File[i] == "0")
continue;
log.Debug($"Loading DT resource {levelType.File[i]}");

View File

@ -7,7 +7,7 @@ namespace OpenDiablo2.Common.Models
// Represents a single cell on a map
public sealed class MapCellInfo
{
public Guid TileId { get; set; }
public bool Ignore { get; set; } = false;
public int AnimationId { get; set; }
public int OffX { get; set; }
public int OffY { get; set; }

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Drawing;
using OpenDiablo2.Common.Enums;
namespace OpenDiablo2.Common.Models
{
public sealed class MapInfo
{
public eLevelId LevelId { get; set; } = eLevelId.None;
public MPQDS1 FileData { get; set; }
public LevelPreset LevelPreset { get; set; }
public LevelDetail LevelDetail { get; set; }
public LevelType LevelType { get; set; }
public Dictionary<eRenderCellType, MapCellInfo[]> CellInfo { get; set; }
public Rectangle TileLocation { get; set; } = new Rectangle();
}
}

View File

@ -106,6 +106,7 @@
<Compile Include="Models\LevelPreset.cs" />
<Compile Include="Models\LevelType.cs" />
<Compile Include="Models\MapCellInfo.cs" />
<Compile Include="Models\MapInfo.cs" />
<Compile Include="Models\MPQDS1.cs" />
<Compile Include="Models\MPQDT1.cs" />
<Compile Include="Models\MPQFont.cs" />

View File

@ -1,14 +1,11 @@
using OpenDiablo2.Common;
using OpenDiablo2.Common.Interfaces;
using OpenDiablo2.Common.Models;
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using OpenDiablo2.Common;
using OpenDiablo2.Common.Interfaces;
using OpenDiablo2.Common.Models;
namespace OpenDiablo2.Core
{
@ -22,6 +19,7 @@ namespace OpenDiablo2.Core
private readonly Func<IMouseInfoProvider> getMouseInfoProvider;
private readonly Func<string, IScene> getScene;
private readonly Func<IResourceManager> getResourceManager;
private readonly Func<IGameState> getGameState;
private IScene currentScene;
private IScene nextScene = null;
@ -40,7 +38,8 @@ namespace OpenDiablo2.Core
Func<IRenderWindow> getRenderWindow,
Func<IMouseInfoProvider> getMouseInfoProvider,
Func<string, IScene> getScene,
Func<IResourceManager> getResourceManager
Func<IResourceManager> getResourceManager,
Func<IGameState> getGameState
)
{
this.globalConfig = globalConfig;
@ -49,6 +48,7 @@ namespace OpenDiablo2.Core
this.getMouseInfoProvider = getMouseInfoProvider;
this.getScene = getScene;
this.getResourceManager = getResourceManager;
this.getGameState = getGameState;
MPQs = mpqProvider.GetMPQs().ToArray();
}
@ -98,9 +98,6 @@ namespace OpenDiablo2.Core
sw.Start();
while (getRenderWindow().IsRunning)
{
while (sw.ElapsedMilliseconds < 33)
Thread.Sleep(1);
var ms = sw.ElapsedMilliseconds;
sw.Restart();
@ -110,6 +107,8 @@ namespace OpenDiablo2.Core
sw.Restart();
continue;
}
getGameState().Update(ms);
getRenderWindow().Update();
currentScene.Update(ms);
if (nextScene!= null)

View File

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
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;
@ -12,13 +10,25 @@ namespace OpenDiablo2.Core.GameState_
{
public sealed class GameState : IGameState
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private readonly ISceneManager sceneManager;
private readonly IResourceManager resourceManager;
private readonly IPaletteProvider paletteProvider;
private readonly IEngineDataManager engineDataManager;
private readonly IRenderWindow renderWindow;
private readonly Func<IMapEngine> getMapEngine;
public MPQDS1 MapData { get; private set; }
private float animationTime = 0f;
private List<MapInfo> mapInfo;
private List<MapCellInfo> mapDataLookup = new List<MapCellInfo>();
// TODO: Break this out further so we can support multiple maps---------------------------------------------
//private MPQDS1 _mapDataTemp, _mapDataTemp2;
//private Dictionary<Guid, List<MapCellInfo>> mapDataLookup = new Dictionary<Guid, List<MapCellInfo>>();
//private Dictionary<Guid, List<MapCellInfo>> mapDataLookup2 = new Dictionary<Guid, List<MapCellInfo>>();
// ---------------------------------------------------------------------------------------------------------
public int Act { get; private set; }
public string MapName { get; private set; }
public Palette CurrentPalette => paletteProvider.PaletteTable[$"ACT{Act}"];
@ -30,6 +40,7 @@ namespace OpenDiablo2.Core.GameState_
IResourceManager resourceManager,
IPaletteProvider paletteProvider,
IEngineDataManager engineDataManager,
IRenderWindow renderWindow,
Func<IMapEngine> getMapEngine
)
{
@ -38,8 +49,8 @@ namespace OpenDiablo2.Core.GameState_
this.paletteProvider = paletteProvider;
this.getMapEngine = getMapEngine;
this.engineDataManager = engineDataManager;
this.renderWindow = renderWindow;
}
public void Initialize(string characterName, eHero hero)
@ -48,16 +59,27 @@ namespace OpenDiablo2.Core.GameState_
Seed = random.Next();
sceneManager.ChangeScene("Game");
ChangeMap(eLevelId.Act1_Town1);
// Initialize our first village
// TODO: Loading may make this 'fun'..
mapInfo = new List<MapInfo>();
LoadMap(eLevelId.Act1_Town1, new Point(0, 0));
}
public void ChangeMap(eLevelId levelId)
public MapInfo LoadMap(eLevelId levelId, Point origin)
{
// Don't generate the map if it doesn't already exist
var existing = mapInfo.FirstOrDefault(x => x.LevelId == levelId);
if (existing != null)
return existing;
var level = engineDataManager.LevelPresets.First(x => x.LevelId == (int)levelId);
var levelDetails = engineDataManager.LevelDetails.First(x => x.Id == level.LevelId);
var levelType = engineDataManager.LevelTypes.First(x => x.Id == levelDetails.LevelType);
// Some maps have variations, so lets pick a random one
var mapNames = new List<string>();
if (level.File1 != "0") mapNames.Add(level.File1);
if (level.File2 != "0") mapNames.Add(level.File2);
@ -66,13 +88,286 @@ namespace OpenDiablo2.Core.GameState_
if (level.File5 != "0") mapNames.Add(level.File5);
if (level.File6 != "0") mapNames.Add(level.File6);
var random = new Random(Seed);
var mapName = "data\\global\\tiles\\" + mapNames[random.Next(mapNames.Count())].Replace("/", "\\");
MapName = level.Name;
Act = levelType.Act;
MapData = resourceManager.GetMPQDS1(mapName, level, levelDetails, levelType);
var fileData = resourceManager.GetMPQDS1(mapName, level, levelDetails, levelType);
var result = new MapInfo
{
LevelId = levelId,
LevelPreset = level,
LevelDetail = levelDetails,
LevelType = levelType,
FileData = fileData,
CellInfo = new Dictionary<eRenderCellType, MapCellInfo[]>(),
TileLocation = new Rectangle(origin, new Size(fileData.Width, fileData.Height))
};
mapInfo.Add(result);
return result;
/*
{
// TODO: TEMP CODE AHEAD!
var transId = nw ? 3 : 2;
var level = engineDataManager.LevelPresets.First(x => x.Def == (int)transId);
var levelDetails = engineDataManager.LevelDetails.First(x => x.Id == level.LevelId);
var levelType = engineDataManager.LevelTypes.First(x => x.Id == levelDetails.LevelType);
// Some maps have variations, so lets pick a random one
var mapNames = new List<string>();
if (level.File1 != "0") mapNames.Add(level.File1);
if (level.File2 != "0") mapNames.Add(level.File2);
if (level.File3 != "0") mapNames.Add(level.File3);
if (level.File4 != "0") mapNames.Add(level.File4);
if (level.File5 != "0") mapNames.Add(level.File5);
if (level.File6 != "0") mapNames.Add(level.File6);
var random = new Random(Seed);
var mapName = "data\\global\\tiles\\" + mapNames[random.Next(mapNames.Count())].Replace("/", "\\");
_mapDataTemp2 = resourceManager.GetMPQDS1(mapName, level, levelDetails, levelType);
}
*/
getMapEngine().NotifyMapChanged();
}
public IEnumerable<MapCellInfo> GetMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType)
{
var map = GetMap(ref cellX, ref cellY);
if (map == null)
return new List<MapCellInfo>();
if (cellY >= map.FileData.Height || cellX >= map.FileData.Width)
return new List<MapCellInfo>(); // Temporary code
var idx = cellX + (cellY * map.FileData.Width);
switch (renderCellType)
{
case eRenderCellType.Floor:
return map.FileData.FloorLayers
.Select(floorLayer => GetMapCellInfo(map, cellX, cellY, floorLayer.Props[idx], eRenderCellType.Floor))
.Where(x => x != null);
case eRenderCellType.WallUpper:
case eRenderCellType.WallLower:
case eRenderCellType.Roof:
return map.FileData.WallLayers
.Select(wallLayer => GetMapCellInfo(map, cellX, cellY, wallLayer.Props[idx], renderCellType, wallLayer.Orientations[idx]))
.Where(x => x != null);
default:
throw new ApplicationException("Unknown render cell type!");
}
}
private MapInfo GetMap(ref int cellX, ref int cellY)
{
var x = cellX;
var y = cellY;
var map = mapInfo.FirstOrDefault(z => z.TileLocation.Contains(x, y));
if (map == null)
{
// TODO: Generate map here
return null;
}
cellX -= map.TileLocation.X;
cellY -= map.TileLocation.Y;
return map;
}
public void UpdateMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType, IEnumerable<MapCellInfo> mapCellInfo)
{
}
private MapCellInfo GetMapCellInfo(
MapInfo map,
int cellX,
int cellY,
MPQDS1TileProps props,
eRenderCellType cellType,
MPQDS1WallOrientationTileProps wallOrientations = null
)
{
if (!map.CellInfo.ContainsKey(cellType))
{
map.CellInfo[cellType] = new MapCellInfo[map.FileData.Width * map.FileData.Height];
}
var cellInfo = map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)];
if (cellInfo != null)
return cellInfo.Ignore ? null : cellInfo;
var sub_index = props.Prop2;
var main_index = (props.Prop3 >> 4) + ((props.Prop4 & 0x03) << 4);
var orientation = 0;
if (cellType == eRenderCellType.Floor)
{
// Floors can't have rotations, should we blow up here?
if (props.Prop1 == 0)
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
}
if (cellType == eRenderCellType.Roof)
{
if (orientation != 15) // Only 15 (roof)
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
if (props.Prop1 == 0)
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
if ((props.Prop4 & 0x80) > 0)
{
if (orientation != 10 && orientation != 11)
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
}
}
if (cellType == eRenderCellType.WallUpper || cellType == eRenderCellType.WallLower)
{
orientation = wallOrientations.Orientation1;
if (props.Prop1 == 0)
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
// < 15 shouldn't happen for upper wall types, should we even check for this?
if (cellType == eRenderCellType.WallUpper && orientation <= 15)
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
// TODO: Support special walls
if (orientation == 10 || orientation == 11)
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
// This is also a thing apparently
if ((props.Prop4 & 0x80) > 0)
{
if (orientation != 10 && orientation != 11)
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
}
}
int frame = 0;
var tiles = map.FileData.LookupTable
.Where(x => x.MainIndex == main_index && x.SubIndex == sub_index && x.Orientation == orientation)
.Select(x => x.TileRef);
if (!tiles.Any())
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
//throw new ApplicationException("Invalid tile id found!");
MPQDT1Tile tile = null;
if (tiles.First().Animated)
{
#if DEBUG
if (!tiles.All(x => x.Animated))
throw new ApplicationException("Some tiles are animated and some aren't...");
#endif
var frameIndex = (int)Math.Floor(tiles.Count() * animationTime);
tile = tiles.ElementAt(frameIndex);
}
else
{
if (tiles.Count() > 0)
{
var totalRarity = tiles.Sum(q => q.RarityOrFrameIndex);
var random = new Random(Seed + cellX + (map.FileData.Width * cellY));
var x = random.Next(totalRarity);
var z = 0;
foreach (var t in tiles)
{
z += t.RarityOrFrameIndex;
if (x <= z)
{
tile = t;
break;
}
}
if (tile.Animated)
throw new ApplicationException("Why are we randomly finding an animated tile? Something's wrong here.");
}
else tile = tiles.First();
}
// This WILL happen to you
if (tile.Width == 0 || tile.Height == 0)
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
if (tile.BlockDataLength == 0)
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null; // Why is this a thing?
}
var mapCellInfo = mapDataLookup.FirstOrDefault(x => x.Tile.Id == tile.Id && x.AnimationId == frame);
if (mapCellInfo == null)
{
mapCellInfo = renderWindow.CacheMapCell(tile);
mapDataLookup.Add(mapCellInfo);
switch (cellType)
{
case eRenderCellType.WallUpper:
case eRenderCellType.WallLower:
mapCellInfo.OffY -= 80;
break;
}
}
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = mapCellInfo;
return mapCellInfo;
}
public void Update(long ms)
{
animationTime += ((float)ms / 1000f);
animationTime -= (float)Math.Truncate(animationTime);
}
}
}

View File

@ -1,13 +1,8 @@
using System;
using System.Collections.Generic;
using System.Drawing;
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.Map_Engine
{
@ -17,9 +12,6 @@ namespace OpenDiablo2.Core.Map_Engine
private readonly IRenderWindow renderWindow;
private readonly IResourceManager resourceManager;
// TODO: Break this out further so we can support multiple maps
private Dictionary<Guid, List<MapCellInfo>> mapDataLookup = new Dictionary<Guid, List<MapCellInfo>>();
private PointF cameraLocation = new PointF();
public PointF CameraLocation
{
@ -42,8 +34,8 @@ namespace OpenDiablo2.Core.Map_Engine
private const int
cellSizeX = 160,
cellSizeY = 80,
renderCellsX = (800 / cellSizeX) + 1,
renderCellsY = (600 / cellSizeY) + 1;
renderCellsX = (800 / cellSizeX),
renderCellsY = (600 / cellSizeY);
public MapEngine(
IGameState gameState,
@ -60,9 +52,8 @@ namespace OpenDiablo2.Core.Map_Engine
public void NotifyMapChanged()
{
PurgeAllMapData();
LoadNewMapData();
CameraLocation = new PointF(gameState.MapData.Width / 2, gameState.MapData.Height / 2);
CameraLocation = new PointF(0, 0);
}
private void LoadNewMapData()
@ -70,229 +61,45 @@ namespace OpenDiablo2.Core.Map_Engine
}
public void Render()
{
// Lower Walls, Floors, and Shadows
for (int y = 0; y < gameState.MapData.Height; y++)
var cx = -(cameraLocation.X - Math.Truncate(cameraLocation.X));
var cy = -(cameraLocation.Y - Math.Truncate(cameraLocation.Y));
for (int ty = -5; ty <= 9; ty++)
{
for (int x = 0; x < gameState.MapData.Width; x++)
for (int tx = -5; tx <= 9; tx++)
{
var visualX = ((x - y) * (cellSizeX / 2)) - cOffX;
var visualY = ((x + y) * (cellSizeY / 2)) - cOffY;
var ax = tx + Math.Truncate(cameraLocation.X);
var ay = ty + Math.Truncate(cameraLocation.Y);
var px = (tx - ty) * (cellSizeX / 2);
var py = (tx + ty) * (cellSizeY / 2);
var ox = (cx - cy) * (cellSizeX / 2);
var oy = (cx + cy) * (cellSizeY / 2);
DrawFloor(x, y, visualX, visualY);
DrawWall(x, y, visualX, visualY, false);
DrawWall(x, y, visualX, visualY, true);
DrawRoof(x, y, visualX, visualY);
// //DrawShadow(x, y, visualX, visualY);
foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.Floor))
renderWindow.DrawMapCell(cellInfo, 320 + (int)px + (int)ox, 210 + (int)py + (int)oy);
foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.WallLower))
renderWindow.DrawMapCell(cellInfo, 320 + (int)px + (int)ox, 210 + (int)py + (int)oy);
foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.WallUpper))
renderWindow.DrawMapCell(cellInfo, 320 + (int)px + (int)ox, 210 + (int)py + (int)oy);
foreach (var cellInfo in gameState.GetMapCellInfo((int)ax, (int)ay, eRenderCellType.Roof))
renderWindow.DrawMapCell(cellInfo, 320 + (int)px + (int)ox, 210 + (int)py + (int)oy);
}
}
}
private void DrawRoof(int x, int y, int visualX, int visualY)
{
var cx = ((x - y) * 80) - cOffX;
var cy = ((x + y) * 40) - cOffY;
foreach (var wallLayer in gameState.MapData.WallLayers)
{
var idx = x + (y * gameState.MapData.Width);
var cellInfo = GetMapCellInfo(
gameState.MapData.Id, cx, cy, wallLayer.Props[idx],
eRenderCellType.Roof,
wallLayer.Orientations[idx]);
if (cellInfo == null)
return;
renderWindow.DrawMapCell(cellInfo, cx, cy);
}
}
private void DrawShadow(int x, int y, int visualX, int visualY)
{
}
private void DrawFloor(int x, int y, int visualX, int visualY)
{
if (visualX < -160 || visualX > 800 || visualY < -120 || visualY > 650)
return;
var cx = ((x - y) * 80) - cOffX;
var cy = ((x + y) * 40) - cOffY;
// Render the floor
foreach (var floorLayer in gameState.MapData.FloorLayers)
{
var idx = x + (y * gameState.MapData.Width);
if (idx >= floorLayer.Props.Length)
break;
var cellInfo = GetMapCellInfo(gameState.MapData.Id, cx, cy, floorLayer.Props[idx], eRenderCellType.Floor);
if (cellInfo == null)
return;
renderWindow.DrawMapCell(cellInfo, cx, cy);
}
}
private void DrawWall(int x, int y, int visualX, int visualY, bool upper)
{
var cx = ((x - y) * 80) - cOffX;
var cy = ((x + y) * 40) - cOffY;
foreach (var wallLayer in gameState.MapData.WallLayers)
{
var idx = x + (y * gameState.MapData.Width);
var cellInfo = GetMapCellInfo(
gameState.MapData.Id, cx, cy, wallLayer.Props[idx],
upper ? eRenderCellType.WallUpper : eRenderCellType.WallLower,
wallLayer.Orientations[idx]);
if (cellInfo == null)
return;
renderWindow.DrawMapCell(cellInfo, cx, cy);
}
}
public void Update(long ms)
{
}
private void PurgeAllMapData()
{
}
private MapCellInfo GetMapCellInfo(
Guid mapId,
int cellX,
int cellY,
MPQDS1TileProps props,
eRenderCellType cellType,
MPQDS1WallOrientationTileProps wallOrientations = null
)
{
var sub_index = props.Prop2;
var main_index = (props.Prop3 >> 4) + ((props.Prop4 & 0x03) << 4);
var orientation = 0;
if (cellType == eRenderCellType.Floor)
{
// Floors can't have rotations, should we blow up here?
if (props.Prop1 == 0)
return null;
}
if (cellType == eRenderCellType.Roof)
{
if (orientation != 15) // Only 15 (roof)
return null;
if (props.Prop1 == 0)
return null;
if ((props.Prop4 & 0x80) > 0)
{
if (orientation != 10 && orientation != 11)
return null;
}
}
if (cellType == eRenderCellType.WallUpper || cellType == eRenderCellType.WallLower)
{
orientation = wallOrientations.Orientation1;
if (props.Prop1 == 0)
return null;
// < 15 shouldn't happen for upper wall types, should we even check for this?
if (cellType == eRenderCellType.WallUpper && orientation <= 15)
return null;
// TODO: Support special walls
if (orientation == 10 || orientation == 11)
return null;
// This is also a thing apparently
if ((props.Prop4 & 0x80) > 0)
{
if (orientation != 10 && orientation != 11)
return null;
}
}
int frame = 0;
var tiles = gameState.MapData.LookupTable
.Where(x => x.MainIndex == main_index && x.SubIndex == sub_index && x.Orientation == orientation)
.Select(x => x.TileRef);
if (!tiles.Any())
throw new ApplicationException("Invalid tile id found!");
MPQDT1Tile tile = null;
if (tiles.First().Animated)
{
#if DEBUG
if (!tiles.All(x => x.Animated))
throw new ApplicationException("Some tiles are animated and some aren't...");
// TODO: Animated tiles
#endif
}
else
{
if (tiles.Count() > 0)
{
var totalRarity = tiles.Sum(q => q.RarityOrFrameIndex);
var random = new Random(gameState.Seed + cellX + (gameState.MapData.Width * cellY));
var x = random.Next(totalRarity);
var z = 0;
foreach (var t in tiles)
{
z += t.RarityOrFrameIndex;
if (x <= z)
{
tile = t;
break;
}
}
if (tile.Animated)
throw new ApplicationException("Why are we randomly finding an animated tile? Something's wrong here.");
}
else tile = tiles.First();
}
// This WILL happen to you
if (tile.Width == 0 || tile.Height == 0)
return null;
if (!mapDataLookup.ContainsKey(mapId))
mapDataLookup[mapId] = new List<MapCellInfo>();
var result = mapDataLookup[mapId].FirstOrDefault(x => x.TileId == tile.Id && x.AnimationId == frame);
if (result != null)
return result;
var mapCellInfo = renderWindow.CacheMapCell(tile);
mapDataLookup[mapId].Add(mapCellInfo);
return mapCellInfo;
}

View File

@ -1,16 +1,10 @@
using OpenDiablo2.Common.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SDL2;
using System.IO;
using System;
using System.Drawing;
using OpenDiablo2.Common.Models;
using Autofac;
using System.Linq;
using System.Runtime.InteropServices;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Interfaces;
using OpenDiablo2.Common.Models;
using SDL2;
namespace OpenDiablo2.SDL2_
{
@ -33,7 +27,7 @@ namespace OpenDiablo2.SDL2_
private readonly IPaletteProvider paletteProvider;
private readonly IResourceManager resourceManager;
private readonly GlobalConfiguration globalConfig;
private readonly IGameState gameState;
private readonly Func<IGameState> getGameState;
private readonly Func<IMapEngine> getMapEngine;
public SDL2RenderWindow(
@ -41,7 +35,7 @@ namespace OpenDiablo2.SDL2_
IMPQProvider mpqProvider,
IPaletteProvider paletteProvider,
IResourceManager resourceManager,
IGameState gameState,
Func<IGameState> getGameState,
Func<IMapEngine> getMapEngine
)
{
@ -49,14 +43,14 @@ namespace OpenDiablo2.SDL2_
this.mpqProvider = mpqProvider;
this.paletteProvider = paletteProvider;
this.resourceManager = resourceManager;
this.gameState = gameState;
this.getGameState = getGameState;
this.getMapEngine = getMapEngine;
SDL.SDL_Init(SDL.SDL_INIT_EVERYTHING);
if (SDL.SDL_SetHint(SDL.SDL_HINT_RENDER_SCALE_QUALITY, "0") == SDL.SDL_bool.SDL_FALSE)
throw new ApplicationException($"Unable to Init hinting: {SDL.SDL_GetError()}");
window = SDL.SDL_CreateWindow("OpenDiablo2", SDL.SDL_WINDOWPOS_UNDEFINED, SDL.SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN | SDL.SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI);
window = SDL.SDL_CreateWindow("OpenDiablo2", SDL.SDL_WINDOWPOS_UNDEFINED, SDL.SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN);
if (window == IntPtr.Zero)
throw new ApplicationException($"Unable to create SDL Window: {SDL.SDL_GetError()}");
@ -66,7 +60,7 @@ namespace OpenDiablo2.SDL2_
SDL.SDL_SetRenderDrawBlendMode(renderer, SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND);
SDL.SDL_GL_SetSwapInterval(1);
SDL.SDL_ShowCursor(globalConfig.MouseMode == eMouseMode.Hardware ? 1 : 0);
IsRunning = true;
@ -283,6 +277,8 @@ namespace OpenDiablo2.SDL2_
public unsafe MapCellInfo CacheMapCell(MPQDT1Tile mapCell)
{
log.Debug($"Caching map cell {mapCell.Id}");
var minX = mapCell.Blocks.Min(x => x.PositionX);
var minY = mapCell.Blocks.Min(x => x.PositionY);
var maxX = mapCell.Blocks.Max(x => x.PositionX + 32);
@ -291,7 +287,7 @@ namespace OpenDiablo2.SDL2_
var diffY = maxY - minY;
var offX = -minX;
var offy = -minY;
var offY = -minY;
var frameSize = new Size(diffX, Math.Abs(diffY));
@ -317,7 +313,7 @@ namespace OpenDiablo2.SDL2_
foreach (var block in mapCell.Blocks)
{
var index = block.PositionX + offX + ((block.PositionY + offy) * pitchChange);
var index = block.PositionX + offX + ((block.PositionY + offY) * pitchChange);
var xx = 0;
var yy = 0;
foreach (var colorIndex in block.PixelData)
@ -326,7 +322,7 @@ namespace OpenDiablo2.SDL2_
{
if (colorIndex == 0)
continue;
var color = gameState.CurrentPalette.Colors[colorIndex];
var color = getGameState().CurrentPalette.Colors[colorIndex];
if (color > 0)
data[index] = color;
@ -354,12 +350,12 @@ namespace OpenDiablo2.SDL2_
return new MapCellInfo
{
Tile = mapCell,
FrameHeight = frameSize.Height,
FrameWidth = frameSize.Width,
OffX = offX,
OffY = offy,
OffY = offY,
Rect = srcRect.ToRectangle(),
TileId = mapCell.Id,
Texture = new SDL2Texture { Pointer = texId }
};
}
@ -367,7 +363,7 @@ namespace OpenDiablo2.SDL2_
public void DrawMapCell(MapCellInfo mapCellInfo, int xPixel, int yPixel)
{
var srcRect = new SDL.SDL_Rect { x = 0, y = 0, w = mapCellInfo.FrameWidth, h = Math.Abs(mapCellInfo.FrameHeight) };
var destRect = new SDL.SDL_Rect { x = xPixel - mapCellInfo.OffX, y = yPixel - mapCellInfo.OffY, w = mapCellInfo.FrameWidth, h = mapCellInfo.FrameHeight};
var destRect = new SDL.SDL_Rect { x = xPixel - mapCellInfo.OffX, y = yPixel - mapCellInfo.OffY, w = mapCellInfo.FrameWidth, h = mapCellInfo.FrameHeight };
SDL.SDL_RenderCopy(renderer, (mapCellInfo.Texture as SDL2Texture).Pointer, ref srcRect, ref destRect);
}
@ -381,8 +377,8 @@ namespace OpenDiablo2.SDL2_
var surface = SDL.SDL_CreateRGBSurface(0, spr.FrameSize.Width * multiple, spr.FrameSize.Height * multiple, 32, 0xFF0000, 0xFF00, 0xFF, 0xFF000000);
var pixels = (UInt32*)((SDL.SDL_Surface*)surface)->pixels;
for (var y = 0; y < (spr.FrameSize.Height * multiple) - 1; y ++)
for (var x = 0; x < (spr.FrameSize.Width * multiple) - 1; x ++)
for (var y = 0; y < (spr.FrameSize.Height * multiple) - 1; y++)
for (var x = 0; x < (spr.FrameSize.Width * multiple) - 1; x++)
{
pixels[x + (y * spr.FrameSize.Width * multiple)] = spr.source.Frames[frame].GetColor(x / multiple, y / multiple, sprite.CurrentPalette);
}

View File

@ -119,9 +119,11 @@ namespace OpenDiablo2.SDL2_
{
UInt32* data = (UInt32*)pixels;
var frameOffset = FrameSize.Height - frame.Height;
for (var y = 0; y < FrameSize.Height; y++)
var frameWidth = FrameSize.Width;
var frameHeight = FrameSize.Height;
for (var y = 0; y < frameHeight; y++)
{
for (int x = 0; x < FrameSize.Width; x++)
for (int x = 0; x < frameWidth; x++)
{
if ((x >= frame.Width) || (y < frameOffset))
{

View File

@ -128,29 +128,29 @@ namespace OpenDiablo2.Scenes
if (keyboardInfoProvider.KeyIsPressed(80 /*left*/))
{
xMod = -8f * seconds;
yMod = 8f * seconds;
}
if (keyboardInfoProvider.KeyIsPressed(79 /*right*/))
{
xMod = 8f * seconds;
yMod = -8f * seconds;
}
if (keyboardInfoProvider.KeyIsPressed(81 /*down*/))
{
yMod = 10f * seconds;
xMod = 10f * seconds;
}
if (keyboardInfoProvider.KeyIsPressed(82 /*up*/))
{
yMod = -10f * seconds;
xMod = -10f * seconds;
}
if (xMod != 0f || yMod != 0f)
{
xMod *= .5f;
yMod *= .5f;
mapEngine.CameraLocation = new PointF(mapEngine.CameraLocation.X + xMod, mapEngine.CameraLocation.Y + yMod);
}
mapEngine.Update(ms);
}