1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-12-25 11:36:26 -05:00

Load Enemy Type Info from monstats.txt (#48)

* EnemyConfig loading from monstats.txt
* removed some unneeded comments
* Added difficulty flag to enemystate
* Fix renaming issue
This commit is contained in:
nicholasdechiara 2018-12-12 18:20:44 -05:00 committed by Tim Sarbin
parent 21666ed4f4
commit 6b40c30b5c
19 changed files with 1413 additions and 13 deletions

View File

@ -49,6 +49,7 @@
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="UT_EnemyState.cs" />
<Compile Include="UT_MobState.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UT_PlayerState.cs" />

View File

@ -0,0 +1,200 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Enums.Mobs;
using OpenDiablo2.Common.Models.Mobs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.UT
{
[TestClass]
public class UT_EnemyState
{
public EnemyState MakeEnemyState(int id, eDifficulty difficulty, Random rand)
{
// does not correspond to any particular enemy in the actual data, just for testing
EnemyTypeConfig config = new EnemyTypeConfig(
InternalName: "TestEnemy",
Name: "TestEnemy1",
Type: "Skeleton",
Descriptor: "",
BaseId: 1,
PopulateId: 1,
Spawned: true,
Beta: false,
Code: "SK",
ClientOnly: false,
NoMap: false,
SizeX: 2,
SizeY: 2,
Height: 3,
NoOverlays: false,
OverlayHeight: 2,
Velocity: 3,
RunVelocity: 6,
CanStealFrom: false,
ColdEffect: 50,
Rarity: true,
MinimumGrouping: 2,
MaximumGrouping: 5,
BaseWeapon: "1hs",
AIParams: new int[] {75, 85, 9, 50, 0 },
Allied: false,
IsNPC: false,
IsCritter: false,
CanEnterTown: false,
HealthRegen: 80,
IsDemon: false,
IsLarge: false,
IsSmall: false,
IsFlying: false,
CanOpenDoors: false,
SpawningColumn: 0,
IsBoss: false,
IsInteractable: false,
IsKillable: true,
CanBeConverted: true,
HitClass: 3,
HasSpecialEndDeath: false,
DeadCollision: false,
CanBeRevivedByOtherMonsters: true,
AppearanceConfig: new EnemyTypeAppearanceConfig(
HasDeathAnimation: true,
HasNeutralAnimation: true,
HasWalkAnimation: true,
HasGetHitAnimation: true,
HasAttack1Animation: true,
HasAttack2Animation: false,
HasBlockAnimation: true,
HasCastAnimation: false,
HasSkillAnimation: new bool[] { false, false, false, false },
HasCorpseAnimation: false,
HasKnockbackAnimation: true,
HasRunAnimation: true,
HasLifeBar: true,
HasNameBar: false,
CannotBeSelected: false,
CanCorpseBeSelected: false,
BleedType: 0,
HasShadow: true,
LightRadius: 0,
HasUniqueBossColors: false,
CompositeDeath: true,
LightRGB: new byte[] { 255, 255, 255 }
),
CombatConfig: new EnemyTypeCombatConfig(
ElementalAttackMode: 4,
ElementalAttackType: eDamageTypes.PHYSICAL,
ElementalOverlayId: 0,
ElementalChance: 0,
ElementalMinDamage: 0,
ElementalMaxDamage: 0,
ElementalDuration: 0,
MissileForAttack: new int[] { 0, 0 },
MissileForSkill: new int[] { 0, 0, 0, 0, },
MissileForCase: 0,
MissileForSequence: 0,
CanMoveAttack: new bool[] { false, false },
CanMoveSkill: new bool[] { false, false, false, false },
CanMoveCast: false,
IsMelee: true,
IsAttackable: true,
MeleeRange: 0,
SkillType: new int[] { 0, 0, 0, 0 },
SkillSequence: new int[] { 0, 0, 0, 0 },
SkillLevel: new int[] { 0, 0, 0, 0 },
IsUndeadWithPhysicalAttacks: true,
IsUndeadWithMagicAttacks: false,
UsesMagicAttacks: false,
ChanceToBlock: 20,
DoesDeathDamage: false,
IgnoredBySummons: false
),
NormalDifficultyConfig: new EnemyTypeDifficultyConfig(
Level: 5,
DamageResist: 0,
MagicResist: 0,
FireResist: 0.5,
LightResist: 0,
ColdResist: 0,
PoisonResist: 0,
MinHP: 10,
MaxHP: 15,
AC: 3,
Exp: 100,
AttackMinDamage: new int[] { 5, 0 },
AttackMaxDamage: new int[] { 10, 0 },
AttackChanceToHit: new int[] { 35, 0 },
Skill1MinDamage: 0,
Skill1MaxDamage: 0,
Skill1ChanceToHit: 0,
TreasureClass: new string[] { "Swarm 1", "Act 2 Champ A", "Act 2 Unique A", "" }
),
NightmareDifficultyConfig: new EnemyTypeDifficultyConfig(
Level: 10,
DamageResist: 0,
MagicResist: 0,
FireResist: 0.5,
LightResist: 0.5,
ColdResist: 0,
PoisonResist: 0,
MinHP: 20,
MaxHP: 25,
AC: 3,
Exp: 1000,
AttackMinDamage: new int[] { 5, 0 },
AttackMaxDamage: new int[] { 10, 0 },
AttackChanceToHit: new int[] { 35, 0 },
Skill1MinDamage: 0,
Skill1MaxDamage: 0,
Skill1ChanceToHit: 0,
TreasureClass: new string[] { "Swarm 1", "Act 3 Champ A", "Act 3 Unique A", "" }
),
HellDifficultyConfig: new EnemyTypeDifficultyConfig(
Level: 15,
DamageResist: 0,
MagicResist: 0,
FireResist: 0.5,
LightResist: 0.5,
ColdResist: 0.5,
PoisonResist: 0,
MinHP: 30,
MaxHP: 45,
AC: 3,
Exp: 10000,
AttackMinDamage: new int[] { 5, 0 },
AttackMaxDamage: new int[] { 10, 0 },
AttackChanceToHit: new int[] { 35, 0 },
Skill1MinDamage: 0,
Skill1MaxDamage: 0,
Skill1ChanceToHit: 0,
TreasureClass: new string[] { "Swarm 2", "Act 4 Champ A", "Act 4 Unique A", "" }
)
);
EnemyState en = new EnemyState("Fallen", id, 0, 0, config, difficulty, rand);
return en;
}
[TestMethod]
public void EnemyDifficultyTest()
{
Random rand = new Random();
EnemyState en = MakeEnemyState(1, eDifficulty.NORMAL, rand);
Assert.AreEqual(5, en.Level);
Assert.AreEqual(100, en.ExperienceGiven);
EnemyState en2 = MakeEnemyState(2, eDifficulty.NIGHTMARE, rand);
Assert.AreEqual(10, en2.Level);
Assert.AreEqual(1000, en2.ExperienceGiven);
EnemyState en3 = MakeEnemyState(3, eDifficulty.HELL, rand);
Assert.AreEqual(15, en3.Level);
Assert.AreEqual(10000, en3.ExperienceGiven);
}
}
}

View File

@ -8,12 +8,19 @@ namespace OpenDiablo2.Common.Enums.Mobs
{
public enum eDamageTypes
{
NONE, // no resistances apply
PHYSICAL,
MAGIC,
FIRE,
COLD,
LIGHTNING,
POISON,
NONE = -1, // no resistances apply
PHYSICAL = 0,
MAGIC = 3, //1=fire, 2=lightning, 4=cold, 5=poison
FIRE = 1,
COLD = 4,
LIGHTNING = 2,
POISON = 5,
LIFE_STEAL = 6,
MANA_STEAL = 7,
STAMINA_STEAL = 8,
STUN = 9,
RANDOM = 10, // random between fire/cold/lightning/poison
BURN = 11,
FREEZE = 12,
}
}

View File

@ -20,6 +20,14 @@ namespace OpenDiablo2.Common.Enums.Mobs
{
PLAYER,
ENEMY,
INVULNERABLE
INVULNERABLE,
BOSS,
NPC,
CRITTER,
LARGE,
SMALL,
INTERACTABLE,
DEMON,
IGNORED_BY_SUMMONS,
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Enums
{
public enum eDifficulty
{
NORMAL,
NIGHTMARE,
HELL,
}
}

View File

@ -12,6 +12,7 @@ namespace OpenDiablo2.Common.Interfaces.Drawing
eHero Hero { get; set; }
PlayerEquipment Equipment { get; set; }
eMobMode MobMode { get; set; }
void Update(long ms);
void Render(int pixelOffsetX, int pixelOffsetY);
void ResetAnimationData();

View File

@ -14,5 +14,6 @@ namespace OpenDiablo2.Common.Interfaces
ImmutableList<Item> Items { get; }
ImmutableDictionary<eHero, ILevelExperienceConfig> ExperienceConfigs { get; }
ImmutableDictionary<eHero, IHeroTypeConfig> HeroTypeConfigs { get; }
ImmutableList<IEnemyTypeConfig> EnemyTypeConfigs { get; }
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Interfaces.Mobs
{
public interface IEnemyTypeAppearanceConfig
{
bool HasDeathAnimation { get; }
bool HasNeutralAnimation { get; }
bool HasWalkAnimation { get; }
bool HasGetHitAnimation { get; }
bool[] HasAttackAnimation { get; } // 1-2
bool HasBlockAnimation { get; }
bool HasCastAnimation { get; }
bool[] HasSkillAnimation { get; } // 1-4
bool HasCorpseAnimation { get; }
bool HasKnockbackAnimation { get; }
// HasSkillSequenceAnimation skipped due to being unused
bool HasRunAnimation { get; }
bool HasLifeBar { get; } // does this monster show a lifebar when you scroll over it
bool HasNameBar { get; } // does this monster show a name when you scroll over it
bool CannotBeSelected { get; } // if true, monster can never be highlighted
bool CanCorpseBeSelected { get; } // if true, the corpse can be highlighted
int BleedType { get; } // how does this monster bleed when hit?
// 0 = it doesn't, 1 = small blood, 2 = large blood, 3+ = random missiles from missiles.txt, the larger
// the number, the more missiles (??? this needs to be tested...)
bool HasShadow { get; } // does this have a shadow?
int LightRadius { get; } // how large of a light does this emit?
bool HasUniqueBossColors { get; } // if true, has unique colors when spawned
// as a boss.
bool CompositeDeath { get; } // if true, death animation uses multiple components
// (not clear if this is just for reference or if it actually has an effect...)
byte[] LightRGB { get; } // 0: R, 1: G, 2: B
}
}

View File

@ -0,0 +1,54 @@
using OpenDiablo2.Common.Enums.Mobs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Interfaces.Mobs
{
public interface IEnemyTypeCombatConfig
{
int ElementalAttackMode { get;} // 4 = on hit, rest are unknown?
eDamageTypes ElementalAttackType { get;} // 1=fire, 2=lightning, 4=cold, 5=poison
int ElementalOverlayId { get;} // see overlays.txt, corresponds to a row number -2
int ElementalChance { get;} // chance of use on a normal attack, 0 = never 100 = always
int ElementalMinDamage { get;}
int ElementalMaxDamage { get;}
int ElementalDuration { get;} // duration of effects like cold and poison
// TODO: these could be switched to SHORTS, is there any benefit to that?
int[] MissileForAttack { get;} // 1-2, which missile is used per attack
// important note: 65535 = NONE. See missiles.txt, corresponds to a row number -2
int[] MissileForSkill { get;} // 1-4, which missile is used for a skill
// important note: 65535 = NONE. See missiles.txt, corresponds to a row number -2
int MissileForCase { get;} // see above, specifies a missile for Case
int MissileForSequence { get;} // see above, specifies a missile for Sequence
bool[] CanMoveAttack { get;} // 1-2, can move while using attack 1 / 2
bool[] CanMoveSkill { get;} // 1-4, can move while using skill 1/2/3/4
bool CanMoveCast { get;} // can move while casting?
bool IsMelee { get;} // is this a melee attacker?
bool IsAttackable { get;} // is this monster attackable?
int MeleeRange { get; }
int[] SkillType { get;} // 1-5, what type of skill is it? 0 = none
int[] SkillSequence { get;} // 1-5 animation sequence number
// should not be 0 if skill is not 0; see animation column in skills.txt
int[] SkillLevel { get;} // 1-5, level of the skill
bool IsUndeadWithPhysicalAttacks { get;} // if true, is an undead attacking with physical attacks
bool IsUndeadWithMagicAttacks { get;} // if true '' magic atttacks
bool UsesMagicAttacks { get;}
int ChanceToBlock { get;}
bool DoesDeathDamage { get;} // if true, does damage when it dies
// like undead flayers (TODO: understand exactly how this works)
bool IgnoredBySummons { get;} // if true, is ignored by summons in combat
}
}

View File

@ -0,0 +1,88 @@
using OpenDiablo2.Common.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Interfaces.Mobs
{
public interface IEnemyTypeConfig
{
string InternalName { get; }
string DisplayName { get; } // Note the distinction here; the second column
// in the monstats is a unique identifier string, whereas the first is non-unique
string Type { get; }
string Descriptor { get; }
int BaseId { get; }
int PopulateId { get; }
bool Spawned { get; }
bool Beta { get; }
string Code { get; }
bool ClientOnly { get; }
bool NoMap { get; }
int SizeX { get; }
int SizeY { get; }
int Height { get; }
bool NoOverlays { get; }
int OverlayHeight { get; }
int Velocity { get; }
int RunVelocity { get; }
bool CanStealFrom { get; }
int ColdEffect { get; }
bool Rarity { get; }
int MinimumGrouping { get; }
int MaximumGrouping { get; }
string BaseWeapon { get; }
int[] AIParams { get; } // up to 5
bool Allied { get; } // Is this enemy on the player's side?
bool IsNPC { get; } // is this an NPC?
bool IsCritter { get; } // is this a critter? (e.g. the chickens)
bool CanEnterTown { get; } // can this enter town or not?
int HealthRegen { get; } // hp regen per minute
bool IsDemon { get; } // demon?
bool IsLarge { get; } // size large (e.g. bosses)
bool IsSmall { get; } // size small (e.g. fallen)
bool IsFlying { get; } // can move over water for instance
bool CanOpenDoors { get; }
int SpawningColumn { get; } // is the monster area restricted
// 0 = no, spawns through levels.txt, 1-3 unknown?
// TODO: understand spawningcolumn
bool IsBoss { get; }
bool IsInteractable { get; } // can this be interacted with? like an NPC
bool IsKillable { get; }
bool CanBeConverted { get; } // if true, can be switched to Allied by spells like Conversion
int HitClass { get; } // TODO: find out what this is
bool HasSpecialEndDeath { get; } // marks if a monster dies a special death
bool DeadCollision { get; } // if true, corpse has collision
bool CanBeRevivedByOtherMonsters { get; }
// appearance config
IEnemyTypeAppearanceConfig AppearanceConfig { get; }
// combat config
IEnemyTypeCombatConfig CombatConfig { get; }
// difficulty configs
IEnemyTypeDifficultyConfig NormalDifficultyConfig { get; }
IEnemyTypeDifficultyConfig NightmareDifficultyConfig { get; }
IEnemyTypeDifficultyConfig HellDifficultyConfig { get; }
IEnemyTypeDifficultyConfig GetDifficultyConfig(eDifficulty Difficulty);
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Interfaces.Mobs
{
public interface IEnemyTypeDifficultyConfig
{
int Level { get; }
double DamageResist { get; }
double MagicResist { get; }
double FireResist { get; }
double LightningResist { get; }
double ColdResist { get; }
double PoisonResist { get; }
int MinHP { get; }
int MaxHP { get; }
int AC { get; } // armor class
int Exp { get; }
int[] AttackMinDamage { get; } // 1-2, min damage attack can roll
int[] AttackMaxDamage { get; } // 1-2 max damage attack can roll
int[] AttackChanceToHit { get; } // 1-2 chance attack has to hit (out of 100??)
int Skill1MinDamage { get; } // min damage skill 1 can do (why only skill 1?)
int Skill1MaxDamage { get; } // max damage for skill 1
int Skill1ChanceToHit { get; } // chance skill has to hit
string[] TreasureClass { get; } // 1-4
}
}

View File

@ -1,17 +1,95 @@
using OpenDiablo2.Common.Enums.Mobs;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Enums.Mobs;
using OpenDiablo2.Common.Interfaces.Mobs;
using System;
namespace OpenDiablo2.Common.Models.Mobs
{
public class EnemyState : MobState
{
public IEnemyTypeConfig EnemyConfig { get; protected set; }
public int ExperienceGiven { get; protected set; }
public Stat[] AttackRating { get; protected set; }
public Stat DefenseRating { get; protected set; }
public Stat HealthRegen { get; protected set; }
public eDifficulty CreatedDifficulty { get; protected set; }
public EnemyState() : base() { }
public EnemyState(string name, int id, int level, int maxhealth, float x, float y, int experiencegiven)
: base(name, id, level, maxhealth, x, y)
public EnemyState(string name, int id, float x, float y,
IEnemyTypeConfig EnemyConfig, eDifficulty Difficulty, Random Rand)
: base(name, id, 0, 0, x, y)
{
ExperienceGiven = experiencegiven;
this.EnemyConfig = EnemyConfig;
IEnemyTypeDifficultyConfig difficultyConfig = EnemyConfig.GetDifficultyConfig(Difficulty);
CreatedDifficulty = Difficulty;
int maxhp = Rand.Next(difficultyConfig.MinHP, difficultyConfig.MaxHP + 1);
Health = new Stat(0, maxhp, maxhp, true);
ExperienceGiven = difficultyConfig.Exp;
Level = difficultyConfig.Level;
// stats
AttackRating = new Stat[2];
AttackRating[0] = new Stat(0, difficultyConfig.AttackChanceToHit[0], difficultyConfig.AttackChanceToHit[0], false);
AttackRating[1] = new Stat(0, difficultyConfig.AttackChanceToHit[1], difficultyConfig.AttackChanceToHit[1], false);
DefenseRating = new Stat(0, EnemyConfig.CombatConfig.ChanceToBlock, EnemyConfig.CombatConfig.ChanceToBlock, false);
HealthRegen = new Stat(0, EnemyConfig.HealthRegen, EnemyConfig.HealthRegen, true);
// handle immunities / resistances
SetResistance(eDamageTypes.COLD, difficultyConfig.ColdResist);
SetResistance(eDamageTypes.FIRE, difficultyConfig.FireResist);
SetResistance(eDamageTypes.MAGIC, difficultyConfig.MagicResist);
SetResistance(eDamageTypes.PHYSICAL, difficultyConfig.DamageResist);
SetResistance(eDamageTypes.POISON, difficultyConfig.PoisonResist);
SetResistance(eDamageTypes.LIGHTNING, difficultyConfig.LightningResist);
// interestingly, monsters don't actually have immunity flags
// TODO: should we treat a resistance of 1 differently?
// should a resistance of 1 be an immunity (e.g. the main difference is that
// effects / debuffs couldn't reduce the resistance below 100%)
// handle flags
if (EnemyConfig.IsBoss)
{
AddFlag(eMobFlags.BOSS);
}
if (EnemyConfig.IsCritter)
{
AddFlag(eMobFlags.CRITTER);
}
if (EnemyConfig.IsNPC)
{
AddFlag(eMobFlags.NPC);
}
if (EnemyConfig.IsLarge)
{
AddFlag(eMobFlags.LARGE);
}
if (EnemyConfig.IsSmall)
{
AddFlag(eMobFlags.SMALL);
}
if (EnemyConfig.IsInteractable)
{
AddFlag(eMobFlags.INTERACTABLE);
}
if (!EnemyConfig.IsKillable)
{
AddFlag(eMobFlags.INVULNERABLE);
}
if (EnemyConfig.IsDemon)
{
AddFlag(eMobFlags.DEMON);
}
if (EnemyConfig.CombatConfig.IgnoredBySummons)
{
AddFlag(eMobFlags.IGNORED_BY_SUMMONS);
}
AddFlag(eMobFlags.ENEMY);
}
}

View File

@ -0,0 +1,77 @@
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 EnemyTypeAppearanceConfig : IEnemyTypeAppearanceConfig
{
public bool HasDeathAnimation { get; private set; }
public bool HasNeutralAnimation { get; private set; }
public bool HasWalkAnimation { get; private set; }
public bool HasGetHitAnimation { get; private set; }
public bool[] HasAttackAnimation { get; private set; } // 1-2
public bool HasBlockAnimation { get; private set; }
public bool HasCastAnimation { get; private set; }
public bool[] HasSkillAnimation { get; private set; } // 1-4
public bool HasCorpseAnimation { get; private set; }
public bool HasKnockbackAnimation { get; private set; }
// HasSkillSequenceAnimation skipped due to being unused
public bool HasRunAnimation { get; private set; }
public bool HasLifeBar { get; private set; } // does this monster show a lifebar when you scroll over it
public bool HasNameBar { get; private set; } // does this monster show a name when you scroll over it
public bool CannotBeSelected { get; private set; } // if true, monster can never be highlighted
public bool CanCorpseBeSelected { get; private set; } // if true, the corpse can be highlighted
public int BleedType { get; private set; } // how does this monster bleed when hit?
// 0 = it doesn't, 1 = small blood, 2 = large blood, 3+ = random missiles from missiles.txt, the larger
// the number, the more missiles (??? this needs to be tested...)
public bool HasShadow { get; private set; } // does this have a shadow?
public int LightRadius { get; private set; } // how large of a light does this emit?
public bool HasUniqueBossColors { get; private set; } // if true, has unique colors when spawned
// as a boss.
public bool CompositeDeath { get; private set; } // if true, death animation uses multiple components
// (not clear if this is just for reference or if it actually has an effect...)
public byte[] LightRGB { get; private set; } // 0: R, 1: G, 2: B
public EnemyTypeAppearanceConfig(bool HasDeathAnimation, bool HasNeutralAnimation,
bool HasWalkAnimation, bool HasGetHitAnimation, bool HasAttack1Animation,
bool HasAttack2Animation, bool HasBlockAnimation, bool HasCastAnimation,
bool[] HasSkillAnimation, bool HasCorpseAnimation, bool HasKnockbackAnimation,
bool HasRunAnimation,
bool HasLifeBar, bool HasNameBar, bool CannotBeSelected, bool CanCorpseBeSelected,
int BleedType, bool HasShadow, int LightRadius, bool HasUniqueBossColors, bool CompositeDeath,
byte[] LightRGB)
{
this.HasDeathAnimation = HasDeathAnimation;
this.HasNeutralAnimation = HasNeutralAnimation;
this.HasWalkAnimation = HasWalkAnimation;
this.HasGetHitAnimation = HasGetHitAnimation;
this.HasAttackAnimation = new bool[] { HasAttack1Animation, HasAttack2Animation };
this.HasBlockAnimation = HasBlockAnimation;
this.HasCastAnimation = HasCastAnimation;
this.HasSkillAnimation = HasSkillAnimation;
this.HasCorpseAnimation = HasCorpseAnimation;
this.HasKnockbackAnimation = HasKnockbackAnimation;
this.HasRunAnimation = HasRunAnimation;
this.HasLifeBar = HasLifeBar;
this.HasNameBar = HasNameBar;
this.CannotBeSelected = CannotBeSelected;
this.CanCorpseBeSelected = CanCorpseBeSelected;
this.BleedType = BleedType;
this.HasShadow = HasShadow;
this.LightRadius = LightRadius;
this.HasUniqueBossColors = HasUniqueBossColors;
this.CompositeDeath = CompositeDeath;
this.LightRGB = LightRGB;
}
}
}

View File

@ -0,0 +1,109 @@
using OpenDiablo2.Common.Enums.Mobs;
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 EnemyTypeCombatConfig : IEnemyTypeCombatConfig
{
public int ElementalAttackMode { get; private set; } // 4 = on hit, rest are unknown?
public eDamageTypes ElementalAttackType { get; private set; } // 1=fire, 2=lightning, 4=cold, 5=poison
public int ElementalOverlayId { get; private set; } // see overlays.txt, corresponds to a row number -2
public int ElementalChance { get; private set; } // chance of use on a normal attack, 0 = never 100 = always
public int ElementalMinDamage { get; private set; }
public int ElementalMaxDamage { get; private set; }
public int ElementalDuration { get; private set; } // duration of effects like cold and poison
// TODO: these could be switched to SHORTS, is there any benefit to that?
public int[] MissileForAttack { get; private set; } // 1-2, which missile is used per attack
// important note: 65535 = NONE. See missiles.txt, corresponds to a row number -2
public int[] MissileForSkill { get; private set; } // 1-4, which missile is used for a skill
// important note: 65535 = NONE. See missiles.txt, corresponds to a row number -2
public int MissileForCase { get; private set; } // see above, specifies a missile for Case
public int MissileForSequence { get; private set; } // see above, specifies a missile for Sequence
public bool[] CanMoveAttack { get; private set; } // 1-2, can move while using attack 1 / 2
public bool[] CanMoveSkill { get; private set; } // 1-4, can move while using skill 1/2/3/4
public bool CanMoveCast { get; private set; } // can move while casting?
public bool IsMelee { get; private set; } // is this a melee attacker?
public bool IsAttackable { get; private set; } // is this monster attackable?
public int MeleeRange { get; private set; }
public int[] SkillType { get; private set; } // 1-5, what type of skill is it? 0 = none
public int[] SkillSequence { get; private set; } // 1-5 animation sequence number
// should not be 0 if skill is not 0; see animation column in skills.txt
public int[] SkillLevel { get; private set; } // 1-5, level of the skill
public bool IsUndeadWithPhysicalAttacks { get; private set; } // if true, is an undead attacking with physical attacks
public bool IsUndeadWithMagicAttacks { get; private set; } // if true '' magic atttacks
public bool UsesMagicAttacks { get; private set; }
public int ChanceToBlock { get; private set; }
public bool DoesDeathDamage { get; private set; } // if true, does damage when it dies
// like undead flayers (TODO: understand exactly how this works)
public bool IgnoredBySummons { get; private set; } // if true, is ignored by summons in combat
// (87) A1Move A2Move S1Move S2Move S3Move S4Move Cmove
// (109) Skill1 Skill1Seq Skill1Lvl Skill2 Skill2Seq Skill2Lvl Skill3 Skill3Seq Skill3Lvl Skill4 Skill4Seq Skill4Lvl Skill5 Skill5Seq Skill5Lvl
// (146) eLUndead eHUndead eDemon eMagicUsing eLarge eSmall eFlying eOpenDoors eSpawnCol eBoss
public EnemyTypeCombatConfig(int ElementalAttackMode, eDamageTypes ElementalAttackType, int ElementalOverlayId,
int ElementalChance, int ElementalMinDamage, int ElementalMaxDamage, int ElementalDuration,
int[] MissileForAttack, int[] MissileForSkill, int MissileForCase, int MissileForSequence,
bool[] CanMoveAttack, bool[] CanMoveSkill, bool CanMoveCast,
bool IsMelee, bool IsAttackable,
int MeleeRange,
int[] SkillType, int[] SkillSequence, int[] SkillLevel,
bool IsUndeadWithPhysicalAttacks, bool IsUndeadWithMagicAttacks, bool UsesMagicAttacks,
int ChanceToBlock,
bool DoesDeathDamage,
bool IgnoredBySummons
)
{
this.ElementalAttackMode = ElementalAttackMode;
this.ElementalAttackType = ElementalAttackType;
this.ElementalOverlayId = ElementalOverlayId;
this.ElementalChance = ElementalChance;
this.ElementalMinDamage = ElementalMinDamage;
this.ElementalMaxDamage = ElementalMaxDamage;
this.ElementalDuration = ElementalDuration;
this.MissileForAttack = MissileForAttack;
this.MissileForSkill = MissileForSkill;
this.MissileForCase = MissileForCase;
this.MissileForSequence = MissileForSequence;
this.CanMoveAttack = CanMoveAttack;
this.CanMoveSkill = CanMoveSkill;
this.CanMoveCast = CanMoveCast;
this.IsMelee = IsMelee;
this.IsAttackable = IsAttackable;
this.MeleeRange = MeleeRange;
this.SkillType = SkillType;
this.SkillSequence = SkillSequence;
this.SkillLevel = SkillLevel;
this.IsUndeadWithPhysicalAttacks = IsUndeadWithPhysicalAttacks;
this.IsUndeadWithMagicAttacks = IsUndeadWithMagicAttacks;
this.UsesMagicAttacks = UsesMagicAttacks;
this.ChanceToBlock = ChanceToBlock;
this.DoesDeathDamage = DoesDeathDamage;
this.IgnoredBySummons = IgnoredBySummons;
}
}
}

View File

@ -0,0 +1,586 @@
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Enums.Mobs;
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 EnemyTypeConfig : IEnemyTypeConfig
{
public string InternalName { get; private set; }
public string DisplayName { get; private set; }
public string Type { get; private set; }
public string Descriptor { get; private set; }
public int BaseId { get; private set; }
public int PopulateId { get; private set; }
public bool Spawned { get; private set; }
public bool Beta { get; private set; }
public string Code { get; private set; }
public bool ClientOnly { get; private set; }
public bool NoMap { get; private set; }
public int SizeX { get; private set; }
public int SizeY { get; private set; }
public int Height { get; private set; }
public bool NoOverlays { get; private set; }
public int OverlayHeight { get; private set; }
public int Velocity { get; private set; }
public int RunVelocity { get; private set; }
public bool CanStealFrom { get; private set; }
public int ColdEffect { get; private set; }
public bool Rarity { get; private set; }
public int MinimumGrouping { get; private set; }
public int MaximumGrouping { get; private set; }
public string BaseWeapon { get; private set; }
public int[] AIParams { get; private set; } // up to 5
public bool Allied { get; private set; } // Is this enemy on the player's side?
public bool IsNPC { get; private set; } // is this an NPC?
public bool IsCritter { get; private set; } // is this a critter? (e.g. the chickens)
public bool CanEnterTown { get; private set; } // can this enter town or not?
public int HealthRegen { get; private set; } // hp regen per minute
public bool IsDemon { get; private set; } // demon?
public bool IsLarge { get; private set; } // size large (e.g. bosses)
public bool IsSmall { get; private set; } // size small (e.g. fallen)
public bool IsFlying { get; private set; } // can move over water for instance
public bool CanOpenDoors { get; private set; }
public int SpawningColumn { get; private set; } // is the monster area restricted
// 0 = no, spawns through levels.txt, 1-3 unknown?
// TODO: understand spawningcolumn
public bool IsBoss { get; private set; }
public bool IsInteractable { get; private set; } // can this be interacted with? like an NPC
public bool IsKillable { get; private set; }
public bool CanBeConverted { get; private set; } // if true, can be switched to Allied by spells like Conversion
public int HitClass { get; private set; } // TODO: find out what this is
public bool HasSpecialEndDeath { get; private set; } // marks if a monster dies a special death
public bool DeadCollision { get; private set; } // if true, corpse has collision
public bool CanBeRevivedByOtherMonsters { get; private set; }
// appearance config
public IEnemyTypeAppearanceConfig AppearanceConfig { get; private set; }
// combat config
public IEnemyTypeCombatConfig CombatConfig { get; private set; }
// difficulty configs
public IEnemyTypeDifficultyConfig NormalDifficultyConfig { get; private set; }
public IEnemyTypeDifficultyConfig NightmareDifficultyConfig { get; private set; }
public IEnemyTypeDifficultyConfig HellDifficultyConfig { get; private set; }
public EnemyTypeConfig(string InternalName, string Name, string Type, string Descriptor,
int BaseId, int PopulateId, bool Spawned, bool Beta, string Code, bool ClientOnly, bool NoMap,
int SizeX, int SizeY, int Height, bool NoOverlays, int OverlayHeight,
int Velocity, int RunVelocity,
bool CanStealFrom, int ColdEffect,
bool Rarity,
int MinimumGrouping, int MaximumGrouping,
string BaseWeapon,
int[] AIParams,
bool Allied, bool IsNPC, bool IsCritter, bool CanEnterTown,
int HealthRegen,
bool IsDemon, bool IsLarge, bool IsSmall, bool IsFlying, bool CanOpenDoors, int SpawningColumn,
bool IsBoss, bool IsInteractable,
bool IsKillable, bool CanBeConverted,
int HitClass,
bool HasSpecialEndDeath, bool DeadCollision,
bool CanBeRevivedByOtherMonsters,
IEnemyTypeAppearanceConfig AppearanceConfig,
IEnemyTypeCombatConfig CombatConfig,
IEnemyTypeDifficultyConfig NormalDifficultyConfig,
IEnemyTypeDifficultyConfig NightmareDifficultyConfig,
IEnemyTypeDifficultyConfig HellDifficultyConfig)
{
this.InternalName = InternalName;
this.DisplayName = Name;
this.Type = Type;
this.Descriptor = Descriptor;
this.BaseId = BaseId;
this.PopulateId = PopulateId;
this.Spawned = Spawned;
this.Beta = Beta;
this.Code = Code;
this.ClientOnly = ClientOnly;
this.NoMap = NoMap;
this.SizeX = SizeX;
this.SizeY = SizeY;
this.Height = Height;
this.NoOverlays = NoOverlays;
this.OverlayHeight = OverlayHeight;
this.Velocity = Velocity;
this.RunVelocity = RunVelocity;
this.CanStealFrom = CanStealFrom;
this.ColdEffect = ColdEffect;
this.Rarity = Rarity;
this.MinimumGrouping = MinimumGrouping;
this.MaximumGrouping = MaximumGrouping;
this.BaseWeapon = BaseWeapon;
this.AIParams = AIParams;
this.Allied = Allied;
this.IsNPC = IsNPC;
this.IsCritter = IsCritter;
this.CanEnterTown = CanEnterTown;
this.HealthRegen = HealthRegen;
this.IsDemon = IsDemon;
this.IsLarge = IsLarge;
this.IsSmall = IsSmall;
this.IsFlying = IsFlying;
this.CanOpenDoors = CanOpenDoors;
this.SpawningColumn = SpawningColumn;
this.IsBoss = IsBoss;
this.IsInteractable = IsInteractable;
this.IsKillable = IsKillable;
this.CanBeConverted = CanBeConverted;
this.HitClass = HitClass;
this.HasSpecialEndDeath = HasSpecialEndDeath;
this.DeadCollision = DeadCollision;
this.CanBeRevivedByOtherMonsters = CanBeRevivedByOtherMonsters;
this.AppearanceConfig = AppearanceConfig;
this.CombatConfig = CombatConfig;
this.NormalDifficultyConfig = NormalDifficultyConfig;
this.NightmareDifficultyConfig = NightmareDifficultyConfig;
this.HellDifficultyConfig = HellDifficultyConfig;
}
public IEnemyTypeDifficultyConfig GetDifficultyConfig(eDifficulty Difficulty)
{
switch (Difficulty)
{
case eDifficulty.HELL:
return HellDifficultyConfig;
case eDifficulty.NIGHTMARE:
return NightmareDifficultyConfig;
case eDifficulty.NORMAL:
return NormalDifficultyConfig;
default:
return NormalDifficultyConfig;
}
}
}
public static class EnemyTypeConfigHelper
{
// (0) Class namco Type Descriptor
// (4) BaseId PopulateId Spawned Beta Code ClientOnly NoMap
// (11) SizeX SizeY Height
// (14) NoOverlays OverlayHeight
// (16) Velocity Run
// (18) CanStealFrom ColdEffect
// (20) Rarity Level Level(N) Level(H)
// (24) MeleeRng MinGrp MaxGrp
// (27) HD TR LG RA LA RH LH SH S1 S2 S3 S4 S5 S6 S7 S8
// (43) TotalPieces SpawnComponents BaseW
// (46) AIParam1 Comment AIParam2 Comment AIParam3 Comment AIParam4 Comment AIParam5 Comment
// (56) ModeDH ModeN ModeW ModeGH ModeA1 ModeA2 ModeB ModeC
// (64) ModeS1 ModeS2 ModeS3 ModeS4 ModeDD ModeKB ModeSQ ModeRN
// (72) ElMode ElType ElOver ElPct ElMinD ElMaxD ElDur
// (79) MissA1 MissA2 MissS1 MissS2 MissS3 MissS4 MissC MissSQ
// (87) A1Move A2Move S1Move S2Move S3Move S4Move Cmove
// (94) Align
// (95) IsMelee IsSel IsSel2 NeverSel CorpseSel IsAtt IsNPC IsCritter InTown
// (104) Bleed Shadow Light NoUniqueShift CompositeDeath
// (109) Skill1 Skill1Seq Skill1Lvl Skill2 Skill2Seq Skill2Lvl Skill3 Skill3Seq Skill3Lvl Skill4 Skill4Seq Skill4Lvl Skill5 Skill5Seq Skill5Lvl
// (124) LightR LightG LightB
// (127) DamageResist MagicResist FireResist LightResist ColdResist PoisonResist
// (133) DamageResist(N) MagicResist(N) FireResist(N) LightResist(N) ColdResist(N) PoisonResist(N)
// (139) DamageResist(H) MagicResist(H) FireResist(H) LightResist(H) ColdResist(H) PoisonResist(H)
// (145) DamageRegen
// (146) eLUndead eHUndead eDemon eMagicUsing eLarge eSmall eFlying eOpenDoors eSpawnCol eBoss
// (156) PixHeight Interact
// (158) MinHP MaxHP AC Exp ToBlock
// (163) A1MinD A1MaxD A1ToHit A2MinD A2MaxD A2ToHit S1MinD S1MaxD S1ToHit
// (172) MinHP(N) MaxHP(N) AC(N) Exp(N) A1MinD(N) A1MaxD(N) A1ToHit(N) A2MinD(N) A2MaxD(N) A2ToHit(N) S1MinD(N) S1MaxD(N) S1ToHit(N)
// (185) MinHP(H) MaxHP(H) AC(H) Exp(H) A1MinD(H) A1MaxD(H) A1ToHit(H) A2MinD(H) A2MaxD(H) A2ToHit(H) S1MinD(H) S1MaxD(H) S1ToHit(H)
// (198) TreasureClass1 TreasureClass2 TreasureClass3 TreasureClass4
// (202) TreasureClass1(N) TreasureClass2(N) TreasureClass3(N) TreasureClass4(N)
// (206) TreasureClass1(H) TreasureClass2(H) TreasureClass3(H) TreasureClass4(H)
// (210) SpawnPctBonus Soft Heart BodyPart Killable Switch Restore NeverCount HitClass
// (219) SplEndDeath SplGetModeChart SplEndGeneric SplClientEnd
// (223) DeadCollision UnflatDead BloodLocal DeathDamage
// (227) PetIgnore NoGfxHitTest
// (229) HitTestTop HitTestLeft HitTestWidth HitTestHeight
// (233) GenericSpawn AutomapCel SparsePopulate Zoo ObjectCollision Inert
private static int IntConvert(string s)
{
// this is a convenience because sometimes they forget to put a 0 in the D2 monstats.txt
if (string.IsNullOrEmpty(s))
{
return 0;
}
return Convert.ToInt32(s);
}
public static IEnemyTypeConfig ToEnemyTypeConfig(this string[] row)
{
return new EnemyTypeConfig(
InternalName: row[0],
Name: row[1],
Type: row[2],
Descriptor: row[3],
BaseId: IntConvert(row[4]),
PopulateId: IntConvert(row[5]),
Spawned: (row[6] == "1"),
Beta: (row[7] == "1"),
Code: row[8],
ClientOnly: (row[9] == "1"),
NoMap: (row[10] == "1"),
SizeX: IntConvert(row[11]),
SizeY: IntConvert(row[12]),
Height: IntConvert(row[13]),
NoOverlays: (row[14] == "1"),
OverlayHeight: IntConvert(row[15]),
Velocity: IntConvert(row[16]),
RunVelocity: IntConvert(row[17]),
CanStealFrom: (row[18] == "1"),
ColdEffect: IntConvert(row[19]),
Rarity: (row[20] == "1"),
MinimumGrouping: IntConvert(row[25]),
MaximumGrouping: IntConvert(row[26]),
BaseWeapon: row[45],
AIParams: new int[]
{
IntConvert(row[46]),
IntConvert(row[48]),
IntConvert(row[50]),
IntConvert(row[52]),
IntConvert(row[54])
},
Allied: (row[94] == "1"),
IsNPC: (row[101] == "1"),
IsCritter: (row[102] == "1"),
CanEnterTown: (row[103] == "1"),
HealthRegen: IntConvert(row[145]),
// (146) eLUndead eHUndead eDemon eMagicUsing eLarge eSmall eFlying eOpenDoors eSpawnCol eBoss
IsDemon: (row[148] == "1"),
IsLarge: (row[150] == "1"),
IsSmall: (row[151] == "1"),
IsFlying: (row[152] == "1"),
CanOpenDoors: (row[153] == "1"),
SpawningColumn: IntConvert(row[154]),
IsBoss: (row[155] == "1"),
IsInteractable: (row[157] == "1"),
IsKillable: (row[215] == "1"),
CanBeConverted: (row[216] == "1"),
HitClass: IntConvert(row[218]),
HasSpecialEndDeath: (row[219] == "1"),
DeadCollision: (row[223] == "1"),
CanBeRevivedByOtherMonsters: (row[236] == "1"),
// (56) ModeDH ModeN ModeW ModeGH ModeA1 ModeA2 ModeB ModeC
// (64) ModeS1 ModeS2 ModeS3 ModeS4 ModeDD ModeKB ModeSQ ModeRN
// (95) IsMelee IsSel IsSel2 NeverSel CorpseSel IsAtt IsNPC IsCritter InTown
// (104) Bleed Shadow Light NoUniqueShift CompositeDeath
// (124) LightR LightG LightB
AppearanceConfig: new EnemyTypeAppearanceConfig
(
HasDeathAnimation: (row[56] == "1"),
HasNeutralAnimation: (row[57] == "1"),
HasWalkAnimation: (row[58] == "1"),
HasGetHitAnimation: (row[59] == "1"),
HasAttack1Animation: (row[60] == "1"),
HasAttack2Animation: (row[61] == "1"),
HasBlockAnimation: (row[62] == "1"),
HasCastAnimation: (row[63] == "1"),
HasSkillAnimation: new bool[]
{
(row[64] == "1"),
(row[65] == "1"),
(row[66] == "1"),
(row[67] == "1")
},
HasCorpseAnimation: (row[68] == "1"),
HasKnockbackAnimation: (row[69] == "1"),
HasRunAnimation: (row[71] == "1"),
HasLifeBar: (row[96] == "1"),
HasNameBar: (row[97] == "1"),
CannotBeSelected: (row[98] == "1"),
CanCorpseBeSelected: (row[99] == "1"),
BleedType: IntConvert(row[104]),
HasShadow: (row[105] == "1"),
LightRadius: IntConvert(row[106]),
HasUniqueBossColors: (row[107] == "0"), // note: inverting the bool here
// since in the table it is "NoUniqueShift" but I want to store it as "HasUniqueShift"
CompositeDeath: (row[108] == "1"),
LightRGB: new byte[]
{
Convert.ToByte(row[124]),
Convert.ToByte(row[125]),
Convert.ToByte(row[126])
}
),
// (72) ElMode ElType ElOver ElPct ElMinD ElMaxD ElDur
// (79) MissA1 MissA2 MissS1 MissS2 MissS3 MissS4 MissC MissSQ
// (87) A1Move A2Move S1Move S2Move S3Move S4Move Cmove
// (95) IsMelee IsSel IsSel2 NeverSel CorpseSel IsAtt IsNPC IsCritter InTown
// (109) Skill1 Skill1Seq Skill1Lvl Skill2 Skill2Seq Skill2Lvl Skill3 Skill3Seq Skill3Lvl Skill4 Skill4Seq Skill4Lvl Skill5 Skill5Seq Skill5Lvl
CombatConfig: new EnemyTypeCombatConfig
(
ElementalAttackMode: IntConvert(row[72]),
ElementalAttackType: (eDamageTypes)IntConvert(row[73]),
ElementalOverlayId: IntConvert(row[74]),
ElementalChance: IntConvert(row[75]),
ElementalMinDamage: IntConvert(row[76]),
ElementalMaxDamage: IntConvert(row[77]),
ElementalDuration: IntConvert(row[78]),
MissileForAttack: new int[]
{
IntConvert(row[79]),
IntConvert(row[80])
},
MissileForSkill: new int[]
{
IntConvert(row[81]),
IntConvert(row[82]),
IntConvert(row[83]),
IntConvert(row[84])
},
MissileForCase: IntConvert(row[85]),
MissileForSequence: IntConvert(row[86]),
CanMoveAttack: new bool[]
{
(row[87] == "1"),
(row[88] == "1")
},
CanMoveSkill: new bool[]
{
(row[89] == "1"),
(row[90] == "1"),
(row[91] == "1"),
(row[92] == "1")
},
CanMoveCast: (row[93] == "1"),
IsMelee: (row[95] == "1"),
IsAttackable: (row[100] == "1"),
MeleeRange: IntConvert(row[24]),
SkillType: new int[]
{
IntConvert(row[109]),
IntConvert(row[112]),
IntConvert(row[115]),
IntConvert(row[118]),
IntConvert(row[121]),
},
SkillSequence: new int[]
{
IntConvert(row[110]),
IntConvert(row[113]),
IntConvert(row[116]),
IntConvert(row[119]),
IntConvert(row[122]),
},
SkillLevel: new int[]
{
IntConvert(row[111]),
IntConvert(row[114]),
IntConvert(row[117]),
IntConvert(row[120]),
IntConvert(row[123]),
},
IsUndeadWithPhysicalAttacks: (row[146] == "1"),
IsUndeadWithMagicAttacks: (row[147] == "1"),
UsesMagicAttacks: (row[149] == "1"),
ChanceToBlock: IntConvert(row[162]),
DoesDeathDamage: (row[226] == "1"),
IgnoredBySummons: (row[227] == "1")
),
// (127) DamageResist MagicResist FireResist LightResist ColdResist PoisonResist
// (133) DamageResist(N) MagicResist(N) FireResist(N) LightResist(N) ColdResist(N) PoisonResist(N)
// (139) DamageResist(H) MagicResist(H) FireResist(H) LightResist(H) ColdResist(H) PoisonResist(H)
// (158) MinHP MaxHP AC Exp ToBlock
// (163) A1MinD A1MaxD A1ToHit A2MinD A2MaxD A2ToHit S1MinD S1MaxD S1ToHit
// (172) MinHP(N) MaxHP(N) AC(N) Exp(N) A1MinD(N) A1MaxD(N) A1ToHit(N) A2MinD(N) A2MaxD(N) A2ToHit(N) S1MinD(N) S1MaxD(N) S1ToHit(N)
// (185) MinHP(H) MaxHP(H) AC(H) Exp(H) A1MinD(H) A1MaxD(H) A1ToHit(H) A2MinD(H) A2MaxD(H) A2ToHit(H) S1MinD(H) S1MaxD(H) S1ToHit(H)
// (192) TreasureClass1 TreasureClass2 TreasureClass3 TreasureClass4
// (202) TreasureClass1(N) TreasureClass2(N) TreasureClass3(N) TreasureClass4(N)
// (206) TreasureClass1(H) TreasureClass2(H) TreasureClass3(H) TreasureClass4(H)
NormalDifficultyConfig: new EnemyTypeDifficultyConfig
(
Level: IntConvert(row[21]),
DamageResist: (IntConvert(row[127]) / 100.0),
MagicResist: (IntConvert(row[128]) / 100.0),
FireResist: (IntConvert(row[129]) / 100.0),
LightResist: (IntConvert(row[130]) / 100.0),
ColdResist: (IntConvert(row[131]) / 100.0),
PoisonResist: (IntConvert(row[132]) / 100.0),
MinHP: IntConvert(row[158]),
MaxHP: IntConvert(row[159]),
AC: IntConvert(row[160]),
Exp: IntConvert(row[161]),
AttackMinDamage: new int[]
{
IntConvert(row[163]),
IntConvert(row[166])
},
AttackMaxDamage: new int[]
{
IntConvert(row[164]),
IntConvert(row[167])
},
AttackChanceToHit: new int[]
{
IntConvert(row[165]),
IntConvert(row[168])
},
Skill1MinDamage: IntConvert(row[169]),
Skill1MaxDamage: IntConvert(row[170]),
Skill1ChanceToHit: IntConvert(row[171]),
TreasureClass: new string[]
{
row[198], row[199], row[200], row[201]
}
),
NightmareDifficultyConfig: new EnemyTypeDifficultyConfig
(
Level: IntConvert(row[22]),
DamageResist: (IntConvert(row[133]) / 100.0),
MagicResist: (IntConvert(row[134]) / 100.0),
FireResist: (IntConvert(row[135]) / 100.0),
LightResist: (IntConvert(row[136]) / 100.0),
ColdResist: (IntConvert(row[137]) / 100.0),
PoisonResist: (IntConvert(row[138]) / 100.0),
MinHP: IntConvert(row[172]),
MaxHP: IntConvert(row[173]),
AC: IntConvert(row[174]),
Exp: IntConvert(row[175]),
AttackMinDamage: new int[]
{
IntConvert(row[176]),
IntConvert(row[179])
},
AttackMaxDamage: new int[]
{
IntConvert(row[177]),
IntConvert(row[180])
},
AttackChanceToHit: new int[]
{
IntConvert(row[178]),
IntConvert(row[181])
},
Skill1MinDamage: IntConvert(row[182]),
Skill1MaxDamage: IntConvert(row[183]),
Skill1ChanceToHit: IntConvert(row[184]),
TreasureClass: new string[]
{
row[202], row[203], row[204], row[205]
}
),
HellDifficultyConfig: new EnemyTypeDifficultyConfig
(
Level: IntConvert(row[23]),
DamageResist: (IntConvert(row[139]) / 100.0),
MagicResist: (IntConvert(row[140]) / 100.0),
FireResist: (IntConvert(row[141]) / 100.0),
LightResist: (IntConvert(row[142]) / 100.0),
ColdResist: (IntConvert(row[143]) / 100.0),
PoisonResist: (IntConvert(row[144]) / 100.0),
MinHP: IntConvert(row[185]),
MaxHP: IntConvert(row[186]),
AC: IntConvert(row[187]),
Exp: IntConvert(row[188]),
AttackMinDamage: new int[]
{
IntConvert(row[189]),
IntConvert(row[192])
},
AttackMaxDamage: new int[]
{
IntConvert(row[190]),
IntConvert(row[193])
},
AttackChanceToHit: new int[]
{
IntConvert(row[191]),
IntConvert(row[194])
},
Skill1MinDamage: IntConvert(row[195]),
Skill1MaxDamage: IntConvert(row[196]),
Skill1ChanceToHit: IntConvert(row[197]),
TreasureClass: new string[]
{
row[206], row[207], row[208], row[209]
}
)
);
}
}
}

View File

@ -0,0 +1,68 @@
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 EnemyTypeDifficultyConfig : IEnemyTypeDifficultyConfig
{
public int Level { get; private set; }
public double DamageResist { get; private set; }
public double MagicResist { get; private set; }
public double FireResist { get; private set; }
public double LightningResist { get; private set; }
public double ColdResist { get; private set; }
public double PoisonResist { get; private set; }
public int MinHP { get; private set; }
public int MaxHP { get; private set; }
public int AC { get; private set; } // armor class
public int Exp { get; private set; }
public int[] AttackMinDamage { get; private set; } // 1-2, min damage attack can roll
public int[] AttackMaxDamage { get; private set; } // 1-2 max damage attack can roll
public int[] AttackChanceToHit { get; private set; } // 1-2 chance attack has to hit (out of 100??)
public int Skill1MinDamage { get; private set; } // min damage skill 1 can do (why only skill 1?)
public int Skill1MaxDamage { get; private set; } // max damage for skill 1
public int Skill1ChanceToHit { get; private set; } // chance skill has to hit
public string[] TreasureClass { get; private set; } // 1-4
public EnemyTypeDifficultyConfig (int Level,
double DamageResist, double MagicResist, double FireResist, double LightResist,
double ColdResist, double PoisonResist,
int MinHP, int MaxHP, int AC, int Exp,
int[] AttackMinDamage, int[] AttackMaxDamage, int[] AttackChanceToHit,
int Skill1MinDamage, int Skill1MaxDamage, int Skill1ChanceToHit,
string[] TreasureClass)
{
this.Level = Level;
this.DamageResist = DamageResist;
this.MagicResist = MagicResist;
this.FireResist = FireResist;
this.LightningResist = LightResist;
this.ColdResist = ColdResist;
this.PoisonResist = PoisonResist;
this.MinHP = MinHP;
this.MaxHP = MaxHP;
this.AC = AC;
this.Exp = Exp;
this.AttackMinDamage = AttackMinDamage;
this.AttackMaxDamage = AttackMaxDamage;
this.AttackChanceToHit = AttackChanceToHit;
this.Skill1MinDamage = Skill1MinDamage;
this.Skill1MaxDamage = Skill1MaxDamage;
this.Skill1ChanceToHit = Skill1ChanceToHit;
this.TreasureClass = TreasureClass;
}
}
}

View File

@ -61,6 +61,7 @@
<Compile Include="Attributes\RandomizedMapAttribute.cs" />
<Compile Include="Attributes\SceneAttribute.cs" />
<Compile Include="AutofacModule.cs" />
<Compile Include="Enums\eDifficulty.cs" />
<Compile Include="Enums\eItemContainerType.cs" />
<Compile Include="Enums\eAnimationFrame.cs" />
<Compile Include="Enums\eArmorType.cs" />
@ -98,6 +99,14 @@
<Compile Include="Interfaces\MessageBus\IMessageFrame.cs" />
<Compile Include="Interfaces\MessageBus\ISessionManager.cs" />
<Compile Include="Interfaces\MessageBus\ISessionServer.cs" />
<Compile Include="Interfaces\Mobs\IEnemyTypeAppearanceConfig.cs" />
<Compile Include="Interfaces\Mobs\IEnemyTypeCombatConfig.cs" />
<Compile Include="Interfaces\Mobs\IEnemyTypeConfig.cs" />
<Compile Include="Interfaces\Mobs\IEnemyTypeDifficultyConfig.cs" />
<Compile Include="Models\Mobs\EnemyTypeAppearanceConfig.cs" />
<Compile Include="Models\Mobs\EnemyTypeCombatConfig.cs" />
<Compile Include="Models\Mobs\EnemyTypeConfig.cs" />
<Compile Include="Models\Mobs\EnemyTypeDifficultyConfig.cs" />
<Compile Include="Interfaces\UI\IGameHUD.cs" />
<Compile Include="Interfaces\UI\IPanel.cs" />
<Compile Include="Models\AnimationData.cs" />

View File

@ -1,4 +1,4 @@
/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C#
/* 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
@ -219,6 +219,9 @@ namespace OpenDiablo2.Common
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";
@ -304,3 +307,4 @@ namespace OpenDiablo2.Common
}
}
}

View File

@ -26,6 +26,7 @@ namespace OpenDiablo2.Core
public ImmutableList<Item> Items { get; internal set; }
public ImmutableDictionary<eHero, ILevelExperienceConfig> ExperienceConfigs { get; internal set; }
public ImmutableDictionary<eHero, IHeroTypeConfig> HeroTypeConfigs { get; internal set; }
public ImmutableList<IEnemyTypeConfig> EnemyTypeConfigs { get; internal set; }
public EngineDataManager(IMPQProvider mpqProvider)
{
@ -33,6 +34,7 @@ namespace OpenDiablo2.Core
LoadLevelDetails();
LoadCharacterData();
LoadEnemyData();
Items = LoadItemData();
}
@ -139,6 +141,23 @@ namespace OpenDiablo2.Core
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.Where(x => x[0] != "Expansion")
.ToArray()
.ToImmutableDictionary(x => (eHero)Enum.Parse(typeof(eHero), x[0]), x => x.ToHeroTypeConfig());
private void LoadEnemyData()
{
EnemyTypeConfigs = LoadEnemyTypeConfig();
}
private ImmutableList<IEnemyTypeConfig> LoadEnemyTypeConfig()
=> mpqProvider
.GetTextFile(ResourcePaths.MonStats)
.Skip(1)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Select(x => x.Split('\t'))
.Where(x => x[0] != "Expansion" && x[0] != "unused")
.ToArray()
.Select(x => x.ToEnemyTypeConfig())
.ToImmutableList();
}
}