diff --git a/OpenDiablo2.Common/Attributes/RandomizedMapAttribute.cs b/OpenDiablo2.Common/Attributes/RandomizedMapAttribute.cs new file mode 100644 index 00000000..80e1b4e5 --- /dev/null +++ b/OpenDiablo2.Common/Attributes/RandomizedMapAttribute.cs @@ -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; + } + + + + } +} diff --git a/OpenDiablo2.Common/Enums/eRenderCellType.cs b/OpenDiablo2.Common/Enums/eRenderCellType.cs index 9849af64..7989b0db 100644 --- a/OpenDiablo2.Common/Enums/eRenderCellType.cs +++ b/OpenDiablo2.Common/Enums/eRenderCellType.cs @@ -11,6 +11,7 @@ namespace OpenDiablo2.Common.Enums Floor, WallNormal, WallLower, + Shadow, Roof, MAX } diff --git a/OpenDiablo2.Common/Interfaces/Data/IResourceManager.cs b/OpenDiablo2.Common/Interfaces/Data/IResourceManager.cs index 56e68ab9..e5b5a5c6 100644 --- a/OpenDiablo2.Common/Interfaces/Data/IResourceManager.cs +++ b/OpenDiablo2.Common/Interfaces/Data/IResourceManager.cs @@ -16,7 +16,7 @@ namespace OpenDiablo2.Common.Interfaces /// The that was requested. Throw an exception if not found. 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); diff --git a/OpenDiablo2.Common/Interfaces/IEngineDataManager.cs b/OpenDiablo2.Common/Interfaces/IEngineDataManager.cs index 028f97a2..20cb52bd 100644 --- a/OpenDiablo2.Common/Interfaces/IEngineDataManager.cs +++ b/OpenDiablo2.Common/Interfaces/IEngineDataManager.cs @@ -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 LevelPresets { get; } - List LevelTypes { get; } - List LevelDetails { get; } - List Items { get; } - Dictionary ExperienceConfigs { get; } - Dictionary HeroTypeConfigs { get; } + ImmutableList Levels { get; } + ImmutableList LevelPresets { get; } + ImmutableList LevelTypes { get; } + ImmutableList Items { get; } + ImmutableDictionary ExperienceConfigs { get; } + ImmutableDictionary HeroTypeConfigs { get; } } } diff --git a/OpenDiablo2.Common/Interfaces/IGameState.cs b/OpenDiablo2.Common/Interfaces/IGameState.cs index d58143ea..84a87221 100644 --- a/OpenDiablo2.Common/Interfaces/IGameState.cs +++ b/OpenDiablo2.Common/Interfaces/IGameState.cs @@ -25,7 +25,13 @@ namespace OpenDiablo2.Common.Interfaces void Update(long ms); IEnumerable GetMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType); void UpdateMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType, IEnumerable 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 GetMapSizes(int cellX, int cellY); + void RemoveEverythingAt(int cellX, int cellY); } } diff --git a/OpenDiablo2.Common/Interfaces/IRandomizedMapGenerator.cs b/OpenDiablo2.Common/Interfaces/IRandomizedMapGenerator.cs new file mode 100644 index 00000000..6d6f85b6 --- /dev/null +++ b/OpenDiablo2.Common/Interfaces/IRandomizedMapGenerator.cs @@ -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); + } +} diff --git a/OpenDiablo2.Common/Models/LevelDetail.cs b/OpenDiablo2.Common/Models/LevelDetail.cs index 28d9dbbd..e47402ea 100644 --- a/OpenDiablo2.Common/Models/LevelDetail.cs +++ b/OpenDiablo2.Common/Models/LevelDetail.cs @@ -9,189 +9,193 @@ namespace OpenDiablo2.Common.Models public sealed class LevelDetail { /// Internal level name - public string Name { get; set; } + public string Name { get; internal set; } /// Level ID (Used in columns like VIS0-1) - public int Id { get; set; } + public int Id { get; internal set; } /// Palette - public int Pal { get; set; } + public int Pal { get; internal set; } /// Act - public int Act { get; set; } + public int Act { get; internal set; } /// What layer the level is on (surface levels are always 0) - public int Layer { get; set; } + public int Layer { get; internal set; } /// Horizontal size of the level - public int SizeX { get; set; } + public int SizeX { get; internal set; } /// Vertical size of the level - public int SizeY { get; set; } + public int SizeY { get; internal set; } /// Horizontal Placement Offset - public int OffsetX { get; set; } + public int OffsetX { get; internal set; } /// Vertical placement offset - public int OffsetY { get; set; } + public int OffsetY { get; internal set; } /// Special setting for levels that aren't random or preset (like Outer Cloister and Arcane Sancturary) - public int Depend { get; set; } + public int Depend { get; internal set; } /// If true, it rains (or snows) - public int Rain { get; set; } + public int Rain { get; internal set; } /// Unused - public int Mud { get; set; } + public int Mud { get; internal set; } /// Perspective mode forced to off if set to 1 - public int NoPer { get; set; } + public int NoPer { get; internal set; } /// Level of sight drawing - public int LOSDraw { get; set; } + public int LOSDraw { get; internal set; } /// Unknown - public int FloorFilter { get; set; } + public int FloorFilter { get; internal set; } /// Unknown - public int BlankScreen { get; set; } + public int BlankScreen { get; internal set; } /// For levels bordered with mountains or walls - public int DrawEdges { get; set; } + public int DrawEdges { get; internal set; } /// Set to 1 if this is underground or inside - public int IsInside { get; set; } + public int IsInside { get; internal set; } /// 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) - public int DrlgType { get; set; } + public int DrlgType { get; internal set; } /// The level id to reference in lvltypes.txt - public int LevelType { get; set; } + public int LevelTypeId { get; internal set; } /// Setting Regarding Level Type for lvlsub.txt (6=wilderness, 9=desert etc, -1=no subtype) - public int SubType { get; set; } + public int SubType { get; internal set; } /// - public int SubTheme { get; set; } + public int SubTheme { get; internal set; } /// - public int SubWaypoint { get; set; } + public int SubWaypoint { get; internal set; } /// - public int SubShrine { get; set; } + public int SubShrine { get; internal set; } /// Entry/Exit to level #1-#8 - public int[] Vis0_7 { get; set; } + public int[] Vis0_7 { get; internal set; } /// ID into lvlwarp.txt - public int[] Warp0_7 { get; set; } + public int[] Warp0_7 { get; internal set; } /// Light intensity (0-255) - public int Intensity { get; set; } + public int Intensity { get; internal set; } /// - public int Red { get; set; } + public int Red { get; internal set; } /// - public int Green { get; set; } + public int Green { get; internal set; } /// - public int Blue { get; set; } + public int Blue { get; internal set; } /// Unknown - public int Portal { get; set; } + public int Portal { get; internal set; } /// Settings for preset levels - public int Position { get; set; } + public int Position { get; internal set; } /// If true, monster/creatures get saved/loaded instead of despawning. - public int SaveMonsters { get; set; } + public int SaveMonsters { get; internal set; } /// Quest flags - public int Quest { get; set; } + public int Quest { get; internal set; } /// Usually 2025, unknown - public int WarpDist { get; set; } + public int WarpDist { get; internal set; } /// Level on Normal (controls the item level of items that drop from chests etc) - public int MonLvl1 { get; set; } + public int MonLvl1 { get; internal set; } /// Level on Nightmare (controls the item level of items that drop from chests etc) - public int MonLvl2 { get; set; } + public int MonLvl2 { get; internal set; } /// Level on Hell (controls the item level of items that drop from chests etc) - public int MonLvl3 { get; set; } + public int MonLvl3 { get; internal set; } /// The Density of Monsters - public int MonDen { get; set; } + public int MonDen { get; internal set; } /// Minimum Unique and Champion Monsters Spawned in this Level - public int MonUMin { get; set; } + public int MonUMin { get; internal set; } /// Maximum Unique and Champion Monsters Spawned in this Level - public int MonUMax { get; set; } + public int MonUMax { get; internal set; } /// - public int MonWndr { get; set; } + public int MonWndr { get; internal set; } /// - public int MonSpcWalk { get; set; } + public int MonSpcWalk { get; internal set; } /// How many different Species of Monsters can occur in this area (example: if you use M1-25 then set Mtot to 25 etc) - public int Mtot { get; set; } + public int Mtot { get; internal set; } /// Monster Species 1-25 (use ID from MonStats.txt) - public int[] M1_25 { get; set; } + public int[] M1_25 { get; internal set; } /// 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) - public int[] S1_25 { get; set; } + public int[] S1_25 { get; internal set; } /// How many different Species of Monsters can spawn as Uniques and Champions in this Area (works like Mtot) - public int Utot { get; set; } + public int Utot { get; internal set; } /// Unique Species 1-25 (same as M1-M25 just for Monsters that you want to appear as Unique/Champions) - public int[] U1_25 { get; set; } + public int[] U1_25 { get; internal set; } /// Critter Species 1-5 (For monsters set to 1 in the IsCritter Column in MonStats.txt) - public int[] C1_5 { get; set; } + public int[] C1_5 { get; internal set; } /// Related to C1-5, eg: if you spawn a critter thru C1 then set this column to 30 etc. (function unknown) - public int[] CA1_5 { get; set; } + public int[] CA1_5 { get; internal set; } /// Unknown - public int[] CD1_5 { get; set; } + public int[] CD1_5 { get; internal set; } /// unknown - public int Themes { get; set; } + public int Themes { get; internal set; } /// Referes to a entry in SoundEnviron.txt (for the Levels Music) - public int SoundEnv { get; set; } + public int SoundEnv { get; internal set; } /// 255=No way Point, other #'s Waypoint ID - public int Waypoint { get; set; } + public int Waypoint { get; internal set; } /// String Code for the Display name of the Level - public string LevelName { get; set; } + public string LevelName { get; internal set; } /// String Code for the Display name of a entrance to this Level - public string LevelWarp { get; set; } + public string LevelWarp { get; internal set; } /// Which *.DC6 Title Image is loaded when you enter this area - public string EntryFile { get; set; } + public string EntryFile { get; internal set; } /// Use the ID of the ObjectGroup you want to Spawn in this Area (from ObjectGroups.txt) - public int[] ObjGrp0_7 { get; set; } + public int[] ObjGrp0_7 { get; internal set; } /// Object Spawn Possibility: the Chance for this object to occur (if you use ObjGrp0 then set ObjPrb0 to a value below 100) - public int[] ObjPrb0_7 { get; set; } + public int[] ObjPrb0_7 { get; internal set; } /// Unused - 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 levelPresets, IEnumerable 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; } } diff --git a/OpenDiablo2.Common/Models/LevelPreset.cs b/OpenDiablo2.Common/Models/LevelPreset.cs index b79f1641..866dc5b8 100644 --- a/OpenDiablo2.Common/Models/LevelPreset.cs +++ b/OpenDiablo2.Common/Models/LevelPreset.cs @@ -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 }; - } + } } diff --git a/OpenDiablo2.Common/Models/MPQDS1.cs b/OpenDiablo2.Common/Models/MPQDS1.cs index 93535e2c..1c23f911 100644 --- a/OpenDiablo2.Common/Models/MPQDS1.cs +++ b/OpenDiablo2.Common/Models/MPQDS1.cs @@ -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(); foreach(var dt1 in DT1s.Where(x => x != null)) { diff --git a/OpenDiablo2.Common/Models/MapInfo.cs b/OpenDiablo2.Common/Models/MapInfo.cs index 80c680c8..42bb4eb1 100644 --- a/OpenDiablo2.Common/Models/MapInfo.cs +++ b/OpenDiablo2.Common/Models/MapInfo.cs @@ -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 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 CellInfo { get; set; } public Rectangle TileLocation { get; set; } = new Rectangle(); } + + public sealed class SubMapInfo : IMapInfo + { + public IMapInfo PrimaryMap { get; set; } + public Dictionary CellInfo { get; set; } + public MPQDS1 FileData { get; set; } + public Rectangle TileLocation { get; set; } = new Rectangle(); + } } diff --git a/OpenDiablo2.Common/Models/Mobs/LevelExperienceConfig.cs b/OpenDiablo2.Common/Models/Mobs/LevelExperienceConfig.cs index 3eb6dd2d..c7f55b9c 100644 --- a/OpenDiablo2.Common/Models/Mobs/LevelExperienceConfig.cs +++ b/OpenDiablo2.Common/Models/Mobs/LevelExperienceConfig.cs @@ -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 ToLevelExperienceConfigs(this string[][] data) + public static ImmutableDictionary ToLevelExperienceConfigs(this string[][] data) { Dictionary result = new Dictionary(); 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(); } } } diff --git a/OpenDiablo2.Common/OpenDiablo2.Common.csproj b/OpenDiablo2.Common/OpenDiablo2.Common.csproj index 205cb339..c5a5cd50 100644 --- a/OpenDiablo2.Common/OpenDiablo2.Common.csproj +++ b/OpenDiablo2.Common/OpenDiablo2.Common.csproj @@ -58,6 +58,7 @@ + @@ -90,6 +91,7 @@ + diff --git a/OpenDiablo2.Core/EngineDataManager.cs b/OpenDiablo2.Core/EngineDataManager.cs index bb08e468..bd503415 100644 --- a/OpenDiablo2.Core/EngineDataManager.cs +++ b/OpenDiablo2.Core/EngineDataManager.cs @@ -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 LevelPresets { get; internal set; } - public List LevelTypes { get; internal set; } - public List LevelDetails { get; internal set; } - public List Items { get; internal set; } = new List(); - public Dictionary ExperienceConfigs { get; internal set; } = new Dictionary(); - public Dictionary HeroTypeConfigs { get; internal set; } = new Dictionary(); + public ImmutableList Levels { get; internal set; } + public ImmutableList LevelPresets { get; internal set; } + public ImmutableList LevelTypes { get; internal set; } + + public ImmutableList Items { get; internal set; } + public ImmutableDictionary ExperienceConfigs { get; internal set; } + public ImmutableDictionary 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(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(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(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 LoadItemData() + => new List() + .Concat(LoadWeaponData()) + .Concat(LoadArmorData()) + .Concat(LoadMiscData()) + .ToImmutableList(); private IEnumerable LoadWeaponData() { @@ -101,7 +89,7 @@ namespace OpenDiablo2.Core //.Where(x => !String.IsNullOrWhiteSpace(x[27])) .Select(x => x.ToWeapon()); - return data; + return data; } private IEnumerable LoadArmorData() @@ -114,7 +102,7 @@ namespace OpenDiablo2.Core //.Where(x => !String.IsNullOrWhiteSpace(x[27])) .Select(x => x.ToArmor()); - return data; + return data; } private IEnumerable 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 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 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()); } } diff --git a/OpenDiablo2.Core/GameState/GameState.cs b/OpenDiablo2.Core/GameState/GameState.cs index a1fa2460..f0fa7309 100644 --- a/OpenDiablo2.Core/GameState/GameState.cs +++ b/OpenDiablo2.Core/GameState/GameState.cs @@ -24,9 +24,10 @@ namespace OpenDiablo2.Core.GameState_ private readonly IMPQProvider mpqProvider; private readonly Func getMapEngine; private readonly Func getSessionManager; + private readonly Func getRandomizedMapGenerator; private float animationTime = 0f; - private List mapInfo; + private List mapInfo; private readonly List mapDataLookup = new List(); private ISessionManager sessionManager; @@ -57,7 +58,8 @@ namespace OpenDiablo2.Core.GameState_ ISoundProvider soundProvider, IMPQProvider mpqProvider, Func getMapEngine, - Func getSessionManager + Func getSessionManager, + Func 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 = new List(); 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 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(); - 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(), 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(); - 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(), 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 tiles = Enumerable.Empty(); + 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; } diff --git a/OpenDiablo2.Core/Map Engine/MapGenerator.cs b/OpenDiablo2.Core/Map Engine/MapGenerator.cs index bb704789..82160e05 100644 --- a/OpenDiablo2.Core/Map Engine/MapGenerator.cs +++ b/OpenDiablo2.Core/Map Engine/MapGenerator.cs @@ -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); } } diff --git a/OpenDiablo2.Core/OpenDiablo2.Core.csproj b/OpenDiablo2.Core/OpenDiablo2.Core.csproj index a19e3c60..04175d91 100644 --- a/OpenDiablo2.Core/OpenDiablo2.Core.csproj +++ b/OpenDiablo2.Core/OpenDiablo2.Core.csproj @@ -42,6 +42,9 @@ ..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + + ..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll + diff --git a/OpenDiablo2.Core/ResourceManager.cs b/OpenDiablo2.Core/ResourceManager.cs index 3e079b10..a26ba708 100644 --- a/OpenDiablo2.Core/ResourceManager.cs +++ b/OpenDiablo2.Core/ResourceManager.cs @@ -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}", () => diff --git a/OpenDiablo2.Core/packages.config b/OpenDiablo2.Core/packages.config index d0b0cbed..28f3824c 100644 --- a/OpenDiablo2.Core/packages.config +++ b/OpenDiablo2.Core/packages.config @@ -3,5 +3,6 @@ + \ No newline at end of file diff --git a/OpenDiablo2.GameServer/OpenDiablo2.GameServer.csproj b/OpenDiablo2.GameServer/OpenDiablo2.GameServer.csproj index 6973a36d..afba024c 100644 --- a/OpenDiablo2.GameServer/OpenDiablo2.GameServer.csproj +++ b/OpenDiablo2.GameServer/OpenDiablo2.GameServer.csproj @@ -39,6 +39,9 @@ ..\packages\log4net.2.0.8\lib\net45-full\log4net.dll + + ..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll + diff --git a/OpenDiablo2.GameServer/packages.config b/OpenDiablo2.GameServer/packages.config index 6764f843..470040d4 100644 --- a/OpenDiablo2.GameServer/packages.config +++ b/OpenDiablo2.GameServer/packages.config @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/OpenDiablo2.MapGenerators/AutofacModule.cs b/OpenDiablo2.MapGenerators/AutofacModule.cs new file mode 100644 index 00000000..e0f5c737 --- /dev/null +++ b/OpenDiablo2.MapGenerators/AutofacModule.cs @@ -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(att.MapName) + .InstancePerDependency(); + } + } + } +} diff --git a/OpenDiablo2.MapGenerators/BloodMoor.cs b/OpenDiablo2.MapGenerators/BloodMoor.cs new file mode 100644 index 00000000..cb607877 --- /dev/null +++ b/OpenDiablo2.MapGenerators/BloodMoor.cs @@ -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; + } + + + } + } + } + } +} diff --git a/OpenDiablo2.MapGenerators/OpenDiablo2.MapGenerators.csproj b/OpenDiablo2.MapGenerators/OpenDiablo2.MapGenerators.csproj new file mode 100644 index 00000000..3e65ec19 --- /dev/null +++ b/OpenDiablo2.MapGenerators/OpenDiablo2.MapGenerators.csproj @@ -0,0 +1,69 @@ + + + + + Debug + AnyCPU + {A3370223-A28B-45B8-AD79-C17EAD99AF36} + Library + Properties + OpenDiablo2.MapGenerators + OpenDiablo2.MapGenerators + v4.6.1 + 512 + true + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + + + + ..\packages\Autofac.4.8.1\lib\net45\Autofac.dll + + + ..\packages\log4net.2.0.8\lib\net45-full\log4net.dll + + + + ..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll + + + + + + + + + + + + + + + + + + {b743160e-a0bb-45dc-9998-967a85e50562} + OpenDiablo2.Common + + + + + + + \ No newline at end of file diff --git a/OpenDiablo2.MapGenerators/Properties/AssemblyInfo.cs b/OpenDiablo2.MapGenerators/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..1ed523ff --- /dev/null +++ b/OpenDiablo2.MapGenerators/Properties/AssemblyInfo.cs @@ -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")] diff --git a/OpenDiablo2.MapGenerators/packages.config b/OpenDiablo2.MapGenerators/packages.config new file mode 100644 index 00000000..470040d4 --- /dev/null +++ b/OpenDiablo2.MapGenerators/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/OpenDiablo2.SDL2/OpenDiablo2.SDL2.csproj b/OpenDiablo2.SDL2/OpenDiablo2.SDL2.csproj index ba59f093..84d97ce9 100644 --- a/OpenDiablo2.SDL2/OpenDiablo2.SDL2.csproj +++ b/OpenDiablo2.SDL2/OpenDiablo2.SDL2.csproj @@ -62,7 +62,7 @@ - + diff --git a/OpenDiablo2.SDL2/SDL2MusicProvider.cs b/OpenDiablo2.SDL2/SDL2SoundProvider.cs similarity index 91% rename from OpenDiablo2.SDL2/SDL2MusicProvider.cs rename to OpenDiablo2.SDL2/SDL2SoundProvider.cs index 360271ac..5e2c6e51 100644 --- a/OpenDiablo2.SDL2/SDL2MusicProvider.cs +++ b/OpenDiablo2.SDL2/SDL2SoundProvider.cs @@ -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) diff --git a/OpenDiablo2.TestConsole/OpenDiablo2.TestConsole.csproj b/OpenDiablo2.TestConsole/OpenDiablo2.TestConsole.csproj index a47a15e0..c38b6cb1 100644 --- a/OpenDiablo2.TestConsole/OpenDiablo2.TestConsole.csproj +++ b/OpenDiablo2.TestConsole/OpenDiablo2.TestConsole.csproj @@ -34,6 +34,9 @@ + + ..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll + @@ -48,6 +51,7 @@ + diff --git a/OpenDiablo2.TestConsole/Program.cs b/OpenDiablo2.TestConsole/Program.cs index bace4f4c..da6719d1 100644 --- a/OpenDiablo2.TestConsole/Program.cs +++ b/OpenDiablo2.TestConsole/Program.cs @@ -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 + "."); diff --git a/OpenDiablo2.TestConsole/packages.config b/OpenDiablo2.TestConsole/packages.config new file mode 100644 index 00000000..f1e32e9e --- /dev/null +++ b/OpenDiablo2.TestConsole/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/OpenDiablo2.sln b/OpenDiablo2.sln index b8cc6238..837c54ea 100644 --- a/OpenDiablo2.sln +++ b/OpenDiablo2.sln @@ -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 diff --git a/OpenDiablo2/OpenDiablo2.csproj b/OpenDiablo2/OpenDiablo2.csproj index d845e60b..a8fdcf74 100644 --- a/OpenDiablo2/OpenDiablo2.csproj +++ b/OpenDiablo2/OpenDiablo2.csproj @@ -106,6 +106,10 @@ {c29a84e8-e708-4be2-9946-202899b68e19} OpenDiablo2.GameServer + + {a3370223-a28b-45b8-ad79-c17ead99af36} + OpenDiablo2.MapGenerators + {05224fe7-293f-4184-b1d6-89f5171b60e0} OpenDiablo2.Scenes diff --git a/OpenDiablo2/Program.cs b/OpenDiablo2/Program.cs index a62cb7d7..0533c583 100644 --- a/OpenDiablo2/Program.cs +++ b/OpenDiablo2/Program.cs @@ -118,6 +118,12 @@ namespace OpenDiablo2 return (itemContainerType) => componentContext.Resolve(new NamedParameter("itemContainerLayout", ItemContainerLayout.Values[itemContainerType])); }); + containerBuilder.Register>(c => + { + var componentContext = c.Resolve(); + return (levelName) => componentContext.ResolveKeyed(levelName); + }); + /* Uncomment the below if we support multiple textbox types containerBuilder.Register>(c => {