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

Minor updates to map engine.

This commit is contained in:
Tim Sarbin 2018-12-10 21:43:06 -05:00
parent b1997963db
commit dbd3e0b74f
33 changed files with 782 additions and 338 deletions

View File

@ -0,0 +1,19 @@
using System;
namespace OpenDiablo2.Common.Attributes
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class RandomizedMapAttribute : Attribute
{
readonly string mapName;
public string MapName => mapName;
public RandomizedMapAttribute(string mapName)
{
this.mapName = mapName;
}
}
}

View File

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

View File

@ -16,7 +16,7 @@ namespace OpenDiablo2.Common.Interfaces
/// <returns>The <see cref="ImageSet"/> that was requested. Throw an exception if not found.</returns>
ImageSet GetImageSet(string resourcePath);
MPQFont GetMPQFont(string resourcePath);
MPQDS1 GetMPQDS1(string resourcePath, LevelPreset level, LevelDetail levelDetail, LevelType levelType);
MPQDS1 GetMPQDS1(string resourcePath, LevelPreset level, LevelType levelType);
MPQDT1 GetMPQDT1(string resourcePath);
Palette GetPalette(string paletteFile);
MPQCOF GetPlayerAnimation(eHero hero, eWeaponClass weaponClass, eMobMode mobMode);

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Interfaces.Mobs;
using OpenDiablo2.Common.Models;
@ -7,11 +8,11 @@ namespace OpenDiablo2.Common.Interfaces
{
public interface IEngineDataManager
{
List<LevelPreset> LevelPresets { get; }
List<LevelType> LevelTypes { get; }
List<LevelDetail> LevelDetails { get; }
List<Item> Items { get; }
Dictionary<eHero, ILevelExperienceConfig> ExperienceConfigs { get; }
Dictionary<eHero, IHeroTypeConfig> HeroTypeConfigs { get; }
ImmutableList<LevelDetail> Levels { get; }
ImmutableList<LevelPreset> LevelPresets { get; }
ImmutableList<LevelType> LevelTypes { get; }
ImmutableList<Item> Items { get; }
ImmutableDictionary<eHero, ILevelExperienceConfig> ExperienceConfigs { get; }
ImmutableDictionary<eHero, IHeroTypeConfig> HeroTypeConfigs { get; }
}
}

View File

@ -25,7 +25,13 @@ namespace OpenDiablo2.Common.Interfaces
void Update(long ms);
IEnumerable<MapCellInfo> GetMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType);
void UpdateMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType, IEnumerable<MapCellInfo> mapCellInfo);
MapInfo LoadMap(eLevelId levelId, Point origin);
MapInfo LoadSubMap(int levelDefId, Point origin, MapInfo primaryMap, int subTile = -1);
IMapInfo InsertMap(eLevelId levelId, IMapInfo parentMap = null);
IMapInfo InsertMap(int levelId, Point origin, IMapInfo parentMap = null);
IMapInfo InsertSubMap(int levelPresetId, int levelTypeId, Point origin, IMapInfo primaryMap, int subTile = -1);
IMapInfo GetSubMapInfo(int levelPresetId, int levelTypeId, IMapInfo primaryMap, Point origin, int subTile = -1);
void AddMap(IMapInfo map);
int HasMap(int cellX, int cellY);
IEnumerable<Size> GetMapSizes(int cellX, int cellY);
void RemoveEverythingAt(int cellX, int cellY);
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Drawing;
using OpenDiablo2.Common.Models;
namespace OpenDiablo2.Common.Interfaces
{
public interface IRandomizedMapGenerator
{
void Generate(IMapInfo parentMap, Point location);
}
}

View File

@ -9,189 +9,193 @@ namespace OpenDiablo2.Common.Models
public sealed class LevelDetail
{
/// <summary>Internal level name</summary>
public string Name { get; set; }
public string Name { get; internal set; }
/// <summary>Level ID (Used in columns like VIS0-1)</summary>
public int Id { get; set; }
public int Id { get; internal set; }
/// <summary>Palette</summary>
public int Pal { get; set; }
public int Pal { get; internal set; }
/// <summary>Act</summary>
public int Act { get; set; }
public int Act { get; internal set; }
/// <summary>What layer the level is on (surface levels are always 0)</summary>
public int Layer { get; set; }
public int Layer { get; internal set; }
/// <summary>Horizontal size of the level</summary>
public int SizeX { get; set; }
public int SizeX { get; internal set; }
/// <summary>Vertical size of the level</summary>
public int SizeY { get; set; }
public int SizeY { get; internal set; }
/// <summary>Horizontal Placement Offset</summary>
public int OffsetX { get; set; }
public int OffsetX { get; internal set; }
/// <summary>Vertical placement offset</summary>
public int OffsetY { get; set; }
public int OffsetY { get; internal set; }
/// <summary>Special setting for levels that aren't random or preset (like Outer Cloister and Arcane Sancturary)</summary>
public int Depend { get; set; }
public int Depend { get; internal set; }
/// <summary>If true, it rains (or snows)</summary>
public int Rain { get; set; }
public int Rain { get; internal set; }
/// <summary>Unused</summary>
public int Mud { get; set; }
public int Mud { get; internal set; }
/// <summary>Perspective mode forced to off if set to 1</summary>
public int NoPer { get; set; }
public int NoPer { get; internal set; }
/// <summary>Level of sight drawing</summary>
public int LOSDraw { get; set; }
public int LOSDraw { get; internal set; }
/// <summary>Unknown</summary>
public int FloorFilter { get; set; }
public int FloorFilter { get; internal set; }
/// <summary>Unknown</summary>
public int BlankScreen { get; set; }
public int BlankScreen { get; internal set; }
/// <summary>For levels bordered with mountains or walls</summary>
public int DrawEdges { get; set; }
public int DrawEdges { get; internal set; }
/// <summary>Set to 1 if this is underground or inside</summary>
public int IsInside { get; set; }
public int IsInside { get; internal 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; }
public int DrlgType { get; internal set; }
/// <summary>The level id to reference in lvltypes.txt</summary>
public int LevelType { get; set; }
public int LevelTypeId { get; internal set; }
/// <summary>Setting Regarding Level Type for lvlsub.txt (6=wilderness, 9=desert etc, -1=no subtype)</summary>
public int SubType { get; set; }
public int SubType { get; internal set; }
/// <summary></summary>
public int SubTheme { get; set; }
public int SubTheme { get; internal set; }
/// <summary></summary>
public int SubWaypoint { get; set; }
public int SubWaypoint { get; internal set; }
/// <summary></summary>
public int SubShrine { get; set; }
public int SubShrine { get; internal set; }
/// <summary>Entry/Exit to level #1-#8</summary>
public int[] Vis0_7 { get; set; }
public int[] Vis0_7 { get; internal set; }
/// <summary>ID into lvlwarp.txt</summary>
public int[] Warp0_7 { get; set; }
public int[] Warp0_7 { get; internal set; }
/// <summary>Light intensity (0-255)</summary>
public int Intensity { get; set; }
public int Intensity { get; internal set; }
/// <summary></summary>
public int Red { get; set; }
public int Red { get; internal set; }
/// <summary></summary>
public int Green { get; set; }
public int Green { get; internal set; }
/// <summary></summary>
public int Blue { get; set; }
public int Blue { get; internal set; }
/// <summary>Unknown</summary>
public int Portal { get; set; }
public int Portal { get; internal set; }
/// <summary>Settings for preset levels</summary>
public int Position { get; set; }
public int Position { get; internal set; }
/// <summary>If true, monster/creatures get saved/loaded instead of despawning.</summary>
public int SaveMonsters { get; set; }
public int SaveMonsters { get; internal set; }
/// <summary>Quest flags</summary>
public int Quest { get; set; }
public int Quest { get; internal set; }
/// <summary>Usually 2025, unknown</summary>
public int WarpDist { get; set; }
public int WarpDist { get; internal set; }
/// <summary>Level on Normal (controls the item level of items that drop from chests etc)</summary>
public int MonLvl1 { get; set; }
public int MonLvl1 { get; internal set; }
/// <summary>Level on Nightmare (controls the item level of items that drop from chests etc)</summary>
public int MonLvl2 { get; set; }
public int MonLvl2 { get; internal set; }
/// <summary> Level on Hell (controls the item level of items that drop from chests etc)</summary>
public int MonLvl3 { get; set; }
public int MonLvl3 { get; internal set; }
/// <summary>The Density of Monsters</summary>
public int MonDen { get; set; }
public int MonDen { get; internal set; }
/// <summary>Minimum Unique and Champion Monsters Spawned in this Level</summary>
public int MonUMin { get; set; }
public int MonUMin { get; internal set; }
/// <summary>Maximum Unique and Champion Monsters Spawned in this Level</summary>
public int MonUMax { get; set; }
public int MonUMax { get; internal set; }
/// <summary></summary>
public int MonWndr { get; set; }
public int MonWndr { get; internal set; }
/// <summary></summary>
public int MonSpcWalk { get; set; }
public int MonSpcWalk { get; internal 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; }
public int Mtot { get; internal set; }
/// <summary>Monster Species 1-25 (use ID from MonStats.txt)</summary>
public int[] M1_25 { get; set; }
public int[] M1_25 { get; internal 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; }
public int[] S1_25 { get; internal 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; }
public int Utot { get; internal 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; }
public int[] U1_25 { get; internal 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; }
public int[] C1_5 { get; internal 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; }
public int[] CA1_5 { get; internal set; }
/// <summary>Unknown</summary>
public int[] CD1_5 { get; set; }
public int[] CD1_5 { get; internal set; }
/// <summary>unknown</summary>
public int Themes { get; set; }
public int Themes { get; internal set; }
/// <summary>Referes to a entry in SoundEnviron.txt (for the Levels Music)</summary>
public int SoundEnv { get; set; }
public int SoundEnv { get; internal set; }
/// <summary>255=No way Point, other #'s Waypoint ID</summary>
public int Waypoint { get; set; }
public int Waypoint { get; internal set; }
/// <summary>String Code for the Display name of the Level</summary>
public string LevelName { get; set; }
public string LevelName { get; internal set; }
/// <summary>String Code for the Display name of a entrance to this Level</summary>
public string LevelWarp { get; set; }
public string LevelWarp { get; internal set; }
/// <summary>Which *.DC6 Title Image is loaded when you enter this area</summary>
public string EntryFile { get; set; }
public string EntryFile { get; internal 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; }
public int[] ObjGrp0_7 { get; internal 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; }
public int[] ObjPrb0_7 { get; internal set; }
/// <summary>Unused</summary>
public bool Beta { get; set; }
public bool Beta { get; internal set; }
public LevelPreset LevelPreset { get; internal set; }
public LevelType LevelType { get; internal set; }
}
public static class LevelDetailHelper
{
public static LevelDetail ToLevelDetail(this string[] v)
public static LevelDetail ToLevelDetail(this string[] v, IEnumerable<LevelPreset> levelPresets, IEnumerable<LevelType> levelTypes)
{
var result = new LevelDetail();
int i = 0;
@ -214,7 +218,7 @@ namespace OpenDiablo2.Common.Models
result.DrawEdges = Convert.ToInt32(v[i++]);
result.IsInside = Convert.ToInt32(v[i++]);
result.DrlgType = Convert.ToInt32(v[i++]);
result.LevelType = Convert.ToInt32(v[i++]);
result.LevelTypeId = Convert.ToInt32(v[i++]);
result.SubType = Convert.ToInt32(v[i++]);
result.SubTheme = Convert.ToInt32(v[i++]);
result.SubWaypoint = Convert.ToInt32(v[i++]);
@ -265,8 +269,8 @@ namespace OpenDiablo2.Common.Models
result.ObjPrb0_7 = new int[8];
for (int j = 0; j < 5; j++) result.ObjPrb0_7[j] = Convert.ToInt32(v[i++]);
result.Beta = Convert.ToInt32(v[i]) == 1;
result.LevelPreset = levelPresets.FirstOrDefault(x => x.LevelId == result.Id);
result.LevelType = levelTypes.FirstOrDefault(x => x.Id == result.LevelTypeId);
return result;
}
}

View File

@ -9,30 +9,30 @@ 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 string Name { get; internal set; }
public int Def { get; internal set; }
public int LevelId { get; internal set; }
public bool Populate { get; internal set; }
public int Logicals { get; internal set; }
public int Outdoors { get; internal set; }
public int Animate { get; internal set; }
public int KillEdge { get; internal set; }
public int FillBlanks { get; internal set; }
public int SizeX { get; internal set; }
public int SizeY { get; internal set; }
public int AutoMap { get; internal set; }
public int Scan { get; internal set; }
public int Pops { get; internal set; }
public int PopPad { get; internal set; }
public int Files { get; internal set; }
public string File1 { get; internal set; }
public string File2 { get; internal set; }
public string File3 { get; internal set; }
public string File4 { get; internal set; }
public string File5 { get; internal set; }
public string File6 { get; internal set; }
public UInt32 Dt1Mask { get; internal set; }
public bool Beta { get; internal set; }
}
public static class LevelPresetHelper
@ -63,7 +63,7 @@ namespace OpenDiablo2.Common.Models
File5 = row[20],
File6 = row[21],
Dt1Mask = Convert.ToUInt32(row[22]),
Beta = Convert.ToInt32(row[23]) == 1,
Beta = Convert.ToInt32(row[23]) == 1
};
}
}
}

View File

@ -3,7 +3,6 @@ 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
@ -96,7 +95,7 @@ namespace OpenDiablo2.Common.Models
public MPQDS1Object[] Objects { get; internal set; }
public MPQDS1Group[] Groups { get; internal set; }
public MPQDS1(Stream stream, LevelPreset level, LevelDetail levelDetail, LevelType levelType, IEngineDataManager engineDataManager, IResourceManager resourceManager)
public MPQDS1(Stream stream, LevelPreset level, LevelType levelType, IEngineDataManager engineDataManager, IResourceManager resourceManager)
{
var br = new BinaryReader(stream);
Version = br.ReadInt32();
@ -295,6 +294,7 @@ namespace OpenDiablo2.Common.Models
DT1s[i] = resourceManager.GetMPQDT1("data\\global\\tiles\\" + levelType.File[i].Replace("/", "\\"));
}
LookupTable = new List<DS1LookupTable>();
foreach(var dt1 in DT1s.Where(x => x != null))
{

View File

@ -4,15 +4,26 @@ using OpenDiablo2.Common.Enums;
namespace OpenDiablo2.Common.Models
{
public sealed class MapInfo
public interface IMapInfo
{
public eLevelId LevelId { get; set; } = eLevelId.None;
public MapInfo PrimaryMap { get; set; } = null;
Dictionary<eRenderCellType, MapCellInfo[]> CellInfo { get; set; }
MPQDS1 FileData { get; set; }
Rectangle TileLocation { get; set; }
}
public sealed class MapInfo : IMapInfo
{
public int LevelId { get; set; } = (int)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();
}
public sealed class SubMapInfo : IMapInfo
{
public IMapInfo PrimaryMap { get; set; }
public Dictionary<eRenderCellType, MapCellInfo[]> CellInfo { get; set; }
public MPQDS1 FileData { get; set; }
public Rectangle TileLocation { get; set; } = new Rectangle();
}
}

View File

@ -3,6 +3,7 @@ using OpenDiablo2.Common.Exceptions;
using OpenDiablo2.Common.Interfaces.Mobs;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -35,7 +36,7 @@ namespace OpenDiablo2.Common.Models.Mobs
public static class LevelExperienceConfigHelper
{
public static Dictionary<eHero, ILevelExperienceConfig> ToLevelExperienceConfigs(this string[][] data)
public static ImmutableDictionary<eHero, ILevelExperienceConfig> ToLevelExperienceConfigs(this string[][] data)
{
Dictionary<eHero, ILevelExperienceConfig> result = new Dictionary<eHero, ILevelExperienceConfig>();
for (int i = 1; i < data[0].Length; i++)
@ -65,7 +66,7 @@ namespace OpenDiablo2.Common.Models.Mobs
result.Add(herotype, new LevelExperienceConfig(expperlevel));
}
return result;
return result.ToImmutableDictionary();
}
}
}

View File

@ -58,6 +58,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Attributes\MessageFrameAttribute.cs" />
<Compile Include="Attributes\RandomizedMapAttribute.cs" />
<Compile Include="Attributes\SceneAttribute.cs" />
<Compile Include="AutofacModule.cs" />
<Compile Include="Enums\eItemContainerType.cs" />
@ -90,6 +91,7 @@
<Compile Include="Interfaces\IItemManager.cs" />
<Compile Include="Extensions\MobManagerExtensions.cs" />
<Compile Include="Interfaces\IGameServer.cs" />
<Compile Include="Interfaces\IRandomizedMapGenerator.cs" />
<Compile Include="Interfaces\MessageBus\ISessionEventProvider.cs" />
<Compile Include="Interfaces\MessageBus\IMessageFrame.cs" />
<Compile Include="Interfaces\MessageBus\ISessionManager.cs" />

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -18,78 +19,65 @@ namespace OpenDiablo2.Core
private readonly IMPQProvider mpqProvider;
public List<LevelPreset> LevelPresets { get; internal set; }
public List<LevelType> LevelTypes { get; internal set; }
public List<LevelDetail> LevelDetails { get; internal set; }
public List<Item> Items { get; internal set; } = new List<Item>();
public Dictionary<eHero, ILevelExperienceConfig> ExperienceConfigs { get; internal set; } = new Dictionary<eHero, ILevelExperienceConfig>();
public Dictionary<eHero, IHeroTypeConfig> HeroTypeConfigs { get; internal set; } = new Dictionary<eHero, IHeroTypeConfig>();
public ImmutableList<LevelDetail> Levels { get; internal set; }
public ImmutableList<LevelPreset> LevelPresets { get; internal set; }
public ImmutableList<LevelType> LevelTypes { get; internal set; }
public ImmutableList<Item> Items { get; internal set; }
public ImmutableDictionary<eHero, ILevelExperienceConfig> ExperienceConfigs { get; internal set; }
public ImmutableDictionary<eHero, IHeroTypeConfig> HeroTypeConfigs { get; internal set; }
public EngineDataManager(IMPQProvider mpqProvider)
{
this.mpqProvider = mpqProvider;
LoadLevelPresets();
LoadLevelTypes();
LoadLevelDetails();
LoadItemData();
LoadCharacterData();
Items = LoadItemData();
}
private void LoadLevelTypes()
private void LoadLevelDetails()
{
log.Info("Loading level types");
var data = mpqProvider
LevelTypes = mpqProvider
.GetTextFile(ResourcePaths.LevelType)
.Skip(1)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.Where(x => x.Count() == 36 && x[0] != "Expansion")
.Select(x => x.ToLevelType());
.Select(x => x.ToLevelType())
.ToImmutableList();
LevelTypes = new List<LevelType>(data);
}
private void LoadLevelPresets()
{
log.Info("Loading level presets");
var data = mpqProvider
LevelPresets = mpqProvider
.GetTextFile(ResourcePaths.LevelPreset)
.Skip(1)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.Where(x => x.Count() == 24 && x[0] != "Expansion")
.Select(x => x.ToLevelPreset());
LevelPresets = new List<LevelPreset>(data);
}
private void LoadLevelDetails()
{
.Select(x => x.ToLevelPreset())
.ToImmutableList();
log.Info("Loading level details");
var data = mpqProvider
Levels = mpqProvider
.GetTextFile(ResourcePaths.LevelDetails)
.Skip(1)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.Where(x => x.Count() > 80 && x[0] != "Expansion")
.Select(x => x.ToLevelDetail());
LevelDetails = new List<LevelDetail>(data);
.Select(x => x.ToLevelDetail(LevelPresets, LevelTypes))
.ToImmutableList();
}
private void LoadItemData()
{
var weaponData = LoadWeaponData();
var armorData = LoadArmorData();
var miscData = LoadMiscData();
Items.AddRange(weaponData);
Items.AddRange(armorData);
Items.AddRange(miscData);
}
private ImmutableList<Item> LoadItemData()
=> new List<Item>()
.Concat(LoadWeaponData())
.Concat(LoadArmorData())
.Concat(LoadMiscData())
.ToImmutableList();
private IEnumerable<Weapon> LoadWeaponData()
{
@ -101,7 +89,7 @@ namespace OpenDiablo2.Core
//.Where(x => !String.IsNullOrWhiteSpace(x[27]))
.Select(x => x.ToWeapon());
return data;
return data;
}
private IEnumerable<Armor> LoadArmorData()
@ -114,7 +102,7 @@ namespace OpenDiablo2.Core
//.Where(x => !String.IsNullOrWhiteSpace(x[27]))
.Select(x => x.ToArmor());
return data;
return data;
}
private IEnumerable<Misc> LoadMiscData()
@ -127,38 +115,30 @@ namespace OpenDiablo2.Core
//.Where(x => !String.IsNullOrWhiteSpace(x[27]))
.Select(x => x.ToMisc());
return data;
return data;
}
private void LoadCharacterData()
{
LoadExperienceConfig();
LoadHeroTypeConfig();
ExperienceConfigs = LoadExperienceConfig();
HeroTypeConfigs = LoadHeroTypeConfig();
}
private void LoadExperienceConfig()
{
var data = mpqProvider
private ImmutableDictionary<eHero, ILevelExperienceConfig> LoadExperienceConfig()
=> mpqProvider
.GetTextFile(ResourcePaths.Experience)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.ToArray()
.ToLevelExperienceConfigs();
ExperienceConfigs = data;
}
private void LoadHeroTypeConfig()
{
var data = mpqProvider
private ImmutableDictionary<eHero, IHeroTypeConfig> LoadHeroTypeConfig()
=> mpqProvider
.GetTextFile(ResourcePaths.CharStats)
.Skip(1)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.Where(x => x[0] != "Expansion")
.ToDictionary(x => (eHero)Enum.Parse(typeof(eHero),x[0]), x => x.ToHeroTypeConfig());
HeroTypeConfigs = data;
}
.ToImmutableDictionary(x => (eHero)Enum.Parse(typeof(eHero), x[0]), x => x.ToHeroTypeConfig());
}
}

View File

@ -24,9 +24,10 @@ namespace OpenDiablo2.Core.GameState_
private readonly IMPQProvider mpqProvider;
private readonly Func<IMapEngine> getMapEngine;
private readonly Func<eSessionType, ISessionManager> getSessionManager;
private readonly Func<string, IRandomizedMapGenerator> getRandomizedMapGenerator;
private float animationTime = 0f;
private List<MapInfo> mapInfo;
private List<IMapInfo> mapInfo;
private readonly List<MapCellInfo> mapDataLookup = new List<MapCellInfo>();
private ISessionManager sessionManager;
@ -57,7 +58,8 @@ namespace OpenDiablo2.Core.GameState_
ISoundProvider soundProvider,
IMPQProvider mpqProvider,
Func<IMapEngine> getMapEngine,
Func<eSessionType, ISessionManager> getSessionManager
Func<eSessionType, ISessionManager> getSessionManager,
Func<string, IRandomizedMapGenerator> getRandomizedMapGenerator
)
{
this.sceneManager = sceneManager;
@ -69,6 +71,7 @@ namespace OpenDiablo2.Core.GameState_
this.renderWindow = renderWindow;
this.soundProvider = soundProvider;
this.mpqProvider = mpqProvider;
this.getRandomizedMapGenerator = getRandomizedMapGenerator;
originalMouseCursor = renderWindow.MouseCursor;
@ -84,7 +87,7 @@ namespace OpenDiablo2.Core.GameState_
sessionManager.OnPlayerInfo += OnPlayerInfo;
sessionManager.OnFocusOnPlayer += OnFocusOnPlayer;
mapInfo = new List<MapInfo>();
mapInfo = new List<IMapInfo>();
sceneManager.ChangeScene(eSceneType.Game);
sessionManager.JoinGame(characterName, hero);
@ -115,76 +118,102 @@ namespace OpenDiablo2.Core.GameState_
new MapGenerator(this).Generate();
}
public int HasMap(int cellX, int cellY)
=> 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)
=> mapInfo
.Where(z => (cellX >= z.TileLocation.Left) && (cellX < z.TileLocation.Right) && (cellY >= z.TileLocation.Top) && (cellY < z.TileLocation.Bottom))
.Select(x => x.TileLocation.Size);
public MapInfo LoadSubMap(int levelDefId, Point origin, MapInfo primaryMap, int subTile = -1)
public void RemoveEverythingAt(int cellX, int cellY)
=> mapInfo.RemoveAll(z => (cellX >= z.TileLocation.Left) && (cellX < z.TileLocation.Right) && (cellY >= z.TileLocation.Top) && (cellY < z.TileLocation.Bottom));
public IMapInfo GetSubMapInfo(int levelPresetId, int levelTypeId, IMapInfo primaryMap, Point origin, int subTile = -1)
{
var level = engineDataManager.LevelPresets.First(x => x.Def == levelDefId);
var levelDetails = engineDataManager.LevelDetails.First(x => x.Id == level.LevelId);
var levelType = engineDataManager.LevelTypes.First(x => x.Id == levelDetails.LevelType);
var levelPreset = engineDataManager.LevelPresets.First(x => x.Def == levelPresetId);
var levelType = engineDataManager.LevelTypes.First(x => x.Id == levelTypeId);
// 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);
if (levelPreset.File1 != "0") mapNames.Add(levelPreset.File1);
if (levelPreset.File2 != "0") mapNames.Add(levelPreset.File2);
if (levelPreset.File3 != "0") mapNames.Add(levelPreset.File3);
if (levelPreset.File4 != "0") mapNames.Add(levelPreset.File4);
if (levelPreset.File5 != "0") mapNames.Add(levelPreset.File5);
if (levelPreset.File6 != "0") mapNames.Add(levelPreset.File6);
var random = new Random(Seed + origin.X + origin.Y);
var mapName = "data\\global\\tiles\\" + mapNames[subTile == -1 ? random.Next(mapNames.Count) : subTile].Replace("/", "\\");
var fileData = resourceManager.GetMPQDS1(mapName, level, levelDetails, levelType);
var fileData = resourceManager.GetMPQDS1(mapName, levelPreset, levelType);
var result = new MapInfo
var result = new SubMapInfo
{
LevelId = eLevelId.None,
LevelPreset = level,
LevelDetail = levelDetails,
LevelType = levelType,
FileData = fileData,
PrimaryMap = primaryMap,
CellInfo = new Dictionary<eRenderCellType, MapCellInfo[]>(),
TileLocation = new Rectangle(origin, new Size(fileData.Width - 1, fileData.Height - 1))
};
return result;
}
public void AddMap(IMapInfo map) => mapInfo.Add(map);
public IMapInfo InsertSubMap(int levelPresetId, int levelTypeId, Point origin, IMapInfo primaryMap, int subTile = -1)
{
var result = GetSubMapInfo(levelPresetId, levelTypeId, primaryMap, origin, subTile);
mapInfo.Add(result);
return result;
}
public MapInfo LoadMap(eLevelId levelId, Point origin)
public IMapInfo InsertMap(eLevelId levelId, IMapInfo parentMap = null) => InsertMap((int)levelId, new Point(0, 0), parentMap);
public IMapInfo InsertMap(int levelId, Point origin, IMapInfo parentMap = null)
{
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);
var levelDetails = engineDataManager.Levels.First(x => x.Id == levelId);
if (levelDetails.LevelPreset == null)
{
// There is no preset level, so we must generate one
var generator = getRandomizedMapGenerator(levelDetails.LevelName);
if (generator == null)
throw new OpenDiablo2Exception($"Could not locate a map generator for '{levelDetails.LevelName}'.");
generator.Generate(parentMap, origin);
// There is no core map so we cannot return a value here. If anyone actually uses
// this value on a generated map they are making a terrible mistake anyway...
return null;
}
// 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);
if (levelDetails.LevelPreset.File1 != "0") mapNames.Add(levelDetails.LevelPreset.File1);
if (levelDetails.LevelPreset.File2 != "0") mapNames.Add(levelDetails.LevelPreset.File2);
if (levelDetails.LevelPreset.File3 != "0") mapNames.Add(levelDetails.LevelPreset.File3);
if (levelDetails.LevelPreset.File4 != "0") mapNames.Add(levelDetails.LevelPreset.File4);
if (levelDetails.LevelPreset.File5 != "0") mapNames.Add(levelDetails.LevelPreset.File5);
if (levelDetails.LevelPreset.File6 != "0") mapNames.Add(levelDetails.LevelPreset.File6);
var random = new Random(Seed);
//var mapName = "data\\global\\tiles\\" + mapNames[random.Next(mapNames.Count)].Replace("/", "\\");
// TEMP FOR TESTING
// TODO: ***TEMP FOR TESTING
var mapName = "data\\global\\tiles\\" + mapNames[0].Replace("/", "\\");
MapName = level.Name;
Act = levelType.Act;
MapName = levelDetails.LevelPreset.Name;
Act = levelDetails.LevelType.Act;
var fileData = resourceManager.GetMPQDS1(mapName, levelDetails.LevelPreset, 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 - 1, fileData.Height - 1))
@ -192,9 +221,13 @@ namespace OpenDiablo2.Core.GameState_
mapInfo.Add(result);
soundProvider.StopSong();
soundProvider.LoadSong(mpqProvider.GetStream(ResourcePaths.GetMusicPathForLevel(levelId)));
soundProvider.PlaySong();
// Only change music if loading a 'core' map
if (Enum.IsDefined(typeof(eLevelId), levelId))
{
soundProvider.StopSong();
soundProvider.LoadSong(mpqProvider.GetStream(ResourcePaths.GetMusicPathForLevel((eLevelId)levelId)));
soundProvider.PlaySong();
}
return result;
@ -218,7 +251,10 @@ namespace OpenDiablo2.Core.GameState_
return map.FileData.FloorLayers
.Select(floorLayer => GetMapCellInfo(map, cellX, cellY, floorLayer.Props[idx], eRenderCellType.Floor, 0))
.Where(x => x != null);
case eRenderCellType.Shadow:
return map.FileData.ShadowLayers
.Select(shadowLayer => GetMapCellInfo(map, cellX, cellY, shadowLayer.Props[idx], eRenderCellType.Shadow, 0))
.Where(x => x != null);
case eRenderCellType.WallNormal:
case eRenderCellType.WallLower:
case eRenderCellType.Roof:
@ -231,11 +267,11 @@ namespace OpenDiablo2.Core.GameState_
}
}
private MapInfo GetMap(ref int cellX, ref int cellY)
private IMapInfo GetMap(ref int cellX, ref int cellY)
{
var x = cellX;
var y = cellY;
var map = mapInfo.FirstOrDefault(z => (x >= z.TileLocation.X) && (y >= z.TileLocation.Y)
var map = mapInfo.LastOrDefault(z => (x >= z.TileLocation.X) && (y >= z.TileLocation.Y)
&& (x < z.TileLocation.Right) && (y < z.TileLocation.Bottom));
if (map == null)
{
@ -268,7 +304,7 @@ namespace OpenDiablo2.Core.GameState_
this.SelectedItem = item;
}
private MapCellInfo GetMapCellInfo(MapInfo map, int cellX, int cellY, MPQDS1TileProps props, eRenderCellType cellType, byte orientation)
private MapCellInfo GetMapCellInfo(IMapInfo map, int cellX, int cellY, MPQDS1TileProps props, eRenderCellType cellType, byte orientation)
{
if (props.Prop1 == 0)
return null;
@ -288,8 +324,8 @@ namespace OpenDiablo2.Core.GameState_
if (orientation == 0)
{
// Floor
if (cellType != eRenderCellType.Floor)
// Floor or Shadow
if (cellType != eRenderCellType.Floor && cellType != eRenderCellType.Shadow)
{
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
@ -297,9 +333,12 @@ namespace OpenDiablo2.Core.GameState_
}
else if (orientation == 10 || orientation == 11)
{
// Special tile
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
if (cellType != eRenderCellType.WallNormal)
{
// Special tile
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}
}
else if (orientation == 14)
{
@ -339,12 +378,14 @@ namespace OpenDiablo2.Core.GameState_
}
int frame = 0;
var tiles = (map.PrimaryMap ?? map).FileData.LookupTable
IEnumerable<MPQDT1Tile> tiles = Enumerable.Empty<MPQDT1Tile>();
tiles = map.FileData.LookupTable
.Where(x => x.MainIndex == main_index && x.SubIndex == sub_index && x.Orientation == orientation)
.Select(x => x.TileRef);
if (!tiles.Any())
if (tiles == null || !tiles.Any())
{
log.Error($"Could not find tile [{main_index}:{sub_index}:{orientation}]!");
map.CellInfo[cellType][cellX + (cellY * map.FileData.Width)] = new MapCellInfo { Ignore = true };
return null;
}

View File

@ -2,6 +2,7 @@
using System.Drawing;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Interfaces;
using OpenDiablo2.Common.Models;
namespace OpenDiablo2.Core.Map_Engine
{
@ -21,137 +22,36 @@ namespace OpenDiablo2.Core.Map_Engine
private void GenerateAct1Town()
{
var townMap = gameState.LoadMap(eLevelId.Act1_Town1, new Point(0, 0));
//var wildBorder = 5; // (4-15)
Rectangle bloodMooreRect;
// 32-37 is grassy field?
bool westExit = false;
bool eastExit = false;
bool southExit = false;
bool northExit = false;
var townMap = gameState.InsertMap((int)eLevelId.Act1_Town1, new Point(0, 0));
Point bloodMoorOrigin;
if (townMap.FileData.MapFile.Contains("S1"))
{
var defId = 3; // Act 1 - Town 1 Transition S
var borderMap = gameState.LoadSubMap(defId, new Point(0, townMap.FileData.Height - 2), townMap);
borderMap.PrimaryMap = townMap;
var borderMap = gameState.InsertSubMap(defId, 1, new Point(0, townMap.FileData.Height - 2), townMap);
var wilderness = gameState.LoadSubMap(defId, new Point(26, townMap.FileData.Height + borderMap.FileData.Height - 2), townMap);
wilderness.PrimaryMap = townMap;
gameState.InsertSubMap(defId, 1, new Point(26, townMap.FileData.Height + borderMap.FileData.Height - 2), townMap);
bloodMooreRect = new Rectangle(-40, townMap.FileData.Height + borderMap.FileData.Height + 4, 120, 80);
southExit = true;
bloodMoorOrigin = new Point(0, townMap.FileData.Height + borderMap.FileData.Height + 4);
}
else if (townMap.FileData.MapFile.Contains("E1"))
{
var defId = 2; // Act 1 - Town 1 Transition E
var borderMap = gameState.LoadSubMap(defId, new Point(townMap.FileData.Width - 2, 0), townMap);
borderMap.PrimaryMap = townMap;
var borderMap = gameState.InsertSubMap(defId, 1, new Point(townMap.FileData.Width - 2, 0), townMap);
bloodMooreRect = new Rectangle(townMap.FileData.Width + borderMap.FileData.Width - 4, -40, 80, 120);
eastExit = true;
bloodMoorOrigin = new Point(townMap.FileData.Width + borderMap.FileData.Width - 4, 0);
}
else if (townMap.FileData.MapFile.Contains("W1"))
{
// West
bloodMooreRect = new Rectangle(-120, 0, 120, townMap.FileData.Height);
westExit = true;
bloodMoorOrigin = new Point(-80, 0);
}
else // North
{
bloodMooreRect = new Rectangle(0, -120, townMap.FileData.Width - 8, 120);
northExit = true;
bloodMoorOrigin = new Point(-22, -80); // Align along the eastern edge
}
// Generate the Blood Moore?
for (var y = 0; y < bloodMooreRect.Height; y += 8)
{
for (var x = 0; x < bloodMooreRect.Width; x += 8)
{
var px = bloodMooreRect.Left + x;
var py = bloodMooreRect.Top + y;
if ((x == 0) && (y == 0)) // North West
{
gameState.LoadSubMap((int)eWildBorder.NorthWest, new Point(px, py), townMap, 0);
}
else if ((x == bloodMooreRect.Width - 9) && (y == 0)) // North East
{
gameState.LoadSubMap((int)eWildBorder.NorthEast, new Point(px, py), townMap, 0);
}
else if ((x == bloodMooreRect.Width - 9) && (y == bloodMooreRect.Height - 9)) // South East
{
if (northExit)
{
gameState.LoadSubMap((int)eWildBorder.RiverUpper, new Point(bloodMooreRect.Left + x, bloodMooreRect.Top + y), townMap, 0);
}
else gameState.LoadSubMap((int)eWildBorder.SouthEast, new Point(px, py), townMap, 0);
}
else if ((x == 0) && (y == bloodMooreRect.Height - 9)) // South West
{
if (northExit)
{
gameState.LoadSubMap((int)eWildBorder.West, new Point(px, py), townMap, 0);
}
else gameState.LoadSubMap((int)eWildBorder.SouthWest, new Point(px, py), townMap, 0);
}
else if ((x == 0) && ((y % 8) == 0)) // West
{
if (westExit)
{
gameState.LoadSubMap((int)eWildBorder.RiverUpper, new Point(px, py), townMap, 3);
}
else if (eastExit)
{
// TODO: Transition to town
}
else gameState.LoadSubMap((int)eWildBorder.West, new Point(px, py), townMap, 0);
}
else if ((x == bloodMooreRect.Width - 9) && ((y % 8) == 0)) // East
{
if (westExit)
{
// TODO: Transition to town
}
if (northExit || eastExit)
{
gameState.LoadSubMap((int)eWildBorder.RiverUpper, new Point(px, py), townMap, 3);
}
else gameState.LoadSubMap((int)eWildBorder.East, new Point(px, py), townMap, 0);
}
else if (((x % 8) == 0) && (y == 0)) // North
{
if (southExit)
{
}
else gameState.LoadSubMap((int)eWildBorder.North, new Point(px, py), townMap, 0);
}
else if (((x % 8) == 0) && (y == (bloodMooreRect.Height - 9))) // South
{
if (northExit)
{
//var tileIdx = 31; // 8x8 fill
//gameState.LoadSubMap(tileIdx, new Point(bloodMooreRect.Left + x, bloodMooreRect.Top + y), townMap);
}
else gameState.LoadSubMap((int)eWildBorder.South, new Point(px, py), townMap, 0);
}
else
{
if (((x % 8) == 0) && ((y % 8)) == 0)
{
var tileIdx = 31; // 8x8 fill
gameState.LoadSubMap(tileIdx, new Point(bloodMooreRect.Left + x, bloodMooreRect.Top + y), townMap, 0);
}
}
}
}
gameState.InsertMap(2 /*Wilderness 1*/, bloodMoorOrigin, townMap);
}
}

View File

@ -42,6 +42,9 @@
<HintPath>..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<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.Runtime.Caching" />

View File

@ -48,9 +48,9 @@ namespace OpenDiablo2.Core
public MPQFont GetMPQFont(string resourcePath)
=> cache.AddOrGetExisting($"Font::{resourcePath}", () => MPQFont.LoadFromStream(mpqProvider.GetStream($"{resourcePath}.DC6"), mpqProvider.GetStream($"{resourcePath}.tbl")));
public MPQDS1 GetMPQDS1(string resourcePath, LevelPreset level, LevelDetail levelDetail, LevelType levelType)
=> cache.AddOrGetExisting($"DS1::{resourcePath}::{level}::{levelDetail}::{levelType}", ()
=> new MPQDS1(mpqProvider.GetStream(resourcePath), level, levelDetail, levelType, engineDataManager, this) { MapFile = resourcePath });
public MPQDS1 GetMPQDS1(string resourcePath, LevelPreset level, LevelType levelType)
=> cache.AddOrGetExisting($"DS1::{resourcePath}::{level}::{levelType}", ()
=> new MPQDS1(mpqProvider.GetStream(resourcePath), level, levelType, engineDataManager, this) { MapFile = resourcePath });
public Palette GetPalette(string paletteFile)
=> cache.AddOrGetExisting($"Palette::{paletteFile}", () =>

View File

@ -3,5 +3,6 @@
<package id="Autofac" version="4.8.1" targetFramework="net461" />
<package id="log4net" version="2.0.8" targetFramework="net461" />
<package id="Newtonsoft.Json" version="12.0.1" targetFramework="net461" />
<package id="System.Collections.Immutable" version="1.5.0" targetFramework="net461" />
<package id="Xabe.FFmpeg" version="3.1.4" targetFramework="net461" />
</packages>

View File

@ -39,6 +39,9 @@
<HintPath>..\packages\log4net.2.0.8\lib\net45-full\log4net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />

View File

@ -2,4 +2,5 @@
<packages>
<package id="Autofac" version="4.8.1" targetFramework="net461" />
<package id="log4net" version="2.0.8" targetFramework="net461" />
<package id="System.Collections.Immutable" version="1.5.0" targetFramework="net461" />
</packages>

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autofac;
using OpenDiablo2.Common.Attributes;
using OpenDiablo2.Common.Interfaces;
namespace OpenDiablo2.MapGenerators
{
public sealed class AutofacModule : Module
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
protected override void Load(ContainerBuilder builder)
{
log.Info("Configuring OpenDiablo2.MapGenerators service implementations.");
var types = ThisAssembly.GetTypes().Where(x => typeof(IRandomizedMapGenerator).IsAssignableFrom(x) && x.IsClass);
foreach (var type in types)
{
var att = type.GetCustomAttributes(true).First(x => (x is RandomizedMapAttribute)) as RandomizedMapAttribute;
builder
.RegisterType(type)
.Keyed<IRandomizedMapGenerator>(att.MapName)
.InstancePerDependency();
}
}
}
}

View File

@ -0,0 +1,288 @@
using OpenDiablo2.Common.Attributes;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Interfaces;
using OpenDiablo2.Common.Models;
using System;
using System.Drawing;
using System.Linq;
namespace OpenDiablo2.MapGenerators
{
[RandomizedMap("Blood Moor")]
public sealed class BloodMoor : IRandomizedMapGenerator
{
private readonly IGameState gameState;
private readonly LevelDetail levelDetail;
public BloodMoor(IGameState gameState, IEngineDataManager dataManager)
{
this.gameState = gameState;
levelDetail = dataManager.Levels.First(x => x.LevelName == "Blood Moor");
}
public void Generate(IMapInfo parentMap, Point location)
{
// Generate the area inside of the borders
GenerateGeneralContents(location, parentMap);
}
private void GenerateGeneralContents(Point location, IMapInfo parentMap)
{
bool northExit = location.Y < 0;
bool southExit = location.Y > 0;
bool westExit = location.X < 0;
bool eastExit = location.X > 0;
if (northExit)
{
GenerateSouthernTownEntrance(location, parentMap);
GenerateEasternRiver(location, parentMap);
GenerateWesternFence(location, parentMap);
GenerateNorthernFence(location, parentMap);
}
FillCenterArea(location, parentMap);
GeneratePointsOfInterests(location, parentMap);
#region old code
//for (var y = 0; y < levelDetail.SizeY; y += 8)
//{
// for (var x = 0; x < levelDetail.SizeX; x += 8)
// {
// var px = location.X + x;
// var py = location.Y + y;
// if ((x == 0) && (y == 0)) // North West
// {
// gameState.InsertSubMap((int)eWildBorder.NorthWest, 2, new Point(px, py), parentMap, 0);
// }
// else if ((x == levelDetail.SizeX - 8) && (y == 0)) // North East
// {
// gameState.InsertSubMap((int)eWildBorder.NorthEast, 2, new Point(px, py), parentMap, 0);
// }
// else if ((x == levelDetail.SizeX - 8) && (y == levelDetail.SizeY - 8)) // South East
// {
// if (northExit)
// {
// gameState.InsertSubMap((int)eWildBorder.RiverUpper, 2, new Point(location.X + x, location.Y + y), parentMap, 0);
// }
// else gameState.InsertSubMap((int)eWildBorder.SouthEast, 2, new Point(px, py), parentMap, 0);
// }
// else if ((x == 0) && (y == levelDetail.SizeY - 8)) // South West
// {
// if (northExit)
// {
// gameState.InsertSubMap((int)eWildBorder.West, 2, new Point(px, py), parentMap, 0);
// }
// else gameState.InsertSubMap((int)eWildBorder.SouthWest, 2, new Point(px, py), parentMap, 0);
// }
// else if ((x == 0) && ((y % 8) == 0)) // West
// {
// if (westExit)
// {
// gameState.InsertSubMap((int)eWildBorder.RiverUpper, 2, new Point(px, py), parentMap, 3);
// }
// else if (eastExit)
// {
// // TODO: Transition to town
// }
// else gameState.InsertSubMap((int)eWildBorder.West, 2, new Point(px, py), parentMap, 0);
// }
// else if ((x == levelDetail.SizeX - 8) && ((y % 8) == 0)) // East
// {
// if (westExit)
// {
// // TODO: Transition to town
// }
// if (northExit || eastExit)
// {
// gameState.InsertSubMap((int)eWildBorder.RiverUpper, 2, new Point(px, py), parentMap, 3);
// }
// else gameState.InsertSubMap((int)eWildBorder.East, 2, new Point(px, py), parentMap, 0);
// }
// else if (((x % 8) == 0) && (y == 0)) // North
// {
// if (southExit)
// {
// }
// else gameState.InsertSubMap((int)eWildBorder.North, 2, new Point(px, py), parentMap, 0);
// }
// else if (((x % 8) == 0) && (y == (levelDetail.SizeY - 8))) // South
// {
// if (northExit)
// {
// //var tileIdx = 31; // 8x8 fill
// //gameState.InsertSubMap(tileIdx, new Point(bloodMooreRect.Left + x, bloodMooreRect.Top + y), townMap);
// }
// else gameState.InsertSubMap((int)eWildBorder.South, 2, new Point(px, py), parentMap, 0);
// }
// else
// {
// //if (((x % 8) == 0) && ((y % 8)) == 0)
// //{
// // var tileIdx = 31; // 8x8 fill
// // gameState.InsertSubMap(tileIdx, 2, new Point(px, py), parentMap, 0);
// //}
// }
// }
//}
#endregion
}
private void GenerateNorthernFence(Point location, IMapInfo parentMap)
{
for (var x = location.X + 8; x < location.X + 64; x += 8)
{
gameState.InsertSubMap((int)eWildBorder.North, 2, new Point(x, location.Y), parentMap, 0);
}
}
private void GenerateWesternFence(Point location, IMapInfo parentMap)
{
gameState.InsertSubMap((int)eWildBorder.NorthWest, 2, new Point(location.X, location.Y), parentMap, 0);
for (var y = location.Y + 8; y < location.Y + 72; y += 8)
{
gameState.InsertSubMap((int)eWildBorder.West, 2, new Point(location.X, y), parentMap, 0);
}
}
private void GenerateEasternRiver(Point location, IMapInfo parentMap)
{
for (var y = location.Y + 8; y < location.Y + 72; y += 8)
{
gameState.InsertSubMap(26 /* River Upper */, 2, new Point(location.X + 62, y), parentMap, 3);
gameState.InsertSubMap(27 /* River Lower */, 2, new Point(location.X + 70, y), parentMap, 3);
}
// River near town
gameState.InsertSubMap(26 /* River Upper */, 2, new Point(location.X + 62, location.Y + 72), parentMap, 2);
gameState.InsertSubMap(27 /* River Lower */, 2, new Point(location.X + 70, location.Y + 72), parentMap, 2);
// River near the back
gameState.InsertSubMap(26 /* River Upper */, 2, new Point(location.X + 62, location.Y), parentMap, 1);
gameState.InsertSubMap(27 /* River Lower */, 2, new Point(location.X + 70, location.Y), parentMap, 1);
}
private void GenerateSouthernTownEntrance(Point location, IMapInfo parentMap)
{
var random = new Random(gameState.Seed + location.X + location.Y);
for (var x = location.X; x < location.X + 64; x += 8)
{
if (x == location.X) // Bottom left of the map
gameState.InsertSubMap((int)eWildBorder.ClosedBoxBottomLeft, 2, new Point(x, location.Y + 72), parentMap);
else if (x == location.X + 48) // Town exit
gameState.InsertSubMap((int)eWildBorder.South, 2, new Point(x, location.Y + 72), parentMap, 3);
else // Everything else
gameState.InsertSubMap((int)eWildBorder.South, 2, new Point(x, location.Y + 72), parentMap, 0);
}
}
private void GeneratePointsOfInterests(Point location, IMapInfo parentMap)
{
var random = new Random(gameState.Seed + location.X + location.Y);
// Generate the cave
while (true)
{
var rx = random.Next(8, levelDetail.SizeX - 16);
var ry = random.Next(8, levelDetail.SizeY - 16);
rx -= (rx % 8);
ry -= (ry % 8);
var caveX = rx + location.X;
var caveY = ry + location.Y;
/*
// Don't generate a camp on something that's already generated
var loc = gameState.GetMapSizes(caveX, caveY).First();
if (loc.Width != 8 || loc.Height != 8)
continue;
gameState.RemoveEverythingAt(caveX, caveY);
*/
gameState.InsertSubMap(52/*cave entrance*/, 2, new Point(caveX, caveY), parentMap);
break;
}
// Generate camps
var campsToGenerate = 3;
while (campsToGenerate > 0)
{
var rx = random.Next(8, levelDetail.SizeX - 16);
var ry = random.Next(8, levelDetail.SizeY - 16);
rx -= (rx % 8);
ry -= (ry % 8);
var campX = rx + location.X;
var campY = ry + location.Y;
/*
// Don't generate a camp on something that's already generated
var loc = gameState.GetMapSizes(campX, campY).First();
if (loc.Width != 8 || loc.Height != 8)
continue;
gameState.RemoveEverythingAt(campX, campY);
*/
if (gameState.HasMap(campX, campY) > 1)
continue;
gameState.InsertSubMap(random.Next(42, 43 + 1), 2, new Point(campX, campY), parentMap);
campsToGenerate--;
}
}
static int[] tileFillIds = new int[] { 29, 30, 31, 38, 39, 40, 41 };
private void FillCenterArea(Point location, IMapInfo parentMap)
{
var rightEdge = levelDetail.SizeX - 8;
var bottomEdge = levelDetail.SizeY - 8;
for (var y = 8; y < bottomEdge; y += 8)
{
for (var x = 8; x < rightEdge; x += 8)
{
var px = location.X + x;
var py = location.Y + y;
// If this space is already filled, move on
if (gameState.HasMap(px, py) > 0)
continue;
// Generate filler
var random = new Random(gameState.Seed + location.X + location.Y + x + y);
while (true)
{
var tileIdx = tileFillIds[random.Next(0, tileFillIds.Count())];
var info = gameState.GetSubMapInfo(tileIdx, 2, parentMap, new Point(location.X + x, location.Y + y));
// Make sure this tile actually fits here
if (info.TileLocation.Right > (location.X + rightEdge) || info.TileLocation.Bottom > (location.Y + bottomEdge))
continue;
if (info.TileLocation.Width == 16 && gameState.HasMap(px + 8, py) > 0)
continue;
if (info.TileLocation.Height == 16 && gameState.HasMap(px, py + 8) > 0)
continue;
// We found a tile that fits, so add it
gameState.AddMap(info);
break;
}
}
}
}
}
}

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A3370223-A28B-45B8-AD79-C17EAD99AF36}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>OpenDiablo2.MapGenerators</RootNamespace>
<AssemblyName>OpenDiablo2.MapGenerators</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Autofac, Version=4.8.1.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\packages\Autofac.4.8.1\lib\net45\Autofac.dll</HintPath>
</Reference>
<Reference Include="log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>..\packages\log4net.2.0.8\lib\net45-full\log4net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<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" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AutofacModule.cs" />
<Compile Include="BloodMoor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenDiablo2.Common\OpenDiablo2.Common.csproj">
<Project>{b743160e-a0bb-45dc-9998-967a85e50562}</Project>
<Name>OpenDiablo2.Common</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("OpenDiablo2.MapGenerators")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("OpenDiablo2.MapGenerators")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("a3370223-a28b-45b8-ad79-c17ead99af36")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Autofac" version="4.8.1" targetFramework="net461" />
<package id="log4net" version="2.0.8" targetFramework="net461" />
<package id="System.Collections.Immutable" version="1.5.0" targetFramework="net461" />
</packages>

View File

@ -62,7 +62,7 @@
<Compile Include="SDL2Font.cs" />
<Compile Include="SDL2Label.cs" />
<Compile Include="SDL2MouseCursor.cs" />
<Compile Include="SDL2MusicProvider.cs" />
<Compile Include="SDL2SoundProvider.cs" />
<Compile Include="SDL2RenderWindow.cs" />
<Compile Include="SDL2Sprite.cs" />
<Compile Include="SDL2Texture.cs" />

View File

@ -41,6 +41,8 @@ namespace OpenDiablo2.SDL2_
return;
musicChannel = SDL_mixer.Mix_PlayChannel(-1, music, 1);
//SDL_mixer.Mix_Volume(musicChannel, 64); // TODO: Customizable volume
}
public void LoadSong(Stream data)
@ -79,7 +81,9 @@ namespace OpenDiablo2.SDL2_
return -1;
var sound = SDL_mixer.Mix_QuickLoad_WAV(data);
return SDL_mixer.Mix_PlayChannel(-1, sound, 0);
var channel = SDL_mixer.Mix_PlayChannel(-1, sound, 0);
//SDL_mixer.Mix_Volume(channel, 64); // TODO: Customizable volume
return channel;
}
public void StopSfx(int channel)

View File

@ -34,6 +34,9 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@ -48,6 +51,7 @@
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenDiablo2.Common\OpenDiablo2.Common.csproj">

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Models;
using OpenDiablo2.Core;
@ -152,7 +153,7 @@ namespace OpenDiablo2.TestConsole
}
string path = words[0];
string output = "public enum eLevelId\r\n{\r\nNone,\r\n";
output += ELevelIdHelper.GenerateEnum(EngineDataMan.LevelPresets);
output += ELevelIdHelper.GenerateEnum(EngineDataMan.Levels.Select(x => x.LevelPreset).Distinct().ToList());
output += "}";
File.WriteAllText(path, output);
Console.WriteLine("Wrote eLevelIds enum to " + path + ".");

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="System.Collections.Immutable" version="1.5.0" targetFramework="net461" />
</packages>

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2036
# Visual Studio Version 16
VisualStudioVersion = 16.0.28315.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenDiablo2", "OpenDiablo2\OpenDiablo2.csproj", "{2B0CF1AC-06DD-4322-AE8B-FF8A8C70A3CD}"
EndProject
@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenDiablo2.ServiceBus", "O
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenDiablo2.GameServer", "OpenDiablo2.GameServer\OpenDiablo2.GameServer.csproj", "{C29A84E8-E708-4BE2-9946-202899B68E19}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenDiablo2.MapGenerators", "OpenDiablo2.MapGenerators\OpenDiablo2.MapGenerators.csproj", "{A3370223-A28B-45B8-AD79-C17EAD99AF36}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -73,6 +75,10 @@ Global
{C29A84E8-E708-4BE2-9946-202899B68E19}.Debug|x64.Build.0 = Debug|x64
{C29A84E8-E708-4BE2-9946-202899B68E19}.Release|x64.ActiveCfg = Release|x64
{C29A84E8-E708-4BE2-9946-202899B68E19}.Release|x64.Build.0 = Release|x64
{A3370223-A28B-45B8-AD79-C17EAD99AF36}.Debug|x64.ActiveCfg = Debug|x64
{A3370223-A28B-45B8-AD79-C17EAD99AF36}.Debug|x64.Build.0 = Debug|x64
{A3370223-A28B-45B8-AD79-C17EAD99AF36}.Release|x64.ActiveCfg = Release|x64
{A3370223-A28B-45B8-AD79-C17EAD99AF36}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -106,6 +106,10 @@
<Project>{c29a84e8-e708-4be2-9946-202899b68e19}</Project>
<Name>OpenDiablo2.GameServer</Name>
</ProjectReference>
<ProjectReference Include="..\OpenDiablo2.MapGenerators\OpenDiablo2.MapGenerators.csproj">
<Project>{a3370223-a28b-45b8-ad79-c17ead99af36}</Project>
<Name>OpenDiablo2.MapGenerators</Name>
</ProjectReference>
<ProjectReference Include="..\OpenDiablo2.Scenes\OpenDiablo2.Scenes.csproj">
<Project>{05224fe7-293f-4184-b1d6-89f5171b60e0}</Project>
<Name>OpenDiablo2.Scenes</Name>

View File

@ -118,6 +118,12 @@ namespace OpenDiablo2
return (itemContainerType) => componentContext.Resolve<IItemContainer>(new NamedParameter("itemContainerLayout", ItemContainerLayout.Values[itemContainerType]));
});
containerBuilder.Register<Func<string, IRandomizedMapGenerator>>(c =>
{
var componentContext = c.Resolve<IComponentContext>();
return (levelName) => componentContext.ResolveKeyed<IRandomizedMapGenerator>(levelName);
});
/* Uncomment the below if we support multiple textbox types
containerBuilder.Register<Func<TextBox>>(c =>
{