diff --git a/OpenDiablo2.Common.UT/UT_PlayerState.cs b/OpenDiablo2.Common.UT/UT_PlayerState.cs index 443be0a2..fd690255 100644 --- a/OpenDiablo2.Common.UT/UT_PlayerState.cs +++ b/OpenDiablo2.Common.UT/UT_PlayerState.cs @@ -17,9 +17,13 @@ namespace OpenDiablo2.Common.UT energy: 20, health: 50, mana: 40, stamina: 30, manaRegen: 20, perlevelhealth: 3, perlevelmana: 1.5, perlevelstamina: 1, pervitalityhealth: 2, pervitalitystamina: 1, perenergymana: 1.5, + perLevelStatPoints: 5, baseatkrating: -30, basedefrating: -30, perdexterityatkrating: 5, perdexteritydefrating: 4, walkVelocity: 6, runVelocity: 9, runDrain: 20, walkFrames: 8, runFrames: 8, swingFrames: 8, - spellFrames: 8, getHitFrames: 8, bowFrames: 8, startingSkill: 0, baseWeaponClass: "hth", + spellFrames: 8, getHitFrames: 8, bowFrames: 8, startingSkill: "", + startingSkills: new string[] { }, + allSkillsBonusString: "", firstTabBonusString: "", secondTabBonusString: "", + thirdTabBonusString: "", classOnlyBonusString: "", baseWeaponClass: "hth", initialEquipment: new List()); LevelExperienceConfig expconfig = new LevelExperienceConfig(new List() { diff --git a/OpenDiablo2.Common/Attributes/SkillInfoAttribute.cs b/OpenDiablo2.Common/Attributes/SkillInfoAttribute.cs new file mode 100644 index 00000000..b65aa4a6 --- /dev/null +++ b/OpenDiablo2.Common/Attributes/SkillInfoAttribute.cs @@ -0,0 +1,40 @@ +/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using OpenDiablo2.Common.Enums; +using System; +using System.Collections.Generic; + +namespace OpenDiablo2.Common.Attributes +{ + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class SkillInfoAttribute : Attribute + { + int a = 5; + public SkillInfoAttribute(eHero hero, int spriteIndex = 0, int level/*levelGroup*/ = 0, params eSkill[] skillsRequired) + { + Hero = hero; + SpriteIndex = spriteIndex; + Level = level; + SkillsRequired = skillsRequired ?? Array.Empty(); + } + + public eHero Hero { get; } + public int SpriteIndex { get; } + public int Level { get; } + public IReadOnlyList SkillsRequired { get; } + } +} diff --git a/OpenDiablo2.Common/Enums/eButtonType.cs b/OpenDiablo2.Common/Enums/eButtonType.cs index 15b743ec..beb8d7fa 100644 --- a/OpenDiablo2.Common/Enums/eButtonType.cs +++ b/OpenDiablo2.Common/Enums/eButtonType.cs @@ -1,26 +1,26 @@ -namespace OpenDiablo2.Common.Enums -{ - public enum eButtonType - { - Wide, - Medium, - Narrow, - Cancel, - Tall, - Short, - // Game UI - Skill, - Run, - Menu, - GoldCoin, - Close, - SecondaryInvHand, - MinipanelCharacter, - MinipanelInventory, - MinipanelSkill, - MinipanelAutomap, - MinipanelMessage, - MinipanelQuest, - MinipanelMenu - } -} +namespace OpenDiablo2.Common.Enums +{ + public enum eButtonType + { + Wide, + Medium, + Narrow, + Cancel, + Tall, + Short, + // Game UI + Skill, + Run, + Menu, + GoldCoin, + Close, + SecondaryInvHand, + MinipanelCharacter, + MinipanelInventory, + MinipanelSkill, + MinipanelAutomap, + MinipanelMessage, + MinipanelQuest, + MinipanelMenu + } +} diff --git a/OpenDiablo2.Common/Enums/eHero.cs b/OpenDiablo2.Common/Enums/eHero.cs index 1a05e89b..1c4b48e2 100644 --- a/OpenDiablo2.Common/Enums/eHero.cs +++ b/OpenDiablo2.Common/Enums/eHero.cs @@ -1,14 +1,28 @@ -using System; +/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OpenDiablo2.Common.Enums { public enum eHero { + None, Barbarian, Necromancer, Paladin, diff --git a/OpenDiablo2.Common/Enums/eSkill.cs b/OpenDiablo2.Common/Enums/eSkill.cs new file mode 100644 index 00000000..e04114c9 --- /dev/null +++ b/OpenDiablo2.Common/Enums/eSkill.cs @@ -0,0 +1,339 @@ +/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using OpenDiablo2.Common.Attributes; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace OpenDiablo2.Common.Enums +{ + public static class SkillsHelper + { + public static SkillInfoAttribute GetSkillInfo(this eSkill skill) + { + var attr = skill.GetType().GetCustomAttributes(typeof(SkillInfoAttribute), false).FirstOrDefault() as SkillInfoAttribute; + Debug.Assert(attr != null, $"Skill {skill} does not contain SkillInfo attribute."); + return attr; + } + + public static IEnumerable GetHeroSkills(eHero hero) + { + foreach(var skill in (eSkill[])Enum.GetValues(typeof(eSkill))) + { + if(skill.GetSkillInfo()?.Hero == hero) + yield return skill; + } + } + } + + //public int[] eSkillLevelTier + //{ + // 1, + // 6, + // 12, + // 18, + // 24, + // 30 + //}; + + public enum eSkill + { + /// Generic Skills + [SkillInfo(eHero.None, 1)] + Attack = 0, + [SkillInfo(eHero.None, 3)] + Throw = 2, + [SkillInfo(eHero.None, 2)] + Unsummon = 3, + + /// Amazon + // Javelin and Spear Skills + [SkillInfo(eHero.Amazon, 4, 1)] + Jab = 10, + [SkillInfo(eHero.Amazon, 8, 6, Jab)] + PowerStrike = 14, + [SkillInfo(eHero.Amazon, 9, 6)] + PoisonJavelin = 15, + [SkillInfo(eHero.Amazon, 13, 12, Jab)] + Impale = 19, + [SkillInfo(eHero.Amazon, 14, 12, PoisonJavelin)] + LightningBolt = 20, + [SkillInfo(eHero.Amazon, 18, 18, LightningBolt, PowerStrike)] + ChargedStrike = 24, + [SkillInfo(eHero.Amazon, 19, 18, LightningBolt)] + PlagueJavelin = 25, + [SkillInfo(eHero.Amazon, 24, 24, Impale)] + Fend = 30, + [SkillInfo(eHero.Amazon, 28, 30, ChargedStrike)] + LightningStrike = 34, + [SkillInfo(eHero.Amazon, 29, 30, PlagueJavelin)] + LightningFury = 35, + // Passive and Magic Skills + [SkillInfo(eHero.Amazon, 2, 1)] + InnerSight = 8, + [SkillInfo(eHero.Amazon, 3, 1)] + CriticalStrike = 9, + [SkillInfo(eHero.Amazon, 7, 6)] + Dodge = 13, + [SkillInfo(eHero.Amazon, 11, 12, InnerSight)] + SlowMissles = 17, + [SkillInfo(eHero.Amazon, 12, 12, Dodge)] + Avoid = 18, + [SkillInfo(eHero.Amazon, 17, 18, CriticalStrike)] + Penetrate = 23, + [SkillInfo(eHero.Amazon, 22, 24, SlowMissles)] + Decoy = 28, // called Dopplezon + [SkillInfo(eHero.Amazon, 23, 24, Avoid)] + Evade = 29, + [SkillInfo(eHero.Amazon, 26, 30, Decoy, Evade)] + Valkyrie = 32, + [SkillInfo(eHero.Amazon, 27, 30, Penetrate)] + Pierce = 33, + // Bow and Crossbow Skills + [SkillInfo(eHero.Amazon, 0, 1)] + MagicArrow = 6, + [SkillInfo(eHero.Amazon, 1, 1)] + FireArrow = 7, + [SkillInfo(eHero.Amazon, 5, 6)] + ColdArrow = 11, + [SkillInfo(eHero.Amazon, 6, 6, MagicArrow)] + MultipleShot = 12, + [SkillInfo(eHero.Amazon, 10, 12, FireArrow)] + ExplodingArrow = 16, + [SkillInfo(eHero.Amazon, 15, 18, ColdArrow)] + IceArrow = 21, + [SkillInfo(eHero.Amazon, 16, 18, MultipleShot)] + GuidedArrow = 22, + [SkillInfo(eHero.Amazon, 20, 24, GuidedArrow)] + Strafe = 26, + [SkillInfo(eHero.Amazon, 21, 24, ExplodingArrow)] + ImmolationArrow = 27, + [SkillInfo(eHero.Amazon, 25, 30, IceArrow)] + FreezingArrow = 31, + + /// Assassin + // Martial Arts + TigerStrike, + DragonTalon, + FistsOfFire, + DragonClaw, + CobraStrike, + ClawsOfThunder, + DragonTail, + BladesOfIce, + DragonFlight, + PhoenixStrike, + // Shadow Disciplines + ClawMastery, + PsychicHammer, + BurstOfSpeed, + WeaponBlock, + CloakOfShadows, + Fade, + ShadowWarrior, + MindBlast, + Venom, + ShadowMaster, + // Traps + FireBlast, + ShockWeb, + BladeSentinel, + ChargedBoltSentry, + WakeOfFire, + BladeFury, + LightningSentry, + WakeOfInferno, + DeathSentry, + BladeShield, + + /// Necromancer + // Summoning Spells + SkeletonMastery, + RaiseSkeleton, + ClayGolem, + GolemMastery, + RaiseSkeletalMage, + BloodGolem, + SummonResist, + IronGolem, + FireGolem, + Revive, + // Poison and Bone Spells + Teeth, + BoneArmor, + PoisonDagger, + CorpseExplosion, + BoneWall, + PoisonExplosion, + BoneSpear, + BonePrision, + PoisonNova, + BoneSpirit, + // Curses + AmplifyDamage, + DimVision, + Weaken, + IronMaiden, + Terror, + Confuse, + LifeTap, + Attract, + Decrepify, + LowerResist, + + /// Barbarian + // Warcries + Howl, + FindPotion, + Taunt, + Shout, + FindItem, + BattleCry, + BattleOrders, + GrimWard, + WarCry, + BattleCommand, + // Combat Masteries + SwordMastery, + AxeMastery, + MaceMastery, + PoleArmMastery, + ThrowingMastery, + SpearMastery, + IncreasedStamina, + IronSkin, + IncreasedSpeed, + NaturalResistance, + // Combat Skills + Bash, + Leap, + DoubleSwing, + Stun, + DoubleThrow, + LeapAttack, + Concentrate, + Frenzy, + Whirlwind, + Berserk, + + /// Paladin + // Defensive Auras + Prayer, + ResistFire, + Defiance, + ResistCold, + Cleansing, + ResistLightning, + Vigor, + Meditation, + Redemption, + Salvation, + // Offensive Auras + Might, + HolyFire, + Thorns, + BlessedAim, + Concentration, + HolyFreeze, + HolyShock, + Sanctuary, + Fanaticism, + Conviction, + // Combat Skills + Sacrafice, + Smite, + HolyBolt, + Zeal, + Charge, + Vengeance, + BlessedHammer, + Conversion, + HolyShield, + FistOfTheHeavens, + + /// Sorceress + // Cold Spells + IceBolt, + FrozenArmor, + FrostNova, + IceBlast, + ShiverArmor, + GlacialSpike, + Blizzard, + ChillingArmor, + FrozenOrb, + ColdMastery, + // Lightning Spells + ChargedBolt, + StaticField, + Telekinesis, + Nova, + Lightning, + ChainLightning, + Teleport, + ThunderStorm, + EnergyShield, + LightningMastery, + // Fire Spells + FireBolt, + Warmth, + Inferno, + Blaze, + FireBall, + FireWall, + Enchant, + Meteor, + FireMastery, + Hydra, + + /// Druid + // Elemental + Firestorm, + MoltenBoulder, + ArcticBlast, + Fissure, + CycloneArmor, + Twister, + Volcano, + Tornado, + Armageddon, + Hurricane, + // Shape Shifting + Werewolf, + Lycanthropy, + Werebear, + FeralRage, + Maul, + Rabies, + FireClaw, + Hunger, + ShockWave, + Fury, + // Summoning + Raven, + PoisonCreeper, + OakSage, + SummonSpiritWolf, + CarrionVine, + heartOfWolverine, + SummonDireWolf, + SolarCreeper, + SpiritOfBarbs, + SummonGrizzly + } +} diff --git a/OpenDiablo2.Common/Interfaces/IGameState.cs b/OpenDiablo2.Common/Interfaces/IGameState.cs index d446fdb6..d2b9a933 100644 --- a/OpenDiablo2.Common/Interfaces/IGameState.cs +++ b/OpenDiablo2.Common/Interfaces/IGameState.cs @@ -15,11 +15,14 @@ namespace OpenDiablo2.Common.Interfaces string MapName { get; } Palette CurrentPalette { get; } IEnumerable PlayerInfos { get; } + eDifficulty Difficulty { get; } ItemInstance SelectedItem { get; } void SelectItem(ItemInstance item); - void Initialize(string characterName, eHero hero, eSessionType sessionType); + int CameraOffset { get; set; } + + void Initialize(string characterName, eHero hero, eSessionType sessionType, eDifficulty difficulty); void Update(long ms); IEnumerable GetMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType); void UpdateMapCellInfo(int cellX, int cellY, eRenderCellType renderCellType, IEnumerable mapCellInfo); diff --git a/OpenDiablo2.Common/Interfaces/Mobs/IHeroTypeConfig.cs b/OpenDiablo2.Common/Interfaces/Mobs/IHeroTypeConfig.cs index 9b36fd13..066c0c6d 100644 --- a/OpenDiablo2.Common/Interfaces/Mobs/IHeroTypeConfig.cs +++ b/OpenDiablo2.Common/Interfaces/Mobs/IHeroTypeConfig.cs @@ -28,6 +28,8 @@ namespace OpenDiablo2.Common.Interfaces.Mobs double PerVitalityStamina { get; } double PerEnergyMana { get; } + int PerLevelStatPoints { get; } + int BaseAttackRating { get; } int PerDexterityAttackRating { get; } @@ -45,7 +47,14 @@ namespace OpenDiablo2.Common.Interfaces.Mobs int GetHitFrames { get; } int BowFrames { get; } - int StartingSkill { get; } + string StartingSkill { get; } + string[] StartingSkills { get; } + + string AllSkillsBonusString { get; } + string FirstTabBonusString { get; } + string SecondTabBonusString { get; } + string ThirdTabBonusString { get; } + string ClassOnlyBonusString { get; } string BaseWeaponClass { get; } List InitialEquipment { get; } diff --git a/OpenDiablo2.Common/Interfaces/Mobs/IMissileTypeConfig.cs b/OpenDiablo2.Common/Interfaces/Mobs/IMissileTypeConfig.cs new file mode 100644 index 00000000..f8a5ea6f --- /dev/null +++ b/OpenDiablo2.Common/Interfaces/Mobs/IMissileTypeConfig.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenDiablo2.Common.Interfaces.Mobs +{ + public interface IMissileTypeConfig + { + } +} diff --git a/OpenDiablo2.Common/Models/ButtonLayout.cs b/OpenDiablo2.Common/Models/ButtonLayout.cs index 8ae0ba47..21cfc7a2 100644 --- a/OpenDiablo2.Common/Models/ButtonLayout.cs +++ b/OpenDiablo2.Common/Models/ButtonLayout.cs @@ -1,66 +1,66 @@ -/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using OpenDiablo2.Common.Enums; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Drawing; - -namespace OpenDiablo2.Common.Models -{ - public class ButtonLayout - { - public int XSegments { get; set; } = 1; - public int YSegments { get; set; } = 1; - public string ResourceName { get; set; } - public string PaletteName { get; set; } - public bool Toggleable { get; set; } = false; - public int BaseFrame { get; set; } = 0; - public int DisabledFrame { get; set; } = -1; - public string FontPath { get; set; } = ResourcePaths.FontExocet10; - public Rectangle ClickableRect { get; set; } - public bool AllowFrameChange { get; set; } = true; - - public bool IsDarkenedWhenDisabled => DisabledFrame == -1; - - public static ImmutableDictionary Values { get; } = new Dictionary - { - {eButtonType.Wide, new ButtonLayout { XSegments = 2, ResourceName = ResourcePaths.WideButtonBlank, PaletteName = Palettes.Units } }, - {eButtonType.Medium, new ButtonLayout{ ResourceName = ResourcePaths.MediumButtonBlank, PaletteName = Palettes.Units } }, - {eButtonType.Narrow, new ButtonLayout { ResourceName = ResourcePaths.NarrowButtonBlank, PaletteName = Palettes.Units } }, - {eButtonType.Tall, new ButtonLayout { ResourceName = ResourcePaths.TallButtonBlank, PaletteName = Palettes.Units } }, - {eButtonType.Short, new ButtonLayout { ResourceName = ResourcePaths.ShortButtonBlank, PaletteName = Palettes.Units, FontPath = ResourcePaths.FontExocet10 } }, - {eButtonType.Cancel, new ButtonLayout { ResourceName = ResourcePaths.CancelButton, PaletteName = Palettes.Units } }, - // Minipanel - {eButtonType.MinipanelCharacter, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 0 } }, - {eButtonType.MinipanelInventory, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 2 } }, - {eButtonType.MinipanelSkill, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 4 } }, - {eButtonType.MinipanelAutomap, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 8 } }, - {eButtonType.MinipanelMessage, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 10 } }, - {eButtonType.MinipanelQuest, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 12 } }, - {eButtonType.MinipanelMenu, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 14 } }, - - {eButtonType.SecondaryInvHand, new ButtonLayout { ResourceName = ResourcePaths.InventoryWeaponsTab, PaletteName = Palettes.Units, - ClickableRect = new Rectangle(0, 0, 0, 20), AllowFrameChange = false } }, - {eButtonType.Run, new ButtonLayout { ResourceName = ResourcePaths.RunButton, PaletteName = Palettes.Units, Toggleable = true } }, - {eButtonType.Menu, new ButtonLayout { ResourceName = ResourcePaths.MenuButton, PaletteName = Palettes.Units, Toggleable = true } }, - {eButtonType.GoldCoin, new ButtonLayout { ResourceName = ResourcePaths.GoldCoinButton, PaletteName = Palettes.Units } }, - {eButtonType.Close, new ButtonLayout { ResourceName = ResourcePaths.SquareButton, PaletteName = Palettes.Units, BaseFrame = 10 } }, - {eButtonType.Skill, new ButtonLayout { ResourceName = ResourcePaths.AddSkillButton, PaletteName = Palettes.Units, DisabledFrame = 2 } }, - }.ToImmutableDictionary(); - } - -} +/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using OpenDiablo2.Common.Enums; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Drawing; + +namespace OpenDiablo2.Common.Models +{ + public class ButtonLayout + { + public int XSegments { get; set; } = 1; + public int YSegments { get; set; } = 1; + public string ResourceName { get; set; } + public string PaletteName { get; set; } + public bool Toggleable { get; set; } = false; + public int BaseFrame { get; set; } = 0; + public int DisabledFrame { get; set; } = -1; + public string FontPath { get; set; } = ResourcePaths.FontExocet10; + public Rectangle ClickableRect { get; set; } + public bool AllowFrameChange { get; set; } = true; + + public bool IsDarkenedWhenDisabled => DisabledFrame == -1; + + public static ImmutableDictionary Values { get; } = new Dictionary + { + {eButtonType.Wide, new ButtonLayout { XSegments = 2, ResourceName = ResourcePaths.WideButtonBlank, PaletteName = Palettes.Units } }, + {eButtonType.Medium, new ButtonLayout{ ResourceName = ResourcePaths.MediumButtonBlank, PaletteName = Palettes.Units } }, + {eButtonType.Narrow, new ButtonLayout { ResourceName = ResourcePaths.NarrowButtonBlank, PaletteName = Palettes.Units } }, + {eButtonType.Tall, new ButtonLayout { ResourceName = ResourcePaths.TallButtonBlank, PaletteName = Palettes.Units } }, + {eButtonType.Short, new ButtonLayout { ResourceName = ResourcePaths.ShortButtonBlank, PaletteName = Palettes.Units, FontPath = ResourcePaths.FontExocet10 } }, + {eButtonType.Cancel, new ButtonLayout { ResourceName = ResourcePaths.CancelButton, PaletteName = Palettes.Units } }, + // Minipanel + {eButtonType.MinipanelCharacter, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 0 } }, + {eButtonType.MinipanelInventory, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 2 } }, + {eButtonType.MinipanelSkill, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 4 } }, + {eButtonType.MinipanelAutomap, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 8 } }, + {eButtonType.MinipanelMessage, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 10 } }, + {eButtonType.MinipanelQuest, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 12 } }, + {eButtonType.MinipanelMenu, new ButtonLayout { ResourceName = ResourcePaths.MinipanelButton, PaletteName = Palettes.Units, BaseFrame = 14 } }, + + {eButtonType.SecondaryInvHand, new ButtonLayout { ResourceName = ResourcePaths.InventoryWeaponsTab, PaletteName = Palettes.Units, + ClickableRect = new Rectangle(0, 0, 0, 20), AllowFrameChange = false } }, + {eButtonType.Run, new ButtonLayout { ResourceName = ResourcePaths.RunButton, PaletteName = Palettes.Units, Toggleable = true } }, + {eButtonType.Menu, new ButtonLayout { ResourceName = ResourcePaths.MenuButton, PaletteName = Palettes.Units, Toggleable = true } }, + {eButtonType.GoldCoin, new ButtonLayout { ResourceName = ResourcePaths.GoldCoinButton, PaletteName = Palettes.Units } }, + {eButtonType.Close, new ButtonLayout { ResourceName = ResourcePaths.SquareButton, PaletteName = Palettes.Units, BaseFrame = 10 } }, + {eButtonType.Skill, new ButtonLayout { ResourceName = ResourcePaths.AddSkillButton, PaletteName = Palettes.Units, DisabledFrame = 2 } }, + }.ToImmutableDictionary(); + } + +} diff --git a/OpenDiablo2.Common/Models/Item/Armor.cs b/OpenDiablo2.Common/Models/Item/Armor.cs index f5a0a65d..6a8bc0d1 100644 --- a/OpenDiablo2.Common/Models/Item/Armor.cs +++ b/OpenDiablo2.Common/Models/Item/Armor.cs @@ -9,25 +9,56 @@ using System.Threading.Tasks; namespace OpenDiablo2.Common.Models { - public sealed class Armor : Item + public sealed class Armor : Item { - public string Type { get; internal set; } = "FIXME"; // TODO: Fix this please + public string Type { get; internal set; } + + public Dictionary ArmorTypes = new Dictionary() { + {eCompositType.RightArm, eArmorType.Lite}, + {eCompositType.LeftArm, eArmorType.Lite}, + {eCompositType.Torso, eArmorType.Lite}, + {eCompositType.Legs, eArmorType.Lite}, + {eCompositType.Special1, eArmorType.Lite}, + {eCompositType.Special2, eArmorType.Lite}, + }; } public static class ArmorHelper { - public static Armor ToArmor(this string[] row) - => new Armor + public static Armor ToArmor(this string[] row) + { + + var Armor = new Armor { Name = row[0], Code = row[17], - InvFile = row[33] + InvFile = row[34], + Type = row[48] }; + if(Armor.Type == "tors") + { + Armor.ArmorTypes[eCompositType.RightArm] = (eArmorType)Enum.Parse(typeof(eArmorType), row[37]); + Armor.ArmorTypes[eCompositType.LeftArm] = (eArmorType)Enum.Parse(typeof(eArmorType), row[38]); + Armor.ArmorTypes[eCompositType.Torso] = (eArmorType)Enum.Parse(typeof(eArmorType), row[39]); + Armor.ArmorTypes[eCompositType.Legs] = (eArmorType)Enum.Parse(typeof(eArmorType), row[40]); + Armor.ArmorTypes[eCompositType.Special1] = (eArmorType)Enum.Parse(typeof(eArmorType), row[41]); + Armor.ArmorTypes[eCompositType.Special2] = (eArmorType)Enum.Parse(typeof(eArmorType), row[42]); + } + + return Armor; + } + public static void Write(this BinaryWriter binaryWriter, Armor armor) { (armor as Item).Write(binaryWriter); binaryWriter.Write(armor.Type); + + // Assuming order will be fine + foreach(var armorType in armor.ArmorTypes) + { + binaryWriter.Write((byte)armorType.Value); + } } public static Armor ReadItemArmor(this BinaryReader binaryReader) @@ -35,6 +66,14 @@ namespace OpenDiablo2.Common.Models var result = new Armor(); Item.Read(binaryReader, result); result.Type = binaryReader.ReadString(); + + result.ArmorTypes[eCompositType.RightArm] = (eArmorType)binaryReader.ReadByte(); + result.ArmorTypes[eCompositType.LeftArm] = (eArmorType)binaryReader.ReadByte(); + result.ArmorTypes[eCompositType.Torso] = (eArmorType)binaryReader.ReadByte(); + result.ArmorTypes[eCompositType.Legs] = (eArmorType)binaryReader.ReadByte(); + result.ArmorTypes[eCompositType.Special1] = (eArmorType)binaryReader.ReadByte(); + result.ArmorTypes[eCompositType.Special2] = (eArmorType)binaryReader.ReadByte(); + return result; } } diff --git a/OpenDiablo2.Common/Models/Item/Misc.cs b/OpenDiablo2.Common/Models/Item/Misc.cs index e4567b8e..14ce63de 100644 --- a/OpenDiablo2.Common/Models/Item/Misc.cs +++ b/OpenDiablo2.Common/Models/Item/Misc.cs @@ -19,8 +19,8 @@ namespace OpenDiablo2.Common.Models => new Misc { Name = row[0], - Code = row[12], - InvFile = row[21] + Code = row[13], + InvFile = row[23] }; public static void Write(this BinaryWriter binaryWriter, Misc misc) diff --git a/OpenDiablo2.Common/Models/Item/Weapon.cs b/OpenDiablo2.Common/Models/Item/Weapon.cs index c4f64bf2..c87dc6ac 100644 --- a/OpenDiablo2.Common/Models/Item/Weapon.cs +++ b/OpenDiablo2.Common/Models/Item/Weapon.cs @@ -19,9 +19,9 @@ namespace OpenDiablo2.Common.Models => new Weapon { Name = row[0], - Code = row[2], - WeaponClass = row[34], - InvFile = row[45] + Code = row[3], + WeaponClass = row[37], + InvFile = row[48] }; public static void Write(this BinaryWriter binaryWriter, Weapon weapon) diff --git a/OpenDiablo2.Common/Models/LevelDetail.cs b/OpenDiablo2.Common/Models/LevelDetail.cs index e47402ea..3c0e6ec9 100644 --- a/OpenDiablo2.Common/Models/LevelDetail.cs +++ b/OpenDiablo2.Common/Models/LevelDetail.cs @@ -1,4 +1,5 @@ -using System; +using OpenDiablo2.Common.Enums; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,6 +7,31 @@ using System.Threading.Tasks; namespace OpenDiablo2.Common.Models { + public sealed class LevelDetailDifficulty + { + /// Horizontal size of the level + public int SizeX { get; internal set; } + /// Vertical size of the level + public int SizeY { get; internal set; } + + /// Level (controls the item level of items that drop from chests etc) + public int MonLevel { get; internal set; } + /// MonLevel, but for expansion games (only use if non-null) + public int? MonLevelEx { get; internal set; } + + /// The Density of Monsters + public int MonDen { get; internal set; } + + /// Minimum Unique and Champion Monsters Spawned in this Level + public int MonUMin { get; internal set; } + + /// Maximum Unique and Champion Monsters Spawned in this Level + public int MonUMax { get; internal set; } + + /// Monster Species 1-25 (use ID from MonStats.txt) + public string[] M1_25 { get; internal set; } + } + public sealed class LevelDetail { /// Internal level name @@ -14,20 +40,26 @@ namespace OpenDiablo2.Common.Models /// Level ID (Used in columns like VIS0-1) public int Id { get; internal set; } + public Dictionary Difficulties = new Dictionary(); + /// Palette public int Pal { get; internal set; } /// Act public int Act { get; internal set; } + /// + /// Used in classic (non-expansion) games + /// If this is set, the character must have completed the quest with this id to take a portal here + /// + public int? QuestFlag { get; internal set; } + /// See above, but for expansion games + public int? QuestFlagEx { get; internal set; } + /// What layer the level is on (surface levels are always 0) public int Layer { get; internal set; } - /// Horizontal size of the level - public int SizeX { get; internal set; } - - /// Vertical size of the level - public int SizeY { get; internal set; } + /// Horizontal Placement Offset public int OffsetX { get; internal set; } @@ -38,29 +70,34 @@ namespace OpenDiablo2.Common.Models /// Special setting for levels that aren't random or preset (like Outer Cloister and Arcane Sancturary) public int Depend { get; internal set; } + /// if false, teleport not allowed in this level + public bool Teleport { get; internal set; } + /// if true, cannot teleport through walls and objects in this level + public bool CantTeleportThroughWallsObjects { get; internal set; } + /// If true, it rains (or snows) - public int Rain { get; internal set; } + public bool Rain { get; internal set; } /// Unused - public int Mud { get; internal set; } + public bool Mud { get; internal set; } /// Perspective mode forced to off if set to 1 - public int NoPer { get; internal set; } + public bool NoPer { get; internal set; } /// Level of sight drawing - public int LOSDraw { get; internal set; } + public bool LOSDraw { get; internal set; } /// Unknown - public int FloorFilter { get; internal set; } + public bool FloorFilter { get; internal set; } /// Unknown - public int BlankScreen { get; internal set; } + public bool BlankScreen { get; internal set; } /// For levels bordered with mountains or walls - public int DrawEdges { get; internal set; } + public bool DrawEdges { get; internal set; } /// Set to 1 if this is underground or inside - public int IsInside { get; internal set; } + public bool 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; internal set; } @@ -99,13 +136,13 @@ namespace OpenDiablo2.Common.Models public int Blue { get; internal set; } /// Unknown - public int Portal { get; internal set; } + public bool Portal { get; internal set; } /// Settings for preset levels - public int Position { get; internal set; } + public bool Position { get; internal set; } /// If true, monster/creatures get saved/loaded instead of despawning. - public int SaveMonsters { get; internal set; } + public bool SaveMonsters { get; internal set; } /// Quest flags public int Quest { get; internal set; } @@ -113,23 +150,9 @@ namespace OpenDiablo2.Common.Models /// Usually 2025, unknown public int WarpDist { get; internal set; } - /// Level on Normal (controls the item level of items that drop from chests etc) - public int MonLvl1 { get; internal set; } + - /// Level on Nightmare (controls the item level of items that drop from chests etc) - public int MonLvl2 { get; internal set; } - - /// Level on Hell (controls the item level of items that drop from chests etc) - public int MonLvl3 { get; internal set; } - - /// The Density of Monsters - public int MonDen { get; internal set; } - - /// Minimum Unique and Champion Monsters Spawned in this Level - public int MonUMin { get; internal set; } - - /// Maximum Unique and Champion Monsters Spawned in this Level - public int MonUMax { get; internal set; } + /// public int MonWndr { get; internal set; } @@ -140,26 +163,20 @@ namespace OpenDiablo2.Common.Models /// 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; internal set; } - /// Monster Species 1-25 (use ID from MonStats.txt) - 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; internal set; } - - /// How many different Species of Monsters can spawn as Uniques and Champions in this Area (works like Mtot) - public int Utot { get; internal set; } + /// If true, ranged monsters have spawning preference + public bool RangedSpawnPreference { 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; internal set; } + public string[] 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; internal set; } + /// Critter Species 1-4 (For monsters set to 1 in the IsCritter Column in MonStats.txt) + public string[] C1_4 { 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; internal set; } + /// Related to C1-5, eg: if you spawn a critter thru C1 then set this column to 30 etc. Controls chance for critter to spawn. + public int[] CA1_4 { get; internal set; } - /// Unknown - public int[] CD1_5 { get; internal set; } + /// Unknown and unused + public int[] CD1_4 { get; internal set; } /// unknown public int Themes { get; internal set; } @@ -195,28 +212,60 @@ namespace OpenDiablo2.Common.Models public static class LevelDetailHelper { + private static int? IntOrEmpty(string s) + { + if (string.IsNullOrWhiteSpace(s)) + { + return null; + } + + return Convert.ToInt32(s); + } + + private static int IntOrZero(string s) + { + if (string.IsNullOrWhiteSpace(s)) + { + return 0; + } + + return Convert.ToInt32(s); + } + public static LevelDetail ToLevelDetail(this string[] v, IEnumerable levelPresets, IEnumerable levelTypes) { var result = new LevelDetail(); + result.Difficulties.Add(eDifficulty.NORMAL, new LevelDetailDifficulty()); + result.Difficulties.Add(eDifficulty.NIGHTMARE, new LevelDetailDifficulty()); + result.Difficulties.Add(eDifficulty.HELL, new LevelDetailDifficulty()); int i = 0; result.Name = v[i++]; result.Id = Convert.ToInt32(v[i++]); result.Pal = Convert.ToInt32(v[i++]); result.Act = Convert.ToInt32(v[i++]); + result.QuestFlag = IntOrEmpty(v[i++]); + result.QuestFlagEx = IntOrEmpty(v[i++]); result.Layer = Convert.ToInt32(v[i++]); - result.SizeX = Convert.ToInt32(v[i++]); - result.SizeY = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.NORMAL].SizeX = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.NORMAL].SizeY = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.NIGHTMARE].SizeX = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.NIGHTMARE].SizeY = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.HELL].SizeX = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.HELL].SizeY = Convert.ToInt32(v[i++]); result.OffsetX = Convert.ToInt32(v[i++]); result.OffsetY = Convert.ToInt32(v[i++]); result.Depend = Convert.ToInt32(v[i++]); - result.Rain = Convert.ToInt32(v[i++]); - result.Mud = Convert.ToInt32(v[i++]); - result.NoPer = Convert.ToInt32(v[i++]); - result.LOSDraw = Convert.ToInt32(v[i++]); - result.FloorFilter = Convert.ToInt32(v[i++]); - result.BlankScreen = Convert.ToInt32(v[i++]); - result.DrawEdges = Convert.ToInt32(v[i++]); - result.IsInside = Convert.ToInt32(v[i++]); + result.Teleport = (v[i] == "1" || v[i] == "2"); + result.CantTeleportThroughWallsObjects = (v[i] == "0" || v[i] == "2"); + i++; + result.Rain = (v[i++] == "1"); + result.Mud = (v[i++] == "1"); + result.NoPer = (v[i++] == "1"); + result.LOSDraw = (v[i++] == "1"); + result.FloorFilter = (v[i++] == "1"); + result.BlankScreen = (v[i++] == "1"); + result.DrawEdges = (v[i++] == "1"); + result.IsInside = (v[i++] == "1"); result.DrlgType = Convert.ToInt32(v[i++]); result.LevelTypeId = Convert.ToInt32(v[i++]); result.SubType = Convert.ToInt32(v[i++]); @@ -231,33 +280,52 @@ namespace OpenDiablo2.Common.Models result.Red = Convert.ToInt32(v[i++]); result.Green = Convert.ToInt32(v[i++]); result.Blue = Convert.ToInt32(v[i++]); - result.Portal = Convert.ToInt32(v[i++]); - result.Position = Convert.ToInt32(v[i++]); - result.SaveMonsters = Convert.ToInt32(v[i++]); + result.Portal = (v[i++] == "1"); + result.Position = (v[i++] == "1"); + result.SaveMonsters = (v[i++] == "1"); result.Quest = Convert.ToInt32(v[i++]); result.WarpDist = Convert.ToInt32(v[i++]); - result.MonLvl1 = Convert.ToInt32(v[i++]); - result.MonLvl2 = Convert.ToInt32(v[i++]); - result.MonLvl3 = Convert.ToInt32(v[i++]); - result.MonDen = Convert.ToInt32(v[i++]); - result.MonUMin = Convert.ToInt32(v[i++]); - result.MonUMax = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.NORMAL].MonLevel = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.NIGHTMARE].MonLevel = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.HELL].MonLevel = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.NORMAL].MonLevelEx = IntOrEmpty(v[i++]); + result.Difficulties[eDifficulty.NIGHTMARE].MonLevelEx = IntOrEmpty(v[i++]); + result.Difficulties[eDifficulty.HELL].MonLevelEx = IntOrEmpty(v[i++]); + result.Difficulties[eDifficulty.NORMAL].MonDen = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.NIGHTMARE].MonDen = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.HELL].MonDen = Convert.ToInt32(v[i++]); + result.Difficulties[eDifficulty.NORMAL].MonUMin = IntOrZero(v[i++]); + result.Difficulties[eDifficulty.NORMAL].MonUMax = IntOrZero(v[i++]); + result.Difficulties[eDifficulty.NIGHTMARE].MonUMin = IntOrZero(v[i++]); + result.Difficulties[eDifficulty.NIGHTMARE].MonUMax = IntOrZero(v[i++]); + result.Difficulties[eDifficulty.HELL].MonUMin = IntOrZero(v[i++]); + result.Difficulties[eDifficulty.HELL].MonUMax = IntOrZero(v[i++]); result.MonWndr = Convert.ToInt32(v[i++]); result.MonSpcWalk = Convert.ToInt32(v[i++]); - result.Mtot = Convert.ToInt32(v[i++]); - result.M1_25 = new int[25]; - for (int j = 0; j < 25; j++) result.M1_25[j] = Convert.ToInt32(v[i++]); - result.S1_25 = new int[25]; - for (int j = 0; j < 25; j++) result.S1_25[j] = Convert.ToInt32(v[i++]); - result.Utot = Convert.ToInt32(v[i++]); - result.U1_25 = new int[25]; - for (int j = 0; j < 25; j++) result.U1_25[j] = Convert.ToInt32(v[i++]); - result.C1_5 = new int[5]; - for (int j = 0; j < 5; j++) result.C1_5[j] = Convert.ToInt32(v[i++]); - result.CA1_5 = new int[5]; - for (int j = 0; j < 5; j++) result.CA1_5[j] = Convert.ToInt32(v[i++]); - result.CD1_5 = new int[5]; - for (int j = 0; j < 5; j++) result.CD1_5[j] = Convert.ToInt32(v[i++]); + result.Mtot = IntOrZero(v[i++]); + result.Difficulties[eDifficulty.NORMAL].M1_25 = new string[25]; // NOTE: the game will accept up to 25, + // but only the first 10 are present in the mpq provided table + // TODO: add check to see if the other 11-25 are present instead of only taking the first 10 + for (int j = 0; j < 10; j++) result.Difficulties[eDifficulty.NORMAL].M1_25[j] = v[i++]; + + result.RangedSpawnPreference = (v[i++] == "1"); + result.Difficulties[eDifficulty.NIGHTMARE].M1_25 = new string[25]; + result.Difficulties[eDifficulty.HELL].M1_25 = new string[25]; + // See above TODO + for (int j = 0; j < 10; j++) + { + result.Difficulties[eDifficulty.NIGHTMARE].M1_25[j] = v[i]; // note: currently these are the same + result.Difficulties[eDifficulty.HELL].M1_25[j] = v[i++]; + } + result.U1_25 = new string[25]; + // See above TODO + for (int j = 0; j < 10; j++) result.U1_25[j] = v[i++]; + result.C1_4 = new string[4]; + for (int j = 0; j < 4; j++) result.C1_4[j] = v[i++]; + result.CA1_4 = new int[4]; + for (int j = 0; j < 4; j++) result.CA1_4[j] = IntOrZero(v[i++]); + result.CD1_4 = new int[4]; + for (int j = 0; j < 4; j++) result.CD1_4[j] = IntOrZero(v[i++]); result.Themes = Convert.ToInt32(v[i++]); result.SoundEnv = Convert.ToInt32(v[i++]); result.Waypoint = Convert.ToInt32(v[i++]); diff --git a/OpenDiablo2.Common/Models/MPQ.cs b/OpenDiablo2.Common/Models/MPQ.cs index 41145d97..821a7cf4 100644 --- a/OpenDiablo2.Common/Models/MPQ.cs +++ b/OpenDiablo2.Common/Models/MPQ.cs @@ -53,7 +53,9 @@ namespace OpenDiablo2.Common.Models KeyAdjusted = 0x00020000, // The file's encryption key is adjusted by the block offset and file size (explained in detail in the File Data section). File must be encrypted. IsEncrypted = 0x00010000, // File is encrypted. IsCompressed = 0x00000200, // File is compressed. File cannot be imploded. - IsImploded = 0x00000100 // File is imploded. File cannot be compressed. + IsImploded = 0x00000100, // File is imploded. File cannot be compressed. + IsPatchFile = 0x00100000, // Marks as a mpq patch file + IsDeleteFile = 0x02000000, // Marks as a delete request } internal struct BlockRecord @@ -71,6 +73,8 @@ namespace OpenDiablo2.Common.Models public bool IsEncrypted => (Flags & (UInt32)eBlockRecordBitflag.IsEncrypted) != 0; public bool IsCompressed => (Flags & (UInt32)eBlockRecordBitflag.IsCompressed) != 0; public bool IsImploded => (Flags & (UInt32)eBlockRecordBitflag.IsImploded) != 0; + public bool IsPatchFile => (Flags & (UInt32)eBlockRecordBitflag.IsPatchFile) != 0; + public bool IsDeleteFile => (Flags & (UInt32)eBlockRecordBitflag.IsDeleteFile) != 0; } internal struct HashRecord @@ -94,9 +98,15 @@ namespace OpenDiablo2.Common.Models public string Path { get; private set; } public eMPQFormatVersion FormatVersion => (eMPQFormatVersion)Header.FormatVersion; public List Files => GetFilePaths(); + private List FilesOverride = null; private List GetFilePaths() { + if(FilesOverride != null) + { + return FilesOverride; // if we were passed in an existing listfile, + // don't query again; use the one we were given instead + } using (var stream = OpenFile(LISTFILE_NAME)) { if (stream == null) @@ -116,9 +126,10 @@ namespace OpenDiablo2.Common.Models InitializeCryptTable(); } - public MPQ(string path) + public MPQ(string path, List listfile = null) { this.Path = path; + this.FilesOverride = listfile; // If you crash here, you may have Diablo2 open... can't do that :) fileStream = new FileStream(path, FileMode.Open); @@ -369,6 +380,11 @@ namespace OpenDiablo2.Common.Models return new MPQStream(this, block); } + public bool HasFile(string filename) + { + return GetHashRecord(filename, out HashRecord hash); + } + public void Dispose() { fileStream?.Dispose(); diff --git a/OpenDiablo2.Common/Models/MPQStream.cs b/OpenDiablo2.Common/Models/MPQStream.cs index 7594546e..b499d5a7 100644 --- a/OpenDiablo2.Common/Models/MPQStream.cs +++ b/OpenDiablo2.Common/Models/MPQStream.cs @@ -27,7 +27,7 @@ namespace OpenDiablo2.Common.Models this.blockRecord = blockRecord; this.blockSize = 0x200 << mpq.Header.BlockSize; - if (blockRecord.IsCompressed && !blockRecord.SingleUnit) + if ((blockRecord.IsCompressed || blockRecord.IsImploded) && !blockRecord.SingleUnit) LoadBlockOffsets(); } @@ -187,7 +187,7 @@ namespace OpenDiablo2.Common.Models int toread; uint encryptionseed; - if (blockRecord.IsCompressed) + if (blockRecord.IsCompressed || blockRecord.IsImploded) { offset = blockPositions[blockIndex]; toread = (int)(blockPositions[blockIndex + 1] - offset); @@ -226,6 +226,11 @@ namespace OpenDiablo2.Common.Models data = PKDecompress(new MemoryStream(data), expectedLength); } + if (blockRecord.IsImploded && (toread != expectedLength)) + { + data = PKDecompress(new MemoryStream(data), expectedLength); + } + return data; } diff --git a/OpenDiablo2.Common/Models/Mobs/HeroTypeConfig.cs b/OpenDiablo2.Common/Models/Mobs/HeroTypeConfig.cs index 69532a62..1ce6dc63 100644 --- a/OpenDiablo2.Common/Models/Mobs/HeroTypeConfig.cs +++ b/OpenDiablo2.Common/Models/Mobs/HeroTypeConfig.cs @@ -26,6 +26,8 @@ namespace OpenDiablo2.Common.Models.Mobs public double PerVitalityStamina { get; protected set; } public double PerEnergyMana { get; protected set; } + public int PerLevelStatPoints { get; protected set; } + public int BaseAttackRating { get; protected set; } public int PerDexterityAttackRating { get; protected set; } @@ -43,7 +45,15 @@ namespace OpenDiablo2.Common.Models.Mobs public int GetHitFrames { get; protected set; } public int BowFrames { get; protected set; } - public int StartingSkill { get; protected set; } + public string StartingSkill { get; protected set; } + public string[] StartingSkills { get; protected set; } + + public string AllSkillsBonusString { get; protected set; } + public string FirstTabBonusString { get; protected set; } + public string SecondTabBonusString { get; protected set; } + public string ThirdTabBonusString { get; protected set; } + public string ClassOnlyBonusString { get; protected set; } + public string BaseWeaponClass { get; protected set; } public List InitialEquipment { get; } @@ -52,10 +62,13 @@ namespace OpenDiablo2.Common.Models.Mobs int health, int mana, int stamina, int manaRegen, double perlevelhealth, double perlevelmana, double perlevelstamina, double pervitalityhealth, double pervitalitystamina, double perenergymana, + int perLevelStatPoints, int baseatkrating, int basedefrating, int perdexterityatkrating, int perdexteritydefrating, int walkVelocity, int runVelocity, int runDrain, int walkFrames, int runFrames, int swingFrames, int spellFrames, int getHitFrames, int bowFrames, - int startingSkill, + string startingSkill, string[] startingSkills, + string allSkillsBonusString, string firstTabBonusString, string secondTabBonusString, + string thirdTabBonusString, string classOnlyBonusString, string baseWeaponClass, List initialEquipment) { @@ -76,6 +89,8 @@ namespace OpenDiablo2.Common.Models.Mobs PerVitalityStamina = pervitalitystamina; PerEnergyMana = perenergymana; + PerLevelStatPoints = perLevelStatPoints; + BaseAttackRating = baseatkrating; BaseDefenseRating = basedefrating; PerDexterityAttackRating = perdexterityatkrating; @@ -93,6 +108,13 @@ namespace OpenDiablo2.Common.Models.Mobs BowFrames = bowFrames; StartingSkill = startingSkill; + StartingSkills = startingSkills; + AllSkillsBonusString = allSkillsBonusString; + FirstTabBonusString = firstTabBonusString; + SecondTabBonusString = secondTabBonusString; + ThirdTabBonusString = thirdTabBonusString; + ClassOnlyBonusString = classOnlyBonusString; + BaseWeaponClass = baseWeaponClass; InitialEquipment = initialEquipment; @@ -122,29 +144,35 @@ namespace OpenDiablo2.Common.Models.Mobs // RunDrain Comment LifePerLevel StaminaPerLevel ManaPerLevel // 21 22 23 // LifePerVitality StaminaPerVitality ManaPerMagic - // 24 25 26 27 28 29 + // 24 + // StatPerLevel + // 25 26 27 28 29 30 // #walk #run #swing #spell #gethit #bow - // 30 31 32 - // BlockFactor StartSkill baseWClass - // 33 34 35 36 37 38 + // 31 32 + // BlockFactor StartSkill + // 33 ... 42 + // Skill1 ... Skill10 + // 43 44 45 46 47 + // StrAllSkills StrSkillTab1 StrSkillTab2 StrSkillTab3 StrClassOnly + // 48 + // baseWClass + // 49 50 51 49+3 50+3 51+3 // item1 item1loc item1count item2 item2loc item2count - // 39 40 41 42 43 44 // item3 item3loc item3count item4 item4loc item4count - // 45 46 47 48 49 50 // item5 item5loc item5count item6 item6loc item6count - // 51 52 53 54 55 56 // item7 item7loc item7count item8 item8loc item8count - // 57 58 59 60 61 62 // item9 item9loc item9count item10 item10loc item10count + // itemn = 49 + (n-1)*3, itemnloc = 50 + (n-1)*3, itemncount = 51 + (n-1)*3 - + // NEW TODO: StatPerLevel Skill1 ... Skill10 + // StrAllSkills StrSkillTab1 StrSkillTab2 StrSkillTab3 StrClassOnly List itemNames = new List(); List itemLocs = new List(); List itemCounts = new List(); List initialEquipment = new List(); - for(int i = 33; i <= 60; i+=3) + for(int i = 49; i <= 78; i+=3) { var item = new InitialEquipment(); item.name = row[i]; @@ -169,21 +197,31 @@ namespace OpenDiablo2.Common.Models.Mobs pervitalityhealth: Convert.ToInt32(row[21]) / 4.0, pervitalitystamina: Convert.ToInt32(row[22]) / 4.0, perenergymana: Convert.ToInt32(row[23]) / 4.0, + perLevelStatPoints: Convert.ToInt32(row[24]), baseatkrating: Convert.ToInt32(row[13]), - basedefrating: Convert.ToInt32(row[30]), + basedefrating: Convert.ToInt32(row[31]), perdexterityatkrating: 5, perdexteritydefrating: 4, walkVelocity: Convert.ToInt32(row[14]), runVelocity: Convert.ToInt32(row[15]), runDrain: Convert.ToInt32(row[16]), - walkFrames: Convert.ToInt32(row[24]), - runFrames: Convert.ToInt32(row[25]), - swingFrames: Convert.ToInt32(row[26]), - spellFrames: Convert.ToInt32(row[27]), - getHitFrames: Convert.ToInt32(row[28]), - bowFrames: Convert.ToInt32(row[29]), - startingSkill: Convert.ToInt32(row[31]), - baseWeaponClass: row[32], + walkFrames: Convert.ToInt32(row[25]), + runFrames: Convert.ToInt32(row[26]), + swingFrames: Convert.ToInt32(row[27]), + spellFrames: Convert.ToInt32(row[28]), + getHitFrames: Convert.ToInt32(row[29]), + bowFrames: Convert.ToInt32(row[30]), + startingSkill: row[32], + startingSkills: new string[] + { + row[33], row[34], row[35], row[36], row[37], row[38], row[39], row[40], row[41], row[42] + }, + allSkillsBonusString: row[43], + firstTabBonusString: row[44], + secondTabBonusString: row[45], + thirdTabBonusString: row[46], + classOnlyBonusString: row[47], + baseWeaponClass: row[48], initialEquipment: initialEquipment); } } diff --git a/OpenDiablo2.Common/Models/Mobs/MissileTypeConfig.cs b/OpenDiablo2.Common/Models/Mobs/MissileTypeConfig.cs new file mode 100644 index 00000000..a4b0986e --- /dev/null +++ b/OpenDiablo2.Common/Models/Mobs/MissileTypeConfig.cs @@ -0,0 +1,131 @@ +using OpenDiablo2.Common.Interfaces.Mobs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenDiablo2.Common.Models.Mobs +{ + public class MissileTypeConfig : IMissileTypeConfig + { + public string Name { get; private set; } + public int Id { get; private set; } + + public int BaseVelocity { get; private set; } + public int MaxVelocity { get; private set; } + public int Acceleration { get; private set; } + public int Range { get; private set; } + public int ExtraRangePerLevel { get; private set; } + + public int LightRadius { get; private set; } + public bool LightFlicker { get; private set; } + public byte[] LightColor { get; private set; } // rgb + + public int FramesBeforeVisible { get; private set; } // how many anim frames before it becomes visible + public int FramesBeforeActive { get; private set; } // anim frames before it can do anything, e.g. collide + public bool LoopAnimation { get; private set; } // true: repeat animation until missile hits range or is otherwise destroyed + // false: repeat animation once, then vanish (WARNING: only becomes invisible, not deleted) + public string CelFilePath { get; private set; } + public int AnimationRate { get; private set; } // does not seem to be used + public int AnimationLength { get; private set; } // length of animation for one direction + public int AnimationSpeed { get; private set; } // actually used, frames per second + public int StartingFrame { get; private set; } // 'randstart' is a misnomer, actually just starts at this frame + public bool AnimationHasSubLoop { get; private set; } // if true, will repeat in a certain range of frames + //instead of repeating the whole animation again + public int AnimationSubLoopStart { get; private set; } // what frame to start at if it has a subloop + public int AnimationSubLoopEnd { get; private set; } // what frame to end at in subloop + // when it hits this, goes back to subloop start or goes to end if missile is set to die (out of range) + + public MissileTypeConfig(string Name, int Id, + int BaseVelocity, int MaxVelocity, int Acceleration, int Range, int ExtraRangePerLevel, + int LightRadius, bool LightFlicker, byte[] LightColor, + int FramesBeforeVisible, int FramesBeforeActive, bool LoopAnimation, string CelFilePath, + int AnimationRate, int AnimationLength, int AnimationSpeed, int StartingFrame, + bool AnimationHasSubLoop, int AnimationSubLoopStart, int AnimationSubLoopEnd) + { + this.Name = Name; + this.Id = Id; + + this.BaseVelocity = BaseVelocity; + this.MaxVelocity = MaxVelocity; + this.Acceleration = Acceleration; + this.Range = Range; + this.ExtraRangePerLevel = ExtraRangePerLevel; + + this.LightRadius = LightRadius; + this.LightFlicker = LightFlicker; + this.LightColor = LightColor; + + this.FramesBeforeVisible = FramesBeforeVisible; + this.FramesBeforeActive = FramesBeforeActive; + this.LoopAnimation = LoopAnimation; + this.CelFilePath = CelFilePath; + this.AnimationRate = AnimationRate; + this.AnimationLength = AnimationLength; + this.AnimationSpeed = AnimationSpeed; + this.StartingFrame = StartingFrame; + this.AnimationHasSubLoop = AnimationHasSubLoop; + this.AnimationSubLoopStart = AnimationSubLoopStart; + this.AnimationSubLoopEnd = AnimationSubLoopEnd; + } + } + + public static class MissileTypeConfigHelper + { + //Missile Id Vel MaxVel Accel Range LevRange + //0 1 2 3 4 5 6 + //Light Flicker Red Green Blue + //7 8 9 10 11 + //InitSteps Activate + //12 13 + //LoopAnim CelFile AnimLen RandStart SubLoop SubStart SubStop + //14 15 16 17 18 19 20 + //CollideType CollideKill CollideFriend LastCollide Collision + //21 22 23 24 25 + //ClientSend NextHit NextDelay Size CanDestroy ToHit AlwaysExplode Explosion + //26 27 28 29 30 31 32 33 + //NeverDup ReturnFire GetHit KnockBack Trans Qty Pierce + //34 35 36 37 38 39 40 + //Param1 Param1 Comment Param2 Param2 Comment SpecialSetup + //41 42 43 44 45 + //Open Beta Skill HitShift SrcDamage MinDamage MaxDamage LevDamage + //46 47 48 49 50 51 52 53 + //EType EMin Emax ELevel ELen ELevelLen HitClass NumDirections LocalBlood + //54 55 56 57 58 59 60 61 62 + + + public static IMissileTypeConfig ToMissileTypeConfig(this string[] row) + { + return new MissileTypeConfig( + Name: row[0], + Id: Convert.ToInt32(row[1]), + + BaseVelocity: Convert.ToInt32(row[2]), + MaxVelocity: Convert.ToInt32(row[3]), + Acceleration: Convert.ToInt32(row[4]), + Range: Convert.ToInt32(row[5]), + ExtraRangePerLevel: Convert.ToInt32(row[6]), + + LightRadius: Convert.ToInt32(row[7]), + LightFlicker: (row[8] == "1"), + LightColor: new byte[] {Convert.ToByte(row[9]), Convert.ToByte(row[10]), Convert.ToByte(row[11])}, + + FramesBeforeVisible: Convert.ToInt32(row[12]), + FramesBeforeActive: Convert.ToInt32(row[13]), + LoopAnimation: (row[14] == "1"), + CelFilePath: row[15], + + // TODO: these rows are wrong! research why our missiles.txt has different columns thatn the one in the guide??? + // TODO: UNFINISHED + AnimationRate: Convert.ToInt32(row[16]), + AnimationLength: Convert.ToInt32(row[17]), + AnimationSpeed: Convert.ToInt32(row[18]), + StartingFrame: Convert.ToInt32(row[19]), + AnimationHasSubLoop: (row[20] == "1"), + AnimationSubLoopStart: Convert.ToInt32(row[16]), + AnimationSubLoopEnd: Convert.ToInt32(row[16]) + ); + } + } +} diff --git a/OpenDiablo2.Common/Models/Mobs/PlayerEquipment.cs b/OpenDiablo2.Common/Models/Mobs/PlayerEquipment.cs index 40dea69b..b9f0d67b 100644 --- a/OpenDiablo2.Common/Models/Mobs/PlayerEquipment.cs +++ b/OpenDiablo2.Common/Models/Mobs/PlayerEquipment.cs @@ -35,15 +35,6 @@ namespace OpenDiablo2.Common.Models.Mobs } } - public eArmorType ArmorType - { - get - { - // TODO: Make things happen here - return eArmorType.Lite; - } - } - public string HashKey => $"{Head?.Item.Name}{Neck?.Item.Name}{Torso?.Item.Name}{RightArm?.Item.Name}{LeftArm?.Item.Name}" + $"{RightRing?.Item.Name}{LeftRing?.Item.Name}{Feet?.Item.Name}{Belt?.Item.Name}{Gloves?.Item.Name}"; diff --git a/OpenDiablo2.Common/OpenDiablo2.Common.csproj b/OpenDiablo2.Common/OpenDiablo2.Common.csproj index dd85d7d6..e676f4c5 100644 --- a/OpenDiablo2.Common/OpenDiablo2.Common.csproj +++ b/OpenDiablo2.Common/OpenDiablo2.Common.csproj @@ -60,6 +60,7 @@ + @@ -80,6 +81,7 @@ + @@ -104,6 +106,7 @@ + @@ -117,6 +120,7 @@ + diff --git a/OpenDiablo2.Common/ResourcePaths.cs b/OpenDiablo2.Common/ResourcePaths.cs index c0235391..d68c95f2 100644 --- a/OpenDiablo2.Common/ResourcePaths.cs +++ b/OpenDiablo2.Common/ResourcePaths.cs @@ -1,346 +1,383 @@ -/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using OpenDiablo2.Common.Enums; -using System; - -namespace OpenDiablo2.Common -{ - public static class ResourcePaths - { - // --- Loading Screen --- - public const string LoadingScreen = @"data\global\ui\Loading\loadingscreen.dc6"; - - // --- Main Menu --- - public const string GameSelectScreen = @"data\global\ui\FrontEnd\gameselectscreenEXP.dc6"; - public const string Diablo2LogoFireLeft = @"data\global\ui\FrontEnd\D2logoFireLeft.DC6"; - public const string Diablo2LogoFireRight = @"data\global\ui\FrontEnd\D2logoFireRight.DC6"; - public const string Diablo2LogoBlackLeft = @"data\global\ui\FrontEnd\D2logoBlackLeft.DC6"; - public const string Diablo2LogoBlackRight = @"data\global\ui\FrontEnd\D2logoBlackRight.DC6"; - - // --- Credits --- - public const string CreditsBackground = @"data\global\ui\CharSelect\creditsbckgexpand.dc6"; - public const string CreditsText = @"data\local\ui\eng\ExpansionCredits.txt"; - - // --- Character Select Screen --- - public const string CharacterSelectBackground = @"data\global\ui\FrontEnd\charactercreationscreenEXP.dc6"; - public const string CharacterSelectCampfire = @"data\global\ui\FrontEnd\fire.DC6"; - - public const string CharacterSelectBarbarianUnselected = @"data\global\ui\FrontEnd\barbarian\banu1.DC6"; - public const string CharacterSelectBarbarianUnselectedH = @"data\global\ui\FrontEnd\barbarian\banu2.DC6"; - public const string CharacterSelectBarbarianSelected = @"data\global\ui\FrontEnd\barbarian\banu3.DC6"; - public const string CharacterSelectBarbarianForwardWalk = @"data\global\ui\FrontEnd\barbarian\bafw.DC6"; - public const string CharacterSelectBarbarianForwardWalkOverlay = @"data\global\ui\FrontEnd\barbarian\BAFWs.DC6"; - public const string CharacterSelectBarbarianBackWalk = @"data\global\ui\FrontEnd\barbarian\babw.DC6"; - - public const string CharacterSelecSorceressUnselected = @"data\global\ui\FrontEnd\sorceress\SONU1.DC6"; - public const string CharacterSelecSorceressUnselectedH = @"data\global\ui\FrontEnd\sorceress\SONU2.DC6"; - public const string CharacterSelecSorceressSelected = @"data\global\ui\FrontEnd\sorceress\SONU3.DC6"; - public const string CharacterSelecSorceressSelectedOverlay = @"data\global\ui\FrontEnd\sorceress\SONU3s.DC6"; - public const string CharacterSelecSorceressForwardWalk = @"data\global\ui\FrontEnd\sorceress\SOFW.DC6"; - public const string CharacterSelecSorceressForwardWalkOverlay = @"data\global\ui\FrontEnd\sorceress\SOFWs.DC6"; - public const string CharacterSelecSorceressBackWalk = @"data\global\ui\FrontEnd\sorceress\SOBW.DC6"; - public const string CharacterSelecSorceressBackWalkOverlay = @"data\global\ui\FrontEnd\sorceress\SOBWs.DC6"; - - public const string CharacterSelectNecromancerUnselected = @"data\global\ui\FrontEnd\necromancer\NENU1.DC6"; - public const string CharacterSelectNecromancerUnselectedH = @"data\global\ui\FrontEnd\necromancer\NENU2.DC6"; - public const string CharacterSelecNecromancerSelected = @"data\global\ui\FrontEnd\necromancer\NENU3.DC6"; - public const string CharacterSelecNecromancerSelectedOverlay = @"data\global\ui\FrontEnd\necromancer\NENU3s.DC6"; - public const string CharacterSelecNecromancerForwardWalk = @"data\global\ui\FrontEnd\necromancer\NEFW.DC6"; - public const string CharacterSelecNecromancerForwardWalkOverlay = @"data\global\ui\FrontEnd\necromancer\NEFWs.DC6"; - public const string CharacterSelecNecromancerBackWalk = @"data\global\ui\FrontEnd\necromancer\NEBW.DC6"; - public const string CharacterSelecNecromancerBackWalkOverlay = @"data\global\ui\FrontEnd\necromancer\NEBWs.DC6"; - - public const string CharacterSelectPaladinUnselected = @"data\global\ui\FrontEnd\paladin\PANU1.DC6"; - public const string CharacterSelectPaladinUnselectedH = @"data\global\ui\FrontEnd\paladin\PANU2.DC6"; - public const string CharacterSelecPaladinSelected = @"data\global\ui\FrontEnd\paladin\PANU3.DC6"; - public const string CharacterSelecPaladinForwardWalk = @"data\global\ui\FrontEnd\paladin\PAFW.DC6"; - public const string CharacterSelecPaladinForwardWalkOverlay = @"data\global\ui\FrontEnd\paladin\PAFWs.DC6"; - public const string CharacterSelecPaladinBackWalk = @"data\global\ui\FrontEnd\paladin\PABW.DC6"; - - - public const string CharacterSelectAmazonUnselected = @"data\global\ui\FrontEnd\amazon\AMNU1.DC6"; - public const string CharacterSelectAmazonUnselectedH = @"data\global\ui\FrontEnd\amazon\AMNU2.DC6"; - public const string CharacterSelecAmazonSelected = @"data\global\ui\FrontEnd\amazon\AMNU3.DC6"; - public const string CharacterSelecAmazonForwardWalk = @"data\global\ui\FrontEnd\amazon\AMFW.DC6"; - public const string CharacterSelecAmazonForwardWalkOverlay = @"data\global\ui\FrontEnd\amazon\AMFWs.DC6"; - public const string CharacterSelecAmazonBackWalk = @"data\global\ui\FrontEnd\amazon\AMBW.DC6"; - - public const string CharacterSelectAssassinUnselected = @"data\global\ui\FrontEnd\assassin\ASNU1.DC6"; - public const string CharacterSelectAssassinUnselectedH = @"data\global\ui\FrontEnd\assassin\ASNU2.DC6"; - public const string CharacterSelectAssassinSelected = @"data\global\ui\FrontEnd\assassin\ASNU3.DC6"; - public const string CharacterSelectAssassinForwardWalk = @"data\global\ui\FrontEnd\assassin\ASFW.DC6"; - public const string CharacterSelectAssassinBackWalk = @"data\global\ui\FrontEnd\assassin\ASBW.DC6"; - - public const string CharacterSelectDruidUnselected = @"data\global\ui\FrontEnd\druid\DZNU1.dc6"; - public const string CharacterSelectDruidUnselectedH = @"data\global\ui\FrontEnd\druid\DZNU2.dc6"; - public const string CharacterSelectDruidSelected = @"data\global\ui\FrontEnd\druid\DZNU3.DC6"; - public const string CharacterSelectDruidForwardWalk = @"data\global\ui\FrontEnd\druid\DZFW.DC6"; - public const string CharacterSelectDruidBackWalk = @"data\global\ui\FrontEnd\druid\DZBW.DC6"; - - // -- Character Selection - public const string CharacterSelectionBackground = @"data\global\ui\CharSelect\characterselectscreenEXP.dc6"; - - // --- Game --- - public const string GamePanels = @"data\global\ui\PANEL\800ctrlpnl7.dc6"; - public const string GameGlobeOverlap = @"data\global\ui\PANEL\overlap.DC6"; - public const string HealthMana = @"data\global\ui\PANEL\hlthmana.DC6"; - public const string GameSmallMenuButton = @"data\global\ui\PANEL\menubutton.DC6"; // TODO: Used for inventory popout - public const string SkillIcon = @"data\global\ui\PANEL\Skillicon.DC6"; // TODO: Used for skill icon button - public const string AddSkillButton = @"data\global\ui\PANEL\level.DC6"; - - // --- Mouse Pointers --- - public const string CursorDefault = @"data\global\ui\CURSOR\ohand.DC6"; - - // --- Fonts --- - public const string Font6 = @"data\local\font\latin\font6"; - public const string Font8 = @"data\local\font\latin\font8"; - public const string Font16 = @"data\local\font\latin\font16"; - public const string Font24 = @"data\local\font\latin\font24"; - public const string Font30 = @"data\local\font\latin\font30"; - public const string FontFormal12 = @"data\local\font\latin\fontformal12"; - public const string FontFormal11 = @"data\local\font\latin\fontformal11"; - public const string FontFormal10 = @"data\local\font\latin\fontformal10"; - public const string FontExocet10 = @"data\local\font\latin\fontexocet10"; - public const string FontExocet8 = @"data\local\font\latin\fontexocet8"; - - // --- UI --- - public const string WideButtonBlank = @"data\global\ui\FrontEnd\WideButtonBlank.dc6"; - public const string MediumButtonBlank = @"data\global\ui\FrontEnd\MediumButtonBlank.dc6"; - public const string CancelButton = @"data\global\ui\FrontEnd\CancelButtonBlank.dc6"; - public const string NarrowButtonBlank = @"data\global\ui\FrontEnd\NarrowButtonBlank.dc6"; - public const string ShortButtonBlank = @"data\global\ui\CharSelect\ShortButtonBlank.dc6"; - public const string TextBox2 = @"data\global\ui\FrontEnd\textbox2.dc6"; - public const string TallButtonBlank = @"data\global\ui\CharSelect\TallButtonBlank.dc6"; - - // --- GAME UI --- - public const string MinipanelSmall = @"data\global\ui\PANEL\minipanel_s.dc6"; - public const string MinipanelButton = @"data\global\ui\PANEL\minipanelbtn.DC6"; - - public const string Frame = @"data\global\ui\PANEL\800borderframe.dc6"; - public const string InventoryCharacterPanel = @"data\global\ui\PANEL\invchar6.DC6"; - public const string InventoryWeaponsTab = @"data\global\ui\PANEL\invchar6Tab.DC6"; - public const string SkillsPanelAmazon = @"data\global\ui\SPELLS\skltree_a_back.DC6"; - public const string SkillsPanelBarbarian = @"data\global\ui\SPELLS\skltree_b_back.DC6"; - public const string SkillsPanelDruid = @"data\global\ui\SPELLS\skltree_d_back.DC6"; - public const string SkillsPanelAssassin = @"data\global\ui\SPELLS\skltree_i_back.DC6"; - public const string SkillsPanelNecromancer = @"data\global\ui\SPELLS\skltree_n_back.DC6"; - public const string SkillsPanelPaladin = @"data\global\ui\SPELLS\skltree_p_back.DC6"; - public const string SkillsPanelSorcerer = @"data\global\ui\SPELLS\skltree_s_back.DC6"; - - public const string RunButton = @"data\global\ui\PANEL\runbutton.dc6"; - public const string MenuButton = @"data\global\ui\PANEL\menubutton.DC6"; - public const string GoldCoinButton = @"data\global\ui\panel\goldcoinbtn.dc6"; - public const string SquareButton = @"data\global\ui\panel\buysellbtn.dc6"; - - public const string ArmorPlaceholder = @"data\global\ui\PANEL\inv_armor.DC6"; - public const string BeltPlaceholder = @"data\global\ui\PANEL\inv_belt.DC6"; - public const string BootsPlaceholder = @"data\global\ui\PANEL\inv_boots.DC6"; - public const string HelmGlovePlaceholder = @"data\global\ui\PANEL\inv_helm_glove.DC6"; - public const string RingAmuletPlaceholder = @"data\global\ui\PANEL\inv_ring_amulet.DC6"; - public const string WeaponsPlaceholder = @"data\global\ui\PANEL\inv_weapons.DC6"; - - // --- Data --- - public const string EnglishTable = @"data\local\lng\eng\English.txt"; - public const string ExpansionStringTable = @"data\local\lng\eng\expansionstring.tbl"; - public const string LevelPreset = @"data\global\excel\LvlPrest.txt"; - public const string LevelType = @"data\global\excel\LvlTypes.txt"; - public const string LevelDetails = @"data\global\excel\Levels.txt"; - public const string ObjectDetails = @"data\global\excel\Objects.txt"; - - // --- Animations --- - public const string ObjectData = @"data\global\objects"; - public const string AnimationData = @"data\global\animdata.d2"; - public const string PlayerAnimationBase = @"data\global\CHARS"; - - // --- Inventory Data --- - public const string Weapons = @"data\global\excel\weapons.txt"; - public const string Armor = @"data\global\excel\armor.txt"; - public const string Misc = @"data\global\excel\misc.txt"; - - // --- Character Data --- - public const string Experience = @"data\global\excel\experience.txt"; - public const string CharStats = @"data\global\excel\charstats.txt"; - - // --- Music --- - public const string BGMTitle = @"data\global\music\introedit.wav"; - public const string BGMOptions = @"data\global\music\Common\options.wav"; - public const string BGMAct1AndarielAction = @"data\global\music\Act1\andarielaction.wav"; - public const string BGMAct1BloodRavenResolution = @"data\global\music\Act1\bloodravenresolution.wav"; - public const string BGMAct1Caves = @"data\global\music\Act1\caves.wav"; - public const string BGMAct1Crypt = @"data\global\music\Act1\crypt.wav"; - public const string BGMAct1DenOfEvilAction = @"data\global\music\Act1\denofevilaction.wav"; - public const string BGMAct1Monastery = @"data\global\music\Act1\monastery.wav"; - public const string BGMAct1Town1 = @"data\global\music\Act1\town1.wav"; - public const string BGMAct1Tristram = @"data\global\music\Act1\tristram.wav"; - public const string BGMAct1Wild = @"data\global\music\Act1\wild.wav"; - public const string BGMAct2Desert = @"data\global\music\Act2\desert.wav"; - public const string BGMAct2Harem = @"data\global\music\Act2\harem.wav"; - public const string BGMAct2HoradricAction = @"data\global\music\Act2\horadricaction.wav"; - public const string BGMAct2Lair = @"data\global\music\Act2\lair.wav"; - public const string BGMAct2RadamentResolution = @"data\global\music\Act2\radamentresolution.wav"; - public const string BGMAct2Sanctuary = @"data\global\music\Act2\sanctuary.wav"; - public const string BGMAct2Sewer = @"data\global\music\Act2\sewer.wav"; - public const string BGMAct2TaintedSunAction = @"data\global\music\Act2\taintedsunaction.wav"; - public const string BGMAct2Tombs = @"data\global\music\Act2\tombs.wav"; - public const string BGMAct2Town2 = @"data\global\music\Act2\town2.wav"; - public const string BGMAct2Valley = @"data\global\music\Act2\valley.wav"; - public const string BGMAct3Jungle = @"data\global\music\Act3\jungle.wav"; - public const string BGMAct3Kurast = @"data\global\music\Act3\kurast.wav"; - public const string BGMAct3KurastSewer = @"data\global\music\Act3\kurastsewer.wav"; - public const string BGMAct3MefDeathAction = @"data\global\music\Act3\mefdeathaction.wav"; - public const string BGMAct3OrbAction = @"data\global\music\Act3\orbaction.wav"; - public const string BGMAct3Spider = @"data\global\music\Act3\spider.wav"; - public const string BGMAct3Town3 = @"data\global\music\Act3\town3.wav"; - public const string BGMAct4Diablo = @"data\global\music\Act4\diablo.wav"; - public const string BGMAct4DiabloAction = @"data\global\music\Act4\diabloaction.wav"; - public const string BGMAct4ForgeAction = @"data\global\music\Act4\forgeaction.wav"; - public const string BGMAct4IzualAction = @"data\global\music\Act4\izualaction.wav"; - public const string BGMAct4Mesa = @"data\global\music\Act4\mesa.wav"; - public const string BGMAct4Town4 = @"data\global\music\Act4\town4.wav"; - public const string BGMAct5Baal = @"data\global\music\Act5\baal.wav"; - public const string BGMAct5XTown = @"data\global\music\Act5\xtown.wav"; - - - // --- Sound Effects --- - public const string SFXButtonClick = @"data\global\sfx\Cursor\button.wav"; - public const string SFXAmazonDeselect = @"data\global\sfx\Cursor\intro\amazon deselect.wav"; - public const string SFXAmazonSelect = @"data\global\sfx\Cursor\intro\amazon select.wav"; - public const string SFXAssassinDeselect = @"data\global\sfx\Cursor\intro\assassin deselect.wav"; - public const string SFXAssassinSelect = @"data\global\sfx\Cursor\intro\assassin select.wav"; - public const string SFXBarbarianDeselect = @"data\global\sfx\Cursor\intro\barbarian deselect.wav"; - public const string SFXBarbarianSelect = @"data\global\sfx\Cursor\intro\barbarian select.wav"; - public const string SFXDruidDeselect = @"data\global\sfx\Cursor\intro\druid deselect.wav"; - public const string SFXDruidSelect = @"data\global\sfx\Cursor\intro\druid select.wav"; - public const string SFXNecromancerDeselect = @"data\global\sfx\Cursor\intro\necromancer deselect.wav"; - public const string SFXNecromancerSelect = @"data\global\sfx\Cursor\intro\necromancer select.wav"; - public const string SFXPaladinDeselect = @"data\global\sfx\Cursor\intro\paladin deselect.wav"; - public const string SFXPaladinSelect = @"data\global\sfx\Cursor\intro\paladin select.wav"; - public const string SFXSorceressDeselect = @"data\global\sfx\Cursor\intro\sorceress deselect.wav"; - public const string SFXSorceressSelect = @"data\global\sfx\Cursor\intro\sorceress select.wav"; - - // --- Enemy Data --- - public static string MonStats = "data\\global\\excel\\monstats.txt"; - - public static string GeneratePathForItem(string spriteName) - { - return $@"data\global\items\{spriteName}.dc6"; - } - - public static string GetMusicPathForLevel(eLevelId levelId) - { - switch (levelId) - { - case eLevelId.None: - return string.Empty; - case eLevelId.Act1_Town1: - return BGMAct1Town1; - case eLevelId.Act1_CaveTreasure2: - return BGMAct1Caves; - case eLevelId.Act1_CaveTreasure3: - return BGMAct1Caves; - case eLevelId.Act1_CaveTreasure4: - return BGMAct1Caves; - case eLevelId.Act1_CaveTreasure5: - return BGMAct1Caves; - case eLevelId.Act1_CryptCountessX: - return BGMAct1BloodRavenResolution; // TODO: Verify - case eLevelId.Act1_Tower2: - return BGMAct1Caves; // TODO: Verify - case eLevelId.Act1_MonFront: - return BGMAct1DenOfEvilAction; // TODO: Verify - case eLevelId.Act1_Courtyard1: - return BGMAct1Monastery; // TODO: Verify - case eLevelId.Act1_Courtyard2: - return BGMAct1Monastery; // TODO: Verify - case eLevelId.Act1_Cathedral: - return BGMAct1Monastery; // TODO: Verify - case eLevelId.Act1_Andariel: - return BGMAct1AndarielAction; - case eLevelId.Act1_Tristram: - return BGMAct1Tristram; - case eLevelId.Act2_Town: - return BGMAct2Town2; - case eLevelId.Act2_Harem: - return BGMAct2Harem; - case eLevelId.Act2_DurielsLair: - return BGMAct2Lair; - case eLevelId.Act3_Town: - return BGMAct3Town3; - case eLevelId.Act3_DungeonTreasure1: - return BGMAct3Kurast; // TODO: Verify - case eLevelId.Act3_DungeonTreasure2: - return BGMAct3Kurast; // TODO: Verify - case eLevelId.Act3_SewerTreasureX: - return BGMAct3KurastSewer; // TODO: Verify - case eLevelId.Act3_Temple1: - return BGMAct3Kurast; // TODO: Verify - case eLevelId.Act3_Temple2: - return BGMAct3Kurast; // TODO: Verify - case eLevelId.Act3_Temple3: - return BGMAct3Kurast; // TODO: Verify - case eLevelId.Act3_Temple4: - return BGMAct3Kurast; // TODO: Verify - case eLevelId.Act3_Temple5: - return BGMAct3Kurast; // TODO: Verify - case eLevelId.Act3_Temple6: - return BGMAct3Kurast; // TODO: Verify - case eLevelId.Act3_MephistoComplex: - return BGMAct3MefDeathAction; // TODO: Verify - case eLevelId.Act4_Fortress: - return BGMAct4Mesa; // TODO: Verify - case eLevelId.Act5_Town: - return BGMAct5XTown; - case eLevelId.Act5_TempleFinalRoom: - return BGMAct2Sanctuary; // TODO: Verify - case eLevelId.Act5_ThroneRoom: - return BGMAct2Sanctuary; // TODO: Verify - case eLevelId.Act5_WorldStone: - return BGMAct4ForgeAction; // TODO: Verify - case eLevelId.Act5_TempleEntrance: - return BGMAct5Baal; // TODO: Verify - case eLevelId.Act5_BaalEntrance: - return BGMAct5Baal; // TODO: Verify - default: - return string.Empty; - } - } - - public static string GetHeroSkillPanel(eHero hero) - { - switch (hero) - { - case eHero.Amazon: - return SkillsPanelAmazon; - case eHero.Assassin: - return SkillsPanelAssassin; - case eHero.Barbarian: - return SkillsPanelBarbarian; - case eHero.Druid: - return SkillsPanelDruid; - case eHero.Necromancer: - return SkillsPanelNecromancer; - case eHero.Paladin: - return SkillsPanelPaladin; - case eHero.Sorceress: - return SkillsPanelSorcerer; - default: - throw new ArgumentException($"Unknown hero type: {hero}"); - } - } - } -} - +/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C# + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using OpenDiablo2.Common.Enums; +using System; + +namespace OpenDiablo2.Common +{ + public static class ResourcePaths + { + // --- Loading Screen --- + public const string LoadingScreen = @"data\global\ui\Loading\loadingscreen.dc6"; + + // --- Main Menu --- + public const string GameSelectScreen = @"data\global\ui\FrontEnd\gameselectscreenEXP.dc6"; + public const string Diablo2LogoFireLeft = @"data\global\ui\FrontEnd\D2logoFireLeft.DC6"; + public const string Diablo2LogoFireRight = @"data\global\ui\FrontEnd\D2logoFireRight.DC6"; + public const string Diablo2LogoBlackLeft = @"data\global\ui\FrontEnd\D2logoBlackLeft.DC6"; + public const string Diablo2LogoBlackRight = @"data\global\ui\FrontEnd\D2logoBlackRight.DC6"; + + // --- Credits --- + public const string CreditsBackground = @"data\global\ui\CharSelect\creditsbckgexpand.dc6"; + public const string CreditsText = @"data\local\ui\eng\ExpansionCredits.txt"; + + // --- Character Select Screen --- + public const string CharacterSelectBackground = @"data\global\ui\FrontEnd\charactercreationscreenEXP.dc6"; + public const string CharacterSelectCampfire = @"data\global\ui\FrontEnd\fire.DC6"; + + public const string CharacterSelectBarbarianUnselected = @"data\global\ui\FrontEnd\barbarian\banu1.DC6"; + public const string CharacterSelectBarbarianUnselectedH = @"data\global\ui\FrontEnd\barbarian\banu2.DC6"; + public const string CharacterSelectBarbarianSelected = @"data\global\ui\FrontEnd\barbarian\banu3.DC6"; + public const string CharacterSelectBarbarianForwardWalk = @"data\global\ui\FrontEnd\barbarian\bafw.DC6"; + public const string CharacterSelectBarbarianForwardWalkOverlay = @"data\global\ui\FrontEnd\barbarian\BAFWs.DC6"; + public const string CharacterSelectBarbarianBackWalk = @"data\global\ui\FrontEnd\barbarian\babw.DC6"; + + public const string CharacterSelecSorceressUnselected = @"data\global\ui\FrontEnd\sorceress\SONU1.DC6"; + public const string CharacterSelecSorceressUnselectedH = @"data\global\ui\FrontEnd\sorceress\SONU2.DC6"; + public const string CharacterSelecSorceressSelected = @"data\global\ui\FrontEnd\sorceress\SONU3.DC6"; + public const string CharacterSelecSorceressSelectedOverlay = @"data\global\ui\FrontEnd\sorceress\SONU3s.DC6"; + public const string CharacterSelecSorceressForwardWalk = @"data\global\ui\FrontEnd\sorceress\SOFW.DC6"; + public const string CharacterSelecSorceressForwardWalkOverlay = @"data\global\ui\FrontEnd\sorceress\SOFWs.DC6"; + public const string CharacterSelecSorceressBackWalk = @"data\global\ui\FrontEnd\sorceress\SOBW.DC6"; + public const string CharacterSelecSorceressBackWalkOverlay = @"data\global\ui\FrontEnd\sorceress\SOBWs.DC6"; + + public const string CharacterSelectNecromancerUnselected = @"data\global\ui\FrontEnd\necromancer\NENU1.DC6"; + public const string CharacterSelectNecromancerUnselectedH = @"data\global\ui\FrontEnd\necromancer\NENU2.DC6"; + public const string CharacterSelecNecromancerSelected = @"data\global\ui\FrontEnd\necromancer\NENU3.DC6"; + public const string CharacterSelecNecromancerSelectedOverlay = @"data\global\ui\FrontEnd\necromancer\NENU3s.DC6"; + public const string CharacterSelecNecromancerForwardWalk = @"data\global\ui\FrontEnd\necromancer\NEFW.DC6"; + public const string CharacterSelecNecromancerForwardWalkOverlay = @"data\global\ui\FrontEnd\necromancer\NEFWs.DC6"; + public const string CharacterSelecNecromancerBackWalk = @"data\global\ui\FrontEnd\necromancer\NEBW.DC6"; + public const string CharacterSelecNecromancerBackWalkOverlay = @"data\global\ui\FrontEnd\necromancer\NEBWs.DC6"; + + public const string CharacterSelectPaladinUnselected = @"data\global\ui\FrontEnd\paladin\PANU1.DC6"; + public const string CharacterSelectPaladinUnselectedH = @"data\global\ui\FrontEnd\paladin\PANU2.DC6"; + public const string CharacterSelecPaladinSelected = @"data\global\ui\FrontEnd\paladin\PANU3.DC6"; + public const string CharacterSelecPaladinForwardWalk = @"data\global\ui\FrontEnd\paladin\PAFW.DC6"; + public const string CharacterSelecPaladinForwardWalkOverlay = @"data\global\ui\FrontEnd\paladin\PAFWs.DC6"; + public const string CharacterSelecPaladinBackWalk = @"data\global\ui\FrontEnd\paladin\PABW.DC6"; + + + public const string CharacterSelectAmazonUnselected = @"data\global\ui\FrontEnd\amazon\AMNU1.DC6"; + public const string CharacterSelectAmazonUnselectedH = @"data\global\ui\FrontEnd\amazon\AMNU2.DC6"; + public const string CharacterSelecAmazonSelected = @"data\global\ui\FrontEnd\amazon\AMNU3.DC6"; + public const string CharacterSelecAmazonForwardWalk = @"data\global\ui\FrontEnd\amazon\AMFW.DC6"; + public const string CharacterSelecAmazonForwardWalkOverlay = @"data\global\ui\FrontEnd\amazon\AMFWs.DC6"; + public const string CharacterSelecAmazonBackWalk = @"data\global\ui\FrontEnd\amazon\AMBW.DC6"; + + public const string CharacterSelectAssassinUnselected = @"data\global\ui\FrontEnd\assassin\ASNU1.DC6"; + public const string CharacterSelectAssassinUnselectedH = @"data\global\ui\FrontEnd\assassin\ASNU2.DC6"; + public const string CharacterSelectAssassinSelected = @"data\global\ui\FrontEnd\assassin\ASNU3.DC6"; + public const string CharacterSelectAssassinForwardWalk = @"data\global\ui\FrontEnd\assassin\ASFW.DC6"; + public const string CharacterSelectAssassinBackWalk = @"data\global\ui\FrontEnd\assassin\ASBW.DC6"; + + public const string CharacterSelectDruidUnselected = @"data\global\ui\FrontEnd\druid\DZNU1.dc6"; + public const string CharacterSelectDruidUnselectedH = @"data\global\ui\FrontEnd\druid\DZNU2.dc6"; + public const string CharacterSelectDruidSelected = @"data\global\ui\FrontEnd\druid\DZNU3.DC6"; + public const string CharacterSelectDruidForwardWalk = @"data\global\ui\FrontEnd\druid\DZFW.DC6"; + public const string CharacterSelectDruidBackWalk = @"data\global\ui\FrontEnd\druid\DZBW.DC6"; + + // -- Character Selection + public const string CharacterSelectionBackground = @"data\global\ui\CharSelect\characterselectscreenEXP.dc6"; + + // --- Game --- + public const string GamePanels = @"data\global\ui\PANEL\800ctrlpnl7.dc6"; + public const string GameGlobeOverlap = @"data\global\ui\PANEL\overlap.DC6"; + public const string HealthMana = @"data\global\ui\PANEL\hlthmana.DC6"; + public const string GameSmallMenuButton = @"data\global\ui\PANEL\menubutton.DC6"; // TODO: Used for inventory popout + public const string SkillIcon = @"data\global\ui\PANEL\Skillicon.DC6"; // TODO: Used for skill icon button + public const string AddSkillButton = @"data\global\ui\PANEL\level.DC6"; + + // --- Mouse Pointers --- + public const string CursorDefault = @"data\global\ui\CURSOR\ohand.DC6"; + + // --- Fonts --- + public const string Font6 = @"data\local\font\latin\font6"; + public const string Font8 = @"data\local\font\latin\font8"; + public const string Font16 = @"data\local\font\latin\font16"; + public const string Font24 = @"data\local\font\latin\font24"; + public const string Font30 = @"data\local\font\latin\font30"; + public const string FontFormal12 = @"data\local\font\latin\fontformal12"; + public const string FontFormal11 = @"data\local\font\latin\fontformal11"; + public const string FontFormal10 = @"data\local\font\latin\fontformal10"; + public const string FontExocet10 = @"data\local\font\latin\fontexocet10"; + public const string FontExocet8 = @"data\local\font\latin\fontexocet8"; + + // --- UI --- + public const string WideButtonBlank = @"data\global\ui\FrontEnd\WideButtonBlank.dc6"; + public const string MediumButtonBlank = @"data\global\ui\FrontEnd\MediumButtonBlank.dc6"; + public const string CancelButton = @"data\global\ui\FrontEnd\CancelButtonBlank.dc6"; + public const string NarrowButtonBlank = @"data\global\ui\FrontEnd\NarrowButtonBlank.dc6"; + public const string ShortButtonBlank = @"data\global\ui\CharSelect\ShortButtonBlank.dc6"; + public const string TextBox2 = @"data\global\ui\FrontEnd\textbox2.dc6"; + public const string TallButtonBlank = @"data\global\ui\CharSelect\TallButtonBlank.dc6"; + + // --- GAME UI --- + public const string MinipanelSmall = @"data\global\ui\PANEL\minipanel_s.dc6"; + public const string MinipanelButton = @"data\global\ui\PANEL\minipanelbtn.DC6"; + + public const string Frame = @"data\global\ui\PANEL\800borderframe.dc6"; + public const string InventoryCharacterPanel = @"data\global\ui\PANEL\invchar6.DC6"; + public const string InventoryWeaponsTab = @"data\global\ui\PANEL\invchar6Tab.DC6"; + public const string SkillsPanelAmazon = @"data\global\ui\SPELLS\skltree_a_back.DC6"; + public const string SkillsPanelBarbarian = @"data\global\ui\SPELLS\skltree_b_back.DC6"; + public const string SkillsPanelDruid = @"data\global\ui\SPELLS\skltree_d_back.DC6"; + public const string SkillsPanelAssassin = @"data\global\ui\SPELLS\skltree_i_back.DC6"; + public const string SkillsPanelNecromancer = @"data\global\ui\SPELLS\skltree_n_back.DC6"; + public const string SkillsPanelPaladin = @"data\global\ui\SPELLS\skltree_p_back.DC6"; + public const string SkillsPanelSorcerer = @"data\global\ui\SPELLS\skltree_s_back.DC6"; + + public const string GenericSkills = @"data\global\ui\SPELLS\Skillicon.DC6"; + public const string AmazonSkills = @"data\global\ui\SPELLS\AmSkillicon.DC6"; + public const string BarbarianSkills = @"data\global\ui\SPELLS\BaSkillicon.DC6"; + public const string DruidSkills = @"data\global\ui\SPELLS\DrSkillicon.DC6"; + public const string AssassinSkills = @"data\global\ui\SPELLS\AsSkillicon.DC6"; + public const string NecromancerSkills = @"data\global\ui\SPELLS\NeSkillicon.DC6"; + public const string PaladinSkills = @"data\global\ui\SPELLS\PaSkillicon.DC6"; + public const string SorcererSkills = @"data\global\ui\SPELLS\SoSkillicon.DC6"; + + public const string RunButton = @"data\global\ui\PANEL\runbutton.dc6"; + public const string MenuButton = @"data\global\ui\PANEL\menubutton.DC6"; + public const string GoldCoinButton = @"data\global\ui\panel\goldcoinbtn.dc6"; + public const string SquareButton = @"data\global\ui\panel\buysellbtn.dc6"; + + public const string ArmorPlaceholder = @"data\global\ui\PANEL\inv_armor.DC6"; + public const string BeltPlaceholder = @"data\global\ui\PANEL\inv_belt.DC6"; + public const string BootsPlaceholder = @"data\global\ui\PANEL\inv_boots.DC6"; + public const string HelmGlovePlaceholder = @"data\global\ui\PANEL\inv_helm_glove.DC6"; + public const string RingAmuletPlaceholder = @"data\global\ui\PANEL\inv_ring_amulet.DC6"; + public const string WeaponsPlaceholder = @"data\global\ui\PANEL\inv_weapons.DC6"; + + // --- Data --- + public const string EnglishTable = @"data\local\lng\eng\English.txt"; + public const string ExpansionStringTable = @"data\local\lng\eng\expansionstring.tbl"; + public const string LevelPreset = @"data\global\excel\LvlPrest.txt"; + public const string LevelType = @"data\global\excel\LvlTypes.txt"; + public const string LevelDetails = @"data\global\excel\Levels.txt"; + public const string ObjectDetails = @"data\global\excel\Objects.txt"; + + // --- Animations --- + public const string ObjectData = @"data\global\objects"; + public const string AnimationData = @"data\global\animdata.d2"; + public const string PlayerAnimationBase = @"data\global\CHARS"; + + // --- Inventory Data --- + public const string Weapons = @"data\global\excel\weapons.txt"; + public const string Armor = @"data\global\excel\armor.txt"; + public const string Misc = @"data\global\excel\misc.txt"; + + // --- Character Data --- + public const string Experience = @"data\global\excel\experience.txt"; + public const string CharStats = @"data\global\excel\charstats.txt"; + + // --- Music --- + public const string BGMTitle = @"data\global\music\introedit.wav"; + public const string BGMOptions = @"data\global\music\Common\options.wav"; + public const string BGMAct1AndarielAction = @"data\global\music\Act1\andarielaction.wav"; + public const string BGMAct1BloodRavenResolution = @"data\global\music\Act1\bloodravenresolution.wav"; + public const string BGMAct1Caves = @"data\global\music\Act1\caves.wav"; + public const string BGMAct1Crypt = @"data\global\music\Act1\crypt.wav"; + public const string BGMAct1DenOfEvilAction = @"data\global\music\Act1\denofevilaction.wav"; + public const string BGMAct1Monastery = @"data\global\music\Act1\monastery.wav"; + public const string BGMAct1Town1 = @"data\global\music\Act1\town1.wav"; + public const string BGMAct1Tristram = @"data\global\music\Act1\tristram.wav"; + public const string BGMAct1Wild = @"data\global\music\Act1\wild.wav"; + public const string BGMAct2Desert = @"data\global\music\Act2\desert.wav"; + public const string BGMAct2Harem = @"data\global\music\Act2\harem.wav"; + public const string BGMAct2HoradricAction = @"data\global\music\Act2\horadricaction.wav"; + public const string BGMAct2Lair = @"data\global\music\Act2\lair.wav"; + public const string BGMAct2RadamentResolution = @"data\global\music\Act2\radamentresolution.wav"; + public const string BGMAct2Sanctuary = @"data\global\music\Act2\sanctuary.wav"; + public const string BGMAct2Sewer = @"data\global\music\Act2\sewer.wav"; + public const string BGMAct2TaintedSunAction = @"data\global\music\Act2\taintedsunaction.wav"; + public const string BGMAct2Tombs = @"data\global\music\Act2\tombs.wav"; + public const string BGMAct2Town2 = @"data\global\music\Act2\town2.wav"; + public const string BGMAct2Valley = @"data\global\music\Act2\valley.wav"; + public const string BGMAct3Jungle = @"data\global\music\Act3\jungle.wav"; + public const string BGMAct3Kurast = @"data\global\music\Act3\kurast.wav"; + public const string BGMAct3KurastSewer = @"data\global\music\Act3\kurastsewer.wav"; + public const string BGMAct3MefDeathAction = @"data\global\music\Act3\mefdeathaction.wav"; + public const string BGMAct3OrbAction = @"data\global\music\Act3\orbaction.wav"; + public const string BGMAct3Spider = @"data\global\music\Act3\spider.wav"; + public const string BGMAct3Town3 = @"data\global\music\Act3\town3.wav"; + public const string BGMAct4Diablo = @"data\global\music\Act4\diablo.wav"; + public const string BGMAct4DiabloAction = @"data\global\music\Act4\diabloaction.wav"; + public const string BGMAct4ForgeAction = @"data\global\music\Act4\forgeaction.wav"; + public const string BGMAct4IzualAction = @"data\global\music\Act4\izualaction.wav"; + public const string BGMAct4Mesa = @"data\global\music\Act4\mesa.wav"; + public const string BGMAct4Town4 = @"data\global\music\Act4\town4.wav"; + public const string BGMAct5Baal = @"data\global\music\Act5\baal.wav"; + public const string BGMAct5XTown = @"data\global\music\Act5\xtown.wav"; + + + // --- Sound Effects --- + public const string SFXButtonClick = @"data\global\sfx\Cursor\button.wav"; + public const string SFXAmazonDeselect = @"data\global\sfx\Cursor\intro\amazon deselect.wav"; + public const string SFXAmazonSelect = @"data\global\sfx\Cursor\intro\amazon select.wav"; + public const string SFXAssassinDeselect = @"data\global\sfx\Cursor\intro\assassin deselect.wav"; + public const string SFXAssassinSelect = @"data\global\sfx\Cursor\intro\assassin select.wav"; + public const string SFXBarbarianDeselect = @"data\global\sfx\Cursor\intro\barbarian deselect.wav"; + public const string SFXBarbarianSelect = @"data\global\sfx\Cursor\intro\barbarian select.wav"; + public const string SFXDruidDeselect = @"data\global\sfx\Cursor\intro\druid deselect.wav"; + public const string SFXDruidSelect = @"data\global\sfx\Cursor\intro\druid select.wav"; + public const string SFXNecromancerDeselect = @"data\global\sfx\Cursor\intro\necromancer deselect.wav"; + public const string SFXNecromancerSelect = @"data\global\sfx\Cursor\intro\necromancer select.wav"; + public const string SFXPaladinDeselect = @"data\global\sfx\Cursor\intro\paladin deselect.wav"; + public const string SFXPaladinSelect = @"data\global\sfx\Cursor\intro\paladin select.wav"; + public const string SFXSorceressDeselect = @"data\global\sfx\Cursor\intro\sorceress deselect.wav"; + public const string SFXSorceressSelect = @"data\global\sfx\Cursor\intro\sorceress select.wav"; + + // --- Enemy Data --- + public static string MonStats = "data\\global\\excel\\monstats.txt"; + + // --- Skill Data --- + public static string Missiles = "data\\global\\excel\\missiles.txt"; + + public static string GeneratePathForItem(string spriteName) + { + return $@"data\global\items\{spriteName}.dc6"; + } + + public static string GetMusicPathForLevel(eLevelId levelId) + { + switch (levelId) + { + case eLevelId.None: + return string.Empty; + case eLevelId.Act1_Town1: + return BGMAct1Town1; + case eLevelId.Act1_CaveTreasure2: + return BGMAct1Caves; + case eLevelId.Act1_CaveTreasure3: + return BGMAct1Caves; + case eLevelId.Act1_CaveTreasure4: + return BGMAct1Caves; + case eLevelId.Act1_CaveTreasure5: + return BGMAct1Caves; + case eLevelId.Act1_CryptCountessX: + return BGMAct1BloodRavenResolution; // TODO: Verify + case eLevelId.Act1_Tower2: + return BGMAct1Caves; // TODO: Verify + case eLevelId.Act1_MonFront: + return BGMAct1DenOfEvilAction; // TODO: Verify + case eLevelId.Act1_Courtyard1: + return BGMAct1Monastery; // TODO: Verify + case eLevelId.Act1_Courtyard2: + return BGMAct1Monastery; // TODO: Verify + case eLevelId.Act1_Cathedral: + return BGMAct1Monastery; // TODO: Verify + case eLevelId.Act1_Andariel: + return BGMAct1AndarielAction; + case eLevelId.Act1_Tristram: + return BGMAct1Tristram; + case eLevelId.Act2_Town: + return BGMAct2Town2; + case eLevelId.Act2_Harem: + return BGMAct2Harem; + case eLevelId.Act2_DurielsLair: + return BGMAct2Lair; + case eLevelId.Act3_Town: + return BGMAct3Town3; + case eLevelId.Act3_DungeonTreasure1: + return BGMAct3Kurast; // TODO: Verify + case eLevelId.Act3_DungeonTreasure2: + return BGMAct3Kurast; // TODO: Verify + case eLevelId.Act3_SewerTreasureX: + return BGMAct3KurastSewer; // TODO: Verify + case eLevelId.Act3_Temple1: + return BGMAct3Kurast; // TODO: Verify + case eLevelId.Act3_Temple2: + return BGMAct3Kurast; // TODO: Verify + case eLevelId.Act3_Temple3: + return BGMAct3Kurast; // TODO: Verify + case eLevelId.Act3_Temple4: + return BGMAct3Kurast; // TODO: Verify + case eLevelId.Act3_Temple5: + return BGMAct3Kurast; // TODO: Verify + case eLevelId.Act3_Temple6: + return BGMAct3Kurast; // TODO: Verify + case eLevelId.Act3_MephistoComplex: + return BGMAct3MefDeathAction; // TODO: Verify + case eLevelId.Act4_Fortress: + return BGMAct4Mesa; // TODO: Verify + case eLevelId.Act5_Town: + return BGMAct5XTown; + case eLevelId.Act5_TempleFinalRoom: + return BGMAct2Sanctuary; // TODO: Verify + case eLevelId.Act5_ThroneRoom: + return BGMAct2Sanctuary; // TODO: Verify + case eLevelId.Act5_WorldStone: + return BGMAct4ForgeAction; // TODO: Verify + case eLevelId.Act5_TempleEntrance: + return BGMAct5Baal; // TODO: Verify + case eLevelId.Act5_BaalEntrance: + return BGMAct5Baal; // TODO: Verify + default: + return string.Empty; + } + } + + public static string GetHeroSkillPanel(eHero hero) + { + switch (hero) + { + case eHero.Amazon: + return SkillsPanelAmazon; + case eHero.Assassin: + return SkillsPanelAssassin; + case eHero.Barbarian: + return SkillsPanelBarbarian; + case eHero.Druid: + return SkillsPanelDruid; + case eHero.Necromancer: + return SkillsPanelNecromancer; + case eHero.Paladin: + return SkillsPanelPaladin; + case eHero.Sorceress: + return SkillsPanelSorcerer; + default: + throw new ArgumentException($"Unknown hero type: {hero}"); + } + } + + public static string GetHeroSkillsIcons(eHero hero) + { + switch (hero) + { + case eHero.None: + return GenericSkills; + case eHero.Amazon: + return AmazonSkills; + case eHero.Assassin: + return AssassinSkills; + case eHero.Barbarian: + return BarbarianSkills; + case eHero.Druid: + return DruidSkills; + case eHero.Necromancer: + return NecromancerSkills; + case eHero.Paladin: + return PaladinSkills; + case eHero.Sorceress: + return SorcererSkills; + default: + throw new ArgumentException($"Unknown hero type: {hero}"); + } + } + } +} + diff --git a/OpenDiablo2.Core/EngineDataManager.cs b/OpenDiablo2.Core/EngineDataManager.cs index 6a9befe3..e2312357 100644 --- a/OpenDiablo2.Core/EngineDataManager.cs +++ b/OpenDiablo2.Core/EngineDataManager.cs @@ -29,6 +29,7 @@ namespace OpenDiablo2.Core public ImmutableDictionary ExperienceConfigs { get; internal set; } public ImmutableDictionary HeroTypeConfigs { get; internal set; } public ImmutableList EnemyTypeConfigs { get; internal set; } + public ImmutableDictionary MissileTypeConfigs { get; internal set; } public EngineDataManager(IMPQProvider mpqProvider) { @@ -37,6 +38,7 @@ namespace OpenDiablo2.Core LoadLevelDetails(); LoadCharacterData(); LoadEnemyData(); + LoadSkillData(); Items = LoadItemData(); } @@ -50,7 +52,7 @@ namespace OpenDiablo2.Core .Skip(1) .Where(x => !String.IsNullOrWhiteSpace(x)) .Select(x => x.Split('\t')) - .Where(x => x.Count() == 36 && x[0] != "Expansion") + .Where(x => x.Count() >= 36 && x[0] != "Expansion") .Select(x => x.ToLevelType()) .ToImmutableList(); @@ -61,7 +63,7 @@ namespace OpenDiablo2.Core .Skip(1) .Where(x => !String.IsNullOrWhiteSpace(x)) .Select(x => x.Split('\t')) - .Where(x => x.Count() == 24 && x[0] != "Expansion") + .Where(x => x.Count() >= 24 && x[0] != "Expansion") .Select(x => x.ToLevelPreset()) .ToImmutableList(); @@ -170,7 +172,8 @@ namespace OpenDiablo2.Core private void LoadEnemyData() { - EnemyTypeConfigs = LoadEnemyTypeConfig(); + //TODO: RE-ENABLE THIS once monstats is being loaded properly + //EnemyTypeConfigs = LoadEnemyTypeConfig(); } private ImmutableList LoadEnemyTypeConfig() @@ -183,5 +186,26 @@ namespace OpenDiablo2.Core .ToArray() .Select(x => x.ToEnemyTypeConfig()) .ToImmutableList(); + + private void LoadSkillData() + { + MissileTypeConfigs = LoadMissileTypeConfig(); + } + + private ImmutableDictionary LoadMissileTypeConfig() + { + var data = mpqProvider + .GetTextFile(ResourcePaths.Missiles) + .Where(x => !String.IsNullOrWhiteSpace(x)); + + var splitdata = data + .Select(x => x.Split('\t')) + .Where(x => x[0] != "Expansion" && x[0] != "unused") + .ToArray(); + + // TODO: UNFINISHED + + return null; + } } } diff --git a/OpenDiablo2.Core/GameState/GameState.cs b/OpenDiablo2.Core/GameState/GameState.cs index 931c0cfe..19438e2d 100644 --- a/OpenDiablo2.Core/GameState/GameState.cs +++ b/OpenDiablo2.Core/GameState/GameState.cs @@ -55,6 +55,8 @@ namespace OpenDiablo2.Core.GameState_ public string MapName { get; private set; } public Palette CurrentPalette => paletteProvider.PaletteTable[$"ACT{Act}"]; public List PlayerInfos { get; private set; } + public eDifficulty Difficulty { get; private set; } + public int Seed { get; internal set; } public ItemInstance SelectedItem { get; internal set; } public object ThreadLocker { get; } = new object(); @@ -93,7 +95,7 @@ namespace OpenDiablo2.Core.GameState_ mapDataLookup = new List(); } - public void Initialize(string characterName, eHero hero, eSessionType sessionType) + public void Initialize(string characterName, eHero hero, eSessionType sessionType, eDifficulty difficulty) { sessionManager = getSessionManager(sessionType); sessionManager.Initialize(); @@ -103,6 +105,8 @@ namespace OpenDiablo2.Core.GameState_ sessionManager.OnPlayerInfo += OnPlayerInfo; sessionManager.OnFocusOnPlayer += OnFocusOnPlayer; + Difficulty = difficulty; + mapInfo = new List(); sceneManager.ChangeScene(eSceneType.Game); diff --git a/OpenDiablo2.Core/MPQProvider.cs b/OpenDiablo2.Core/MPQProvider.cs index e88d23b6..d1450050 100644 --- a/OpenDiablo2.Core/MPQProvider.cs +++ b/OpenDiablo2.Core/MPQProvider.cs @@ -42,7 +42,7 @@ namespace OpenDiablo2.Core .EnumerateFiles(globalConfiguration.BaseDataPath, "*.mpq") .Where(x => !(Path.GetFileName(x)?.StartsWith("patch") ?? false)) .Select(file => new MPQ(file)) - .ToArray(); + .ToList(); if (!_mpqs.Any()) @@ -74,6 +74,32 @@ namespace OpenDiablo2.Core foreach (var file in _mpqs[i].Files) _mpqLookup[file.ToLower()] = i; } + + // Get the combined list file by joining all of the other mpqs + List superListFile = _mpqs.SelectMany(x => x.Files).ToList(); + + var patchMPQ = Directory + .EnumerateFiles(globalConfiguration.BaseDataPath, "*.mpq") + .Where(x => Path.GetFileName(x).StartsWith("patch")) + .Select(file => new MPQ(file, superListFile)) + .First(); + + _mpqs.Add(patchMPQ); + int patchMPQIndex = _mpqs.Count - 1; + + // Replace existing mpqLookups with those from the patch, which take precedence + foreach (var file in patchMPQ.Files) + { + // unlike the other mpqs, we need to ensure that the files actually exist + // inside of the patch mpq instead of assuming that they do, because + // we can't trust the filelist + if (!patchMPQ.HasFile(file)) + { + continue; + } + + _mpqLookup[file.ToLower()] = patchMPQIndex; + } } public byte[] GetBytes(string fileName) @@ -98,6 +124,11 @@ namespace OpenDiablo2.Core public string GetCharacterDccPath(eHero hero, eMobMode mobMode, eCompositType compositType, PlayerEquipment equipment) { var fileName = $@"{ResourcePaths.PlayerAnimationBase}\{hero.ToToken()}\{compositType.ToToken()}\{hero.ToToken()}{compositType.ToToken()}".ToLower(); + var armorType = eArmorType.Lite; + + // Override default armor type based on equipped torso + if(equipment.Torso != null && (equipment.Torso.Item as Armor).ArmorTypes.ContainsKey(compositType)) + armorType = (equipment.Torso.Item as Armor).ArmorTypes[compositType]; switch (compositType) { @@ -110,7 +141,7 @@ namespace OpenDiablo2.Core case eCompositType.Legs: case eCompositType.RightArm: case eCompositType.LeftArm: - fileName += $"{equipment.ArmorType.ToToken()}{mobMode.ToToken()}"; + fileName += $"{armorType.ToToken()}{mobMode.ToToken()}"; return _mpqLookup.ContainsKey($"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower()) ? $"{fileName}{equipment.WeaponClass.ToToken()}.dcc".ToLower() : $"{fileName}{eWeaponClass.HandToHand.ToToken()}.dcc".ToLower(); @@ -134,7 +165,7 @@ namespace OpenDiablo2.Core // TODO: Figure these out... case eCompositType.Special1: case eCompositType.Special2: - fileName += $"{equipment.ArmorType.ToToken()}{mobMode.ToToken()}{equipment.WeaponClass}.dcc".ToLower(); + fileName += $"{armorType.ToToken()}{mobMode.ToToken()}{equipment.WeaponClass}.dcc".ToLower(); return _mpqLookup.ContainsKey(fileName) ? fileName : null; // TODO: Should we silence this? diff --git a/OpenDiablo2.Core/UI/Button.cs b/OpenDiablo2.Core/UI/Button.cs index ecbf985b..d2385d62 100644 --- a/OpenDiablo2.Core/UI/Button.cs +++ b/OpenDiablo2.Core/UI/Button.cs @@ -1,234 +1,234 @@ -using OpenDiablo2.Common; -using OpenDiablo2.Common.Interfaces; -using OpenDiablo2.Common.Models; -using System.Drawing; - -namespace OpenDiablo2.Core.UI -{ - public sealed class Button : IButton - { - private readonly IMouseInfoProvider mouseInfoProvider; - private readonly IRenderWindow renderWindow; - private readonly ISoundProvider musicProvider; - private readonly ButtonLayout buttonLayout; - private readonly byte[] sfxButtonClick; - - public OnActivateDelegate OnActivate { get; set; } - public OnToggleDelegate OnToggle { get; set; } - - private Point location = new Point(); - public Point Location - { - get => location; - set - { - location = value; - sprite.Location = value; - } - } - - private readonly int buttonWidth; - private readonly int buttonHeight; - private readonly ISprite sprite; - private readonly IFont font; - private readonly ILabel label; - private bool pressed = false; - private bool active = false; // When true, button is actively being focus pressed - private bool activeLock = false; // When true, we have locked the mouse from everything else - public bool Toggled { get; private set; } = false; - - private Point labelOffset = new Point(); - - private bool enabled = true; - public bool Enabled - { - get => enabled; - set - { - if (value == enabled) - return; - enabled = value; - - if(buttonLayout.IsDarkenedWhenDisabled) - sprite.Darken = !enabled; - } - } - - private string text; - public string Text - { - get => text; - set - { - text = value; - UpdateText(); - } - } - - - public Button( - ButtonLayout buttonLayout, - IRenderWindow renderWindow, - IMouseInfoProvider mouseInfoProvider, - ISoundProvider soundProvider, - IMPQProvider mpqProvider - ) - { - this.buttonLayout = buttonLayout; - this.renderWindow = renderWindow; - this.mouseInfoProvider = mouseInfoProvider; - this.musicProvider = soundProvider; - - font = renderWindow.LoadFont(buttonLayout.FontPath, Palettes.Units); - label = renderWindow.CreateLabel(font); - - sprite = renderWindow.LoadSprite(buttonLayout.ResourceName, buttonLayout.PaletteName, true); - - // TODO: Less stupid way of doing this would be super nice - buttonWidth = 0; - buttonHeight = 0; - for (int i = 0; i < buttonLayout.XSegments; i++) - { - sprite.Frame = i; - buttonWidth += sprite.LocalFrameSize.Width; - } - for(int i = 0; i < buttonLayout.YSegments; i++) - { - sprite.Frame = i * buttonLayout.YSegments; - buttonHeight += sprite.LocalFrameSize.Height; - } - - label.MaxWidth = buttonWidth - 8; - label.Alignment = Common.Enums.eTextAlign.Centered; - - sfxButtonClick = mpqProvider.GetBytes(ResourcePaths.SFXButtonClick); - } - - public bool Toggle() - { - Toggled = !Toggled; - - OnToggle?.Invoke(Toggled); - - return Toggled; - } - - public bool Toggle(bool isToggled) - { - if(Toggled != isToggled) - { - OnToggle?.Invoke(isToggled); - - Toggled = isToggled; - } - - return isToggled; - } - - public void Update() - { - if (!enabled) - { - // Prevent sticky locks - if (activeLock && mouseInfoProvider.ReserveMouse) - { - activeLock = false; - mouseInfoProvider.ReserveMouse = false; - } - - active = false; - return; - } - - int clickWidth = buttonLayout.ClickableRect.Width > 0 ? buttonLayout.ClickableRect.Width : buttonWidth; - int clickHeight = buttonLayout.ClickableRect.Height > 0 ? buttonLayout.ClickableRect.Height : buttonHeight; - - var hovered = mouseInfoProvider.MouseX >= location.X + buttonLayout.ClickableRect.X - && mouseInfoProvider.MouseY >= location.Y + buttonLayout.ClickableRect.Y - && mouseInfoProvider.MouseX < location.X + clickWidth + buttonLayout.ClickableRect.X - && mouseInfoProvider.MouseY < location.Y + clickHeight + buttonLayout.ClickableRect.Y; - - if (!activeLock && hovered && mouseInfoProvider.LeftMouseDown && !mouseInfoProvider.ReserveMouse) - { - // The button is being pressed down - mouseInfoProvider.ReserveMouse = true; - active = true; - musicProvider.PlaySfx(sfxButtonClick); - } - else if (active && !mouseInfoProvider.LeftMouseDown) - { - mouseInfoProvider.ReserveMouse = false; - active = false; - - if (hovered) - { - OnActivate?.Invoke(); - - if (buttonLayout.Toggleable) - { - Toggle(); - } - } - - } - else if (!active && mouseInfoProvider.LeftMouseDown) - { - activeLock = true; - } - else if (activeLock && !mouseInfoProvider.LeftMouseDown) - { - activeLock = false; - } - - pressed = hovered && mouseInfoProvider.LeftMouseDown && active; - } - - public void Render() - { - var frame = buttonLayout.BaseFrame; - - if (buttonLayout.AllowFrameChange) - { - if(!Enabled && buttonLayout.DisabledFrame >= 0) - { - frame = buttonLayout.DisabledFrame; - } - else if (Toggled && pressed) - { - frame = buttonLayout.BaseFrame + 3; - } - else if (pressed) - { - frame = buttonLayout.BaseFrame + 1; - } - else if (Toggled) - { - frame = buttonLayout.BaseFrame + 2; - } - } - - renderWindow.Draw(sprite, buttonLayout.XSegments, buttonLayout.YSegments, frame); - var offset = pressed ? -2 : 0; - - label.Location = new Point(location.X + offset + labelOffset.X, location.Y - offset + labelOffset.Y); - renderWindow.Draw(label); - } - - private void UpdateText() - { - label.Text = text; - label.TextColor = Color.FromArgb(75, 75, 75); - - var offsetX = (buttonWidth / 2) - (label.TextArea.Width / 2); - var offsetY = (buttonHeight / 2) - (label.TextArea.Height / 2); - labelOffset = new Point(offsetX, offsetY - 5); - } - - public void Dispose() - { - sprite.Dispose(); - font.Dispose(); - label.Dispose(); - } - } -} +using OpenDiablo2.Common; +using OpenDiablo2.Common.Interfaces; +using OpenDiablo2.Common.Models; +using System.Drawing; + +namespace OpenDiablo2.Core.UI +{ + public sealed class Button : IButton + { + private readonly IMouseInfoProvider mouseInfoProvider; + private readonly IRenderWindow renderWindow; + private readonly ISoundProvider musicProvider; + private readonly ButtonLayout buttonLayout; + private readonly byte[] sfxButtonClick; + + public OnActivateDelegate OnActivate { get; set; } + public OnToggleDelegate OnToggle { get; set; } + + private Point location = new Point(); + public Point Location + { + get => location; + set + { + location = value; + sprite.Location = value; + } + } + + private readonly int buttonWidth; + private readonly int buttonHeight; + private readonly ISprite sprite; + private readonly IFont font; + private readonly ILabel label; + private bool pressed = false; + private bool active = false; // When true, button is actively being focus pressed + private bool activeLock = false; // When true, we have locked the mouse from everything else + public bool Toggled { get; private set; } = false; + + private Point labelOffset = new Point(); + + private bool enabled = true; + public bool Enabled + { + get => enabled; + set + { + if (value == enabled) + return; + enabled = value; + + if(buttonLayout.IsDarkenedWhenDisabled) + sprite.Darken = !enabled; + } + } + + private string text; + public string Text + { + get => text; + set + { + text = value; + UpdateText(); + } + } + + + public Button( + ButtonLayout buttonLayout, + IRenderWindow renderWindow, + IMouseInfoProvider mouseInfoProvider, + ISoundProvider soundProvider, + IMPQProvider mpqProvider + ) + { + this.buttonLayout = buttonLayout; + this.renderWindow = renderWindow; + this.mouseInfoProvider = mouseInfoProvider; + this.musicProvider = soundProvider; + + font = renderWindow.LoadFont(buttonLayout.FontPath, Palettes.Units); + label = renderWindow.CreateLabel(font); + + sprite = renderWindow.LoadSprite(buttonLayout.ResourceName, buttonLayout.PaletteName, true); + + // TODO: Less stupid way of doing this would be super nice + buttonWidth = 0; + buttonHeight = 0; + for (int i = 0; i < buttonLayout.XSegments; i++) + { + sprite.Frame = i; + buttonWidth += sprite.LocalFrameSize.Width; + } + for(int i = 0; i < buttonLayout.YSegments; i++) + { + sprite.Frame = i * buttonLayout.YSegments; + buttonHeight += sprite.LocalFrameSize.Height; + } + + label.MaxWidth = buttonWidth - 8; + label.Alignment = Common.Enums.eTextAlign.Centered; + + sfxButtonClick = mpqProvider.GetBytes(ResourcePaths.SFXButtonClick); + } + + public bool Toggle() + { + Toggled = !Toggled; + + OnToggle?.Invoke(Toggled); + + return Toggled; + } + + public bool Toggle(bool isToggled) + { + if(Toggled != isToggled) + { + OnToggle?.Invoke(isToggled); + + Toggled = isToggled; + } + + return isToggled; + } + + public void Update() + { + if (!enabled) + { + // Prevent sticky locks + if (activeLock && mouseInfoProvider.ReserveMouse) + { + activeLock = false; + mouseInfoProvider.ReserveMouse = false; + } + + active = false; + return; + } + + int clickWidth = buttonLayout.ClickableRect.Width > 0 ? buttonLayout.ClickableRect.Width : buttonWidth; + int clickHeight = buttonLayout.ClickableRect.Height > 0 ? buttonLayout.ClickableRect.Height : buttonHeight; + + var hovered = mouseInfoProvider.MouseX >= location.X + buttonLayout.ClickableRect.X + && mouseInfoProvider.MouseY >= location.Y + buttonLayout.ClickableRect.Y + && mouseInfoProvider.MouseX < location.X + clickWidth + buttonLayout.ClickableRect.X + && mouseInfoProvider.MouseY < location.Y + clickHeight + buttonLayout.ClickableRect.Y; + + if (!activeLock && hovered && mouseInfoProvider.LeftMouseDown && !mouseInfoProvider.ReserveMouse) + { + // The button is being pressed down + mouseInfoProvider.ReserveMouse = true; + active = true; + musicProvider.PlaySfx(sfxButtonClick); + } + else if (active && !mouseInfoProvider.LeftMouseDown) + { + mouseInfoProvider.ReserveMouse = false; + active = false; + + if (hovered) + { + OnActivate?.Invoke(); + + if (buttonLayout.Toggleable) + { + Toggle(); + } + } + + } + else if (!active && mouseInfoProvider.LeftMouseDown) + { + activeLock = true; + } + else if (activeLock && !mouseInfoProvider.LeftMouseDown) + { + activeLock = false; + } + + pressed = hovered && mouseInfoProvider.LeftMouseDown && active; + } + + public void Render() + { + var frame = buttonLayout.BaseFrame; + + if (buttonLayout.AllowFrameChange) + { + if(!Enabled && buttonLayout.DisabledFrame >= 0) + { + frame = buttonLayout.DisabledFrame; + } + else if (Toggled && pressed) + { + frame = buttonLayout.BaseFrame + 3; + } + else if (pressed) + { + frame = buttonLayout.BaseFrame + 1; + } + else if (Toggled) + { + frame = buttonLayout.BaseFrame + 2; + } + } + + renderWindow.Draw(sprite, buttonLayout.XSegments, buttonLayout.YSegments, frame); + var offset = pressed ? -2 : 0; + + label.Location = new Point(location.X + offset + labelOffset.X, location.Y - offset + labelOffset.Y); + renderWindow.Draw(label); + } + + private void UpdateText() + { + label.Text = text; + label.TextColor = Color.FromArgb(75, 75, 75); + + var offsetX = (buttonWidth / 2) - (label.TextArea.Width / 2); + var offsetY = (buttonHeight / 2) - (label.TextArea.Height / 2); + labelOffset = new Point(offsetX, offsetY - 5); + } + + public void Dispose() + { + sprite.Dispose(); + font.Dispose(); + label.Dispose(); + } + } +} diff --git a/OpenDiablo2.GameServer/GameServer.cs b/OpenDiablo2.GameServer/GameServer.cs index 2e76e911..cc43d88e 100644 --- a/OpenDiablo2.GameServer/GameServer.cs +++ b/OpenDiablo2.GameServer/GameServer.cs @@ -93,6 +93,8 @@ namespace OpenDiablo2.GameServer_ } } + // TODO: Default torso for testing. Remove when... we're done testing. + newPlayer.UpdateEquipment("tors", itemManager.getItemInstance("aar")); mobManager.AddPlayer(newPlayer); return newPlayer.Id; diff --git a/OpenDiablo2.MapGenerators/BloodMoor.cs b/OpenDiablo2.MapGenerators/BloodMoor.cs index d69cddf9..267c8321 100644 --- a/OpenDiablo2.MapGenerators/BloodMoor.cs +++ b/OpenDiablo2.MapGenerators/BloodMoor.cs @@ -15,10 +15,12 @@ namespace OpenDiablo2.MapGenerators private readonly IGameState gameState; private readonly LevelDetail levelDetail; + private readonly eDifficulty difficulty; public BloodMoor(IGameState gameState, IEngineDataManager dataManager) { this.gameState = gameState; + this.difficulty = gameState.Difficulty; levelDetail = dataManager.Levels.First(x => x.LevelName == "Blood Moor"); } @@ -193,8 +195,8 @@ namespace OpenDiablo2.MapGenerators // Generate the cave while (true) { - var rx = random.Next(8, levelDetail.SizeX - 16); - var ry = random.Next(8, levelDetail.SizeY - 16); + var rx = random.Next(8, levelDetail.Difficulties[difficulty].SizeX - 16); + var ry = random.Next(8, levelDetail.Difficulties[difficulty].SizeY - 16); rx -= (rx % 8); ry -= (ry % 8); var caveX = rx + location.X; @@ -216,8 +218,8 @@ namespace OpenDiablo2.MapGenerators var campsToGenerate = 3; while (campsToGenerate > 0) { - var rx = random.Next(8, levelDetail.SizeX - 16); - var ry = random.Next(8, levelDetail.SizeY - 16); + var rx = random.Next(8, levelDetail.Difficulties[difficulty].SizeX - 16); + var ry = random.Next(8, levelDetail.Difficulties[difficulty].SizeY - 16); rx -= (rx % 8); ry -= (ry % 8); var campX = rx + location.X; @@ -244,8 +246,8 @@ namespace OpenDiablo2.MapGenerators 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; + var rightEdge = levelDetail.Difficulties[difficulty].SizeX - 8; + var bottomEdge = levelDetail.Difficulties[difficulty].SizeY - 8; for (var y = 8; y < bottomEdge; y += 8) { diff --git a/OpenDiablo2.Scenes/SelectHeroClass.cs b/OpenDiablo2.Scenes/SelectHeroClass.cs index cf29d2cb..fd68acb3 100644 --- a/OpenDiablo2.Scenes/SelectHeroClass.cs +++ b/OpenDiablo2.Scenes/SelectHeroClass.cs @@ -271,7 +271,8 @@ namespace OpenDiablo2.Scenes StopSfx(); // TODO: Support other session types - gameState.Initialize(characterNameTextBox.Text, selectedHero.Value, eSessionType.Local); + // TODO: support other difficulty types + gameState.Initialize(characterNameTextBox.Text, selectedHero.Value, eSessionType.Local, eDifficulty.NORMAL); } private void OnExitClicked() @@ -321,7 +322,7 @@ namespace OpenDiablo2.Scenes private void RenderHeros() { - var heros = Enum.GetValues(typeof(eHero)).Cast(); + var heros = Enum.GetValues(typeof(eHero)).Cast().Skip(1); // skip NONE foreach (var hero in heros) if (heroRenderInfo[hero].Stance == eHeroStance.Idle || heroRenderInfo[hero].Stance == eHeroStance.IdleSelected) RenderHero(hero); @@ -419,6 +420,10 @@ namespace OpenDiablo2.Scenes private void UpdateHeroSelectionHover(eHero hero, long ms, bool canSelect) { + if(hero == eHero.None) + { + return; + } var renderInfo = heroRenderInfo[hero]; if (renderInfo.Stance == eHeroStance.Approaching) { diff --git a/README.md b/README.md index 86a5ba29..8b8fa9f4 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,18 @@ # OpenDiablo2 [![Build status](https://ci.appveyor.com/api/projects/status/jx83as0ku784r5vh?svg=true)](https://ci.appveyor.com/project/essial/opendiablo2) -An open source re-implementation of Diablo 2 in C# - [Join us on Discord!](https://discord.gg/pRy8tdc)\ [Development Live stream](https://www.twitch.tv/essial/) - - - ## About this project -This is an attempt to re-create Diablo 2's game engine in C#, and make it cross platform as well. This project does not ship with the assets or content required to work. You must have a legally purchased copy of [Diablo 2](https://us.shop.battle.net/en-us/product/diablo-ii) and its expansion [Lord of Destruction](https://us.shop.battle.net/en-us/product/diablo-ii-lord-of-destruction) installed on your computer in order to run this engine. If you have an original copy of the disks, those files should work fine as well. +OpenDiablo2 is an ARPG game engine in the same vein of the 2000's games, and supports playing Diablo 2. The engine is written in C# and is cross platform. However, please note that this project does not ship with the assets or content required to play Diablo 2. You must have a legally purchased copy of [Diablo 2](https://us.shop.battle.net/en-us/product/diablo-ii) and its expansion [Lord of Destruction](https://us.shop.battle.net/en-us/product/diablo-ii-lord-of-destruction) installed on your computer in order to run that game on this engine. If you have an original copy of the disks, those files should work fine as well. + +Currently we are working on features necessary to play Diablo 2 in its entireity, but will then expand with tools and plugin support to allow modding, as well as writing completely new games with the engine. + +Currently we have the main menu, credits screen, hero selection screen, map rendering, character animations, the beginnings of map generation, some equipment management, and a client/server stack. This project is still early in development and is not yet at a playable state. Please note that **this game is neither developed by, nor endorsed by Blizzard or its parent company Activision**. -This game is a clean-room implementation based on observations of how the original game works. Aside from the data file formats themselves, we have not and will not reverse engineer the original binaries of the game in an attempt to copy or duplicate intellectual property. - Diablo 2 and its content is ©2000 Blizzard Entertainment, Inc. All rights reserved. Diablo and Blizzard Entertainment are trademarks or registered trademarks of Blizzard Entertainment, Inc. in the U.S. and/or other countries. ALL OTHER TRADEMARKS ARE THE PROPERTY OF THEIR RESPECTIVE OWNERS. diff --git a/Screenshot.png b/Screenshot.png deleted file mode 100644 index 213fbe21..00000000 Binary files a/Screenshot.png and /dev/null differ diff --git a/Screenshot2.png b/Screenshot2.png deleted file mode 100644 index 12210945..00000000 Binary files a/Screenshot2.png and /dev/null differ