From 2434da44cb8a7ba0ea46dbcbb39ee72e386cf4f2 Mon Sep 17 00:00:00 2001 From: soul4soul <5142635+soul4soul@users.noreply.github.com> Date: Tue, 25 May 2021 11:15:21 -0400 Subject: [PATCH] Fix many issues parsing and writing TFS monster abilities (#18) These changes fix issues #6, #8, and #14 (both the melee issue and combat damage type issue) Other improvements beyond the reported issues TFS XML: Fixed parsing of condition causing spells, melee attack name, duration, animations, effects, and area information for all spells TFS RevScriptSys: Writing condition causing spells, duration, area information, outfit changing spells, animations, and effects for all, and combat type TFSXML->TFS RevScriptSys: Spells from files now in some cases but needs more testing and checking overall --- README.md | 20 +- .../Enums/Animation.cs | 61 ++ app/MonsterConverterInterface/Enums/Blood.cs | 15 + .../Enums/ChangeAppearanceType.cs | 13 + .../Enums/CombatDamage.cs | 21 + .../Enums/ConditionType.cs | 39 ++ app/MonsterConverterInterface/Enums/Effect.cs | 99 +++ .../Enums/SoundLevel.cs | 13 + .../{MonsterTypes => Enums}/SpellCategory.cs | 0 .../MonsterTypes/EnumTypes.cs | 225 ------- .../MonsterTypes/Spell.cs | 25 +- .../TfsRevScriptSysConverter.cs | 230 +++---- .../SerializerClasses.cs | 373 ++++++++++++ app/MonsterConverterTfsXml/TfsXmlConverter.cs | 572 ++++-------------- .../TibiaWikiConverter.cs | 11 +- 15 files changed, 890 insertions(+), 827 deletions(-) create mode 100644 app/MonsterConverterInterface/Enums/Animation.cs create mode 100644 app/MonsterConverterInterface/Enums/Blood.cs create mode 100644 app/MonsterConverterInterface/Enums/ChangeAppearanceType.cs create mode 100644 app/MonsterConverterInterface/Enums/CombatDamage.cs create mode 100644 app/MonsterConverterInterface/Enums/ConditionType.cs create mode 100644 app/MonsterConverterInterface/Enums/Effect.cs create mode 100644 app/MonsterConverterInterface/Enums/SoundLevel.cs rename app/MonsterConverterInterface/{MonsterTypes => Enums}/SpellCategory.cs (100%) delete mode 100644 app/MonsterConverterInterface/MonsterTypes/EnumTypes.cs create mode 100644 app/MonsterConverterTfsXml/SerializerClasses.cs diff --git a/README.md b/README.md index 05e5df79..27356741 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,14 @@ Tibia OT Monster Converter is a tool for converting monster files between the va - WPF - Visual Studio 2019 -## Status - -Parsing and writing of nested loot is not yet supported by any formats. This is a low priority item as cipbia removed nested loot and its no longer commonly found in OT servers. - -### Supported Formats - -| Format | Input % Complete | Output % Complete | Notes | -| ---------------------------------------------------- | ---------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| TFS XML | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-XML-Input-Status) | [0%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-XML-Output-Status) | - Most common OT Monster format which has been around for over a decade | -| [PyOT](https://bitbucket.org/vapus/pyot/) | [0%](https://github.com/soul4soul/ot-monster-converter/wiki/PyOT-Input-Status) | [90%](https://github.com/soul4soul/ot-monster-converter/wiki/PyOT-Output-Status) | - This format can be consider dead as PyOT development has ceased. Unless development is picked back up support for this format is unlikely to be completed. | -| TFS revscriptsys | [0%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-revscriptsys-Input-Status) | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-revscriptsys-Output-Status) | - Very new OT monster format that was theorized many years ago. In the future there is a good chance it will replace TFS XML completely. This is likely the output type that most users of this program will use. | -| [TibiaWiki](https://tibia.fandom.com/wiki/Main_Page) | [80%](https://github.com/soul4soul/ot-monster-converter/wiki/TibiaWiki-Input-Status) | [60%](https://github.com/soul4soul/ot-monster-converter/wiki/TibiaWiki-Output-Status) | - Helpful for keeping monsters up to date with cipbia
- See The [Infobox Creature Template](https://tibia.fandom.com/wiki/Template:Infobox_Creature) for information about TibiaWiki Format
- Monsters created from TibiaWiki will require corpse id, looktype, and spells to be created manually | +## Supported Formats + +| Format | Input % Complete | Output % Complete | Notes | +| - | - | - | - | +| TFS XML | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-XML-Input-Status) | [0%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-XML-Output-Status) | - Most common OT Monster format which has been around for over a decade | +| [PyOT](https://bitbucket.org/vapus/pyot/) | [0%](https://github.com/soul4soul/ot-monster-converter/wiki/PyOT-Input-Status) | [90%](https://github.com/soul4soul/ot-monster-converter/wiki/PyOT-Output-Status) | - This format can be consider dead as PyOT development has ceased. Unless development is picked back up support for this format is unlikely to be completed. | +| TFS revscriptsys | [0%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-revscriptsys-Input-Status) | [95%](https://github.com/soul4soul/ot-monster-converter/wiki/TFS-revscriptsys-Output-Status) | - Very new OT monster format that was theorized many years ago. In the future there is a good chance it will replace TFS XML completely. This is likely the output type that most users of this program will use.
- Opentibiabr RevScriptSys format is not completely compatible with TFS RevScriptSys format | +| [TibiaWiki](https://tibia.fandom.com/wiki/Main_Page) | [80%](https://github.com/soul4soul/ot-monster-converter/wiki/TibiaWiki-Input-Status) | [60%](https://github.com/soul4soul/ot-monster-converter/wiki/TibiaWiki-Output-Status) | - Helpful for keeping monsters up to date with cipbia
- See The [Infobox Creature Template](https://tibia.fandom.com/wiki/Template:Infobox_Creature) for information about TibiaWiki Format
- Monsters created from TibiaWiki will require corpse id, looktype, and spells to be created manually | ## Graphical Interface diff --git a/app/MonsterConverterInterface/Enums/Animation.cs b/app/MonsterConverterInterface/Enums/Animation.cs new file mode 100644 index 00000000..ceb47983 --- /dev/null +++ b/app/MonsterConverterInterface/Enums/Animation.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonsterConverterInterface.MonsterTypes +{ + public enum Animation + { + None = 0, + Spear, + Bolt, + Arrow, + Fire, + Energy, + PoisonArrow, + BurstArrow, + ThrowingStar, + ThrowingKnife, + SmallStone, + Death, + LargeRock, + Snowball, + PowerBolt, + Poison, + InfernalBolt, + HuntingSpear, + EnchantedSpear, + RedStar, + GreenStar, + RoyalSpear, + SniperArrow, + OnyxArrow, + PiercingBolt, + WhirlwindSword, + WhirlwindAxe, + WhirlwindClub, + EtherealSpear, + Ice, + Earth, + Holy, + SuddenDeath, + FlashArrow, + FlammingArrow, + ShiverArrow, + EnergyBall, + SmallIce, + SmallHoly, + SmallEarth, + EarthArrow, + Explosion, + Cake, + TarsalArrow = 44, + VortexBolt = 45, + PrismaticBolt, + CrystallineArrow, + DrillBolt, + EnvenomedArrow, + GloothSpear, + SimpleArrow + } +} diff --git a/app/MonsterConverterInterface/Enums/Blood.cs b/app/MonsterConverterInterface/Enums/Blood.cs new file mode 100644 index 00000000..a874de18 --- /dev/null +++ b/app/MonsterConverterInterface/Enums/Blood.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonsterConverterInterface.MonsterTypes +{ + public enum Blood + { + blood, + venom, + undead, + fire, + energy + } +} diff --git a/app/MonsterConverterInterface/Enums/ChangeAppearanceType.cs b/app/MonsterConverterInterface/Enums/ChangeAppearanceType.cs new file mode 100644 index 00000000..9acf900d --- /dev/null +++ b/app/MonsterConverterInterface/Enums/ChangeAppearanceType.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonsterConverterInterface.MonsterTypes +{ + public enum ChangeAppearanceType + { + MonsterName, + OutfitId, + Item + } +} diff --git a/app/MonsterConverterInterface/Enums/CombatDamage.cs b/app/MonsterConverterInterface/Enums/CombatDamage.cs new file mode 100644 index 00000000..c2589d20 --- /dev/null +++ b/app/MonsterConverterInterface/Enums/CombatDamage.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonsterConverterInterface.MonsterTypes +{ + public enum CombatDamage + { + Physical, + Energy, + Earth, + Fire, + LifeDrain, + ManaDrain, + Healing, + Drown, + Ice, + Holy, + Death + } +} diff --git a/app/MonsterConverterInterface/Enums/ConditionType.cs b/app/MonsterConverterInterface/Enums/ConditionType.cs new file mode 100644 index 00000000..32111ec3 --- /dev/null +++ b/app/MonsterConverterInterface/Enums/ConditionType.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonsterConverterInterface.MonsterTypes +{ + public enum ConditionType + { + None, + Poison, + Fire, + Energy, + Bleeding, + Haste, + Paralyze, + Outfit, + Invisible, + Light, + ManaShield, + InFight, + Drunk, + ExhaustWeapon, // unused + Regeneration, + Soul, + Drown, + Muted, + ChannelMutedTicks, + YellTicks, + Attributes, + Freezing, + Dazzled, + Cursed, + ExhaustCombat, // unused + Exhaust_heal, // unused + Pacified, + SpellCoolDown, + SpellGroupCoolDown + } +} diff --git a/app/MonsterConverterInterface/Enums/Effect.cs b/app/MonsterConverterInterface/Enums/Effect.cs new file mode 100644 index 00000000..84d94230 --- /dev/null +++ b/app/MonsterConverterInterface/Enums/Effect.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonsterConverterInterface.MonsterTypes +{ + public enum Effect + { + None = 0, + DrawBlood, + LoseEnergy, + Poff, + BlockHit, + ExplosionArea, + ExplosionHit, + FireArea, + YellowRings, + GreenRings, + HitArea, + Teleport, + EnergyHit, + MagicBlue, + MagicRed, + MagicGreen, + HitByFire, + HitByPoison, + MortArea, + SoundGreen, + SoundRed, + PoisonArea, + SoundYellow, + SoundPurple, + SoundBlue, + SoundWhite, + Bubbles, + Craps, + GiftWraps, + FireworkYellow, + FireworkRed, + FireworkBlue, + Stun, + Sleep, + WaterCreature, + GroundShaker, + Hearts, + FireAttack, + EnergyArea, + SmallClouds, + HolyDamage, + BigClouds, + IceArea, + IceTornado, + IceAttack, + Stones, + SmallPlants, + Carniphila, + PurpleEnergy, + YellowEnergy, + HolyArea, + BigPlants, + Cake, + GiantIce, + WaterSplash, + PlantAttack, + TutorialArrow, + TutorialSquare, + MirrorHorizontal, + MirrorVertical, + SkullHorizontal, + SkullVertical, + Assassin, + StepsHorizontal, + BloodySteps, + StepsVertical, + YalahariGhost, + Bats, + Smoke, + Insects, + Dragonhead, + OrcShaman, + OrcShamanFire, + Thunder, + Ferumbras, + ConfettiHorizontal, + ConfettiVertical, + // 77-157 are empty + BlackSmoke = 158, + // 159-166 are empty + RedSmoke = 167, + YellowSmoke = 168, + GreenSmoke = 169, + PurpleSmoke = 170, + EarlyThunder = 171, + RagiazBoneCapsule = 172, + CriticalDamage = 173, + // 174 is empty + PlungingFish = 175 + } +} diff --git a/app/MonsterConverterInterface/Enums/SoundLevel.cs b/app/MonsterConverterInterface/Enums/SoundLevel.cs new file mode 100644 index 00000000..966ec124 --- /dev/null +++ b/app/MonsterConverterInterface/Enums/SoundLevel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonsterConverterInterface.MonsterTypes +{ + public enum SoundLevel + { + Whisper, + Say, + Yell + } +} diff --git a/app/MonsterConverterInterface/MonsterTypes/SpellCategory.cs b/app/MonsterConverterInterface/Enums/SpellCategory.cs similarity index 100% rename from app/MonsterConverterInterface/MonsterTypes/SpellCategory.cs rename to app/MonsterConverterInterface/Enums/SpellCategory.cs diff --git a/app/MonsterConverterInterface/MonsterTypes/EnumTypes.cs b/app/MonsterConverterInterface/MonsterTypes/EnumTypes.cs deleted file mode 100644 index 1a52b4aa..00000000 --- a/app/MonsterConverterInterface/MonsterTypes/EnumTypes.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace MonsterConverterInterface.MonsterTypes -{ - public enum Condition - { - None, - Poison, - Fire, - Energy, - Bleeding, - Haste, - Paralyze, - Outfit, - Invisible, - Light, - ManaShield, - InFight, - Drunk, - ExhaustWeapon, // unused - Regeneration, - Soul, - Drown, - Muted, - ChannelMutedTicks, - YellTicks, - Attributes, - Freezing, - Dazzled, - Cursed, - ExhaustCombat, // unused - Exhaust_heal, // unused - Pacified, - SpellCoolDown, - SpellGroupCoolDown - } - - public enum CombatDamage - { - Physical, - Energy, - Earth, - Fire, - LifeDrain, - ManaDrain, - Healing, - Drown, - Ice, - Holy, - Death - } - - public enum TargetType - { - Direction, - Self, - Area - } - - public enum Effect - { - None = 0, - DrawBlood, - LoseEnergy, - Poff, - BlockHit, - ExplosionArea, - ExplosionHit, - FireArea, - YellowRings, - GreenRings, - HitArea, - Teleport, - EnergyHit, - MagicBlue, - MagicRed, - MagicGreen, - HitByFire, - HitByPoison, - MortArea, - SoundGreen, - SoundRed, - PoisonArea, - SoundYellow, - SoundPurple, - SoundBlue, - SoundWhite, - Bubbles, - Craps, - GiftWraps, - FireworkYellow, - FireworkRed, - FireworkBlue, - Stun, - Sleep, - WaterCreature, - GroundShaker, - Hearts, - FireAttack, - EnergyArea, - SmallClouds, - HolyDamage, - BigClouds, - IceArea, - IceTornado, - IceAttack, - Stones, - SmallPlants, - Carniphila, - PurpleEnergy, - YellowEnergy, - HolyArea, - BigPlants, - Cake, - GiantIce, - WaterSplash, - PlantAttack, - TutorialArrow, - TutorialSquare, - MirrorHorizontal, - MirrorVertical, - SkullHorizontal, - SkullVertical, - Assassin, - StepsHorizontal, - BloodySteps, - StepsVertical, - YalahariGhost, - Bats, - Smoke, - Insects, - Dragonhead, - OrcShaman, - OrcShamanFire, - Thunder, - Ferumbras, - ConfettiHorizontal, - ConfettiVertical, - // 77-157 are empty - BlackSmoke = 158, - // 159-166 are empty - RedSmoke = 167, - YellowSmoke = 168, - GreenSmoke = 169, - PurpleSmoke = 170, - EarlyThunder = 171, - RagiazBoneCapsule = 172, - CriticalDamage = 173, - // 174 is empty - PlungingFish = 175 - } - - public enum Animation - { - None = 0, - Spear, - Bolt, - Arrow, - Fire, - Energy, - PoisonArrow, - BurstArrow, - ThrowingStar, - ThrowingKnife, - SmallStone, - Death, - LargeRock, - Snowball, - PowerBolt, - Poison, - InfernalBolt, - HuntingSpear, - EnchantedSpear, - RedStar, - GreenStar, - RoyalSpear, - SniperArrow, - OnyxArrow, - PiercingBolt, - WhirlwindSword, - WhirlwindAxe, - WhirlwindClub, - EtherealSpear, - Ice, - Earth, - Holy, - SuddenDeath, - FlashArrow, - FlammingArrow, - ShiverArrow, - EnergyBall, - SmallIce, - SmallHoly, - SmallEarth, - EarthArrow, - Explosion, - Cake, - TarsalArrow = 44, - VortexBolt = 45, - PrismaticBolt, - CrystallineArrow, - DrillBolt, - EnvenomedArrow, - GloothSpear, - SimpleArrow - } - - public enum Blood - { - blood, - venom, - undead, - fire, - energy - } - - public enum SoundLevel - { - Whisper, - Say, - Yell - } -} diff --git a/app/MonsterConverterInterface/MonsterTypes/Spell.cs b/app/MonsterConverterInterface/MonsterTypes/Spell.cs index 6bd1bf0d..6c31404a 100644 --- a/app/MonsterConverterInterface/MonsterTypes/Spell.cs +++ b/app/MonsterConverterInterface/MonsterTypes/Spell.cs @@ -10,29 +10,32 @@ public class Spell public SpellCategory SpellCategory { get; set; } public int? MinDamage { get; set; } public int? MaxDamage { get; set; } - public TargetType? TargetStyle { get; set; } - public CombatDamage? DamageElement { get; set; } - public Effect? AreaEffect { get; set; } - public Animation? ShootEffect { get; set; } - public uint? Chance { get; set; } - public uint? Interval { get; set; } + public Effect AreaEffect { get; set; } + public Animation ShootEffect { get; set; } + public double Chance { get; set; } + public uint Interval { get; set; } public uint? Range { get; set; } public uint? Radius { get; set; } public uint? Length { get; set; } public uint? Spread { get; set; } - public bool? Target { get; set; } + public bool? OnTarget { get; set; } + // Magic damage + public CombatDamage? DamageElement { get; set; } // Speed - public int? SpeedChange { get; set; } + public int? MinSpeedChange { get; set; } + public int? MaxSpeedChange { get; set; } public int? Duration { get; set; } - // Melee Stuff + // Melee damage overrides public int? AttackValue { get; set; } public int? Skill { get; set; } - // Spell Condition stuff + // Spell Condition stuff (used for condition only + melee conditions) public int? Tick { get; set; } public int? StartDamage { get; set; } - public Condition? Condition { get; set; } + public ConditionType Condition { get; set; } // Outfit stuff public string MonsterName { get; set; } public int? ItemId { get; set; } + // Drunk + public double? Drunkenness { get; set; } } } diff --git a/app/MonsterConverterTfsRevScriptSys/TfsRevScriptSysConverter.cs b/app/MonsterConverterTfsRevScriptSys/TfsRevScriptSysConverter.cs index a1d0507c..520e8f55 100644 --- a/app/MonsterConverterTfsRevScriptSys/TfsRevScriptSysConverter.cs +++ b/app/MonsterConverterTfsRevScriptSys/TfsRevScriptSysConverter.cs @@ -15,45 +15,45 @@ public class TfsRevScriptSysConverter : MonsterConverter const uint MAX_LOOTCHANCE = 100000; - IDictionary ConditioToTFsConstants = new Dictionary + IDictionary ConditionToTfsConstant = new Dictionary { - {Condition.Poison, "CONDITION_POISON"}, - {Condition.Fire, "CONDITION_FIRE"}, - {Condition.Energy, "CONDITION_ENERGY"}, - {Condition.Bleeding, "CONDITION_BLEEDING"}, - {Condition.Paralyze, "CONDITION_POISON"}, - {Condition.Drown, "CONDITION_DROWN"}, - {Condition.Freezing, "CONDITION_FREEZING"}, - {Condition.Dazzled, "CONDITION_DAZZLED"}, - {Condition.Cursed, "CONDITION_CURSED"} + {ConditionType.Poison, "CONDITION_POISON"}, + {ConditionType.Fire, "CONDITION_FIRE"}, + {ConditionType.Energy, "CONDITION_ENERGY"}, + {ConditionType.Bleeding, "CONDITION_BLEEDING"}, + {ConditionType.Paralyze, "CONDITION_POISON"}, + {ConditionType.Drown, "CONDITION_DROWN"}, + {ConditionType.Freezing, "CONDITION_FREEZING"}, + {ConditionType.Dazzled, "CONDITION_DAZZLED"}, + {ConditionType.Cursed, "CONDITION_CURSED"} }; - IDictionary ConditionToString = new Dictionary + IDictionary ConditionToString = new Dictionary { - {Condition.Poison, "poison"}, - {Condition.Fire, "fire"}, - {Condition.Energy, "energy"}, - {Condition.Bleeding, "bleeding"}, - {Condition.Paralyze, "poison"}, - {Condition.Drown, "drown"}, - {Condition.Freezing, "freezing"}, - {Condition.Dazzled, "dazzled"}, - {Condition.Cursed, "cursed"} + {ConditionType.Poison, "poison"}, + {ConditionType.Fire, "fire"}, + {ConditionType.Energy, "energy"}, + {ConditionType.Bleeding, "bleeding"}, + {ConditionType.Paralyze, "poison"}, + {ConditionType.Drown, "drown"}, + {ConditionType.Freezing, "freezing"}, + {ConditionType.Dazzled, "dazzled"}, + {ConditionType.Cursed, "cursed"} }; IDictionary CombatDamageNames = new Dictionary { - {CombatDamage.Physical, "COMBAT_PHYSICAL"}, - {CombatDamage.Energy, "COMBAT_ENERGY"}, - {CombatDamage.Earth, "COMBAT_EARTH"}, - {CombatDamage.Fire, "COMBAT_FIRE"}, + {CombatDamage.Physical, "COMBAT_PHYSICALDAMAGE"}, + {CombatDamage.Energy, "COMBAT_ENERGYDAMAGE"}, + {CombatDamage.Earth, "COMBAT_EARTHDAMAGE"}, + {CombatDamage.Fire, "COMBAT_FIREDAMAGE"}, {CombatDamage.LifeDrain, "COMBAT_LIFEDRAIN"}, {CombatDamage.ManaDrain, "COMBAT_MANADRAIN"}, {CombatDamage.Healing, "COMBAT_HEALING"}, - {CombatDamage.Drown, "COMBAT_DROWN"}, - {CombatDamage.Ice, "COMBAT_ICE"}, - {CombatDamage.Holy, "COMBAT_HOLY"}, - {CombatDamage.Death, "COMBAT_DEATH"} + {CombatDamage.Drown, "COMBAT_DROWNDAMAGE"}, + {CombatDamage.Ice, "COMBAT_ICEDAMAGE"}, + {CombatDamage.Holy, "COMBAT_HOLYDAMAGE"}, + {CombatDamage.Death, "COMBAT_DEATHDAMAGE"} //{"undefined", CombatDamage.Undefined} }; @@ -454,17 +454,17 @@ public override bool WriteMonster(string directory, ref Monster monster) dest.WriteLine(""); dest.WriteLine("monster.elements = {"); - dest.WriteLine($" {{type = COMBAT_PHYSICALDAMAGE, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Physical)}}},"); - dest.WriteLine($" {{type = COMBAT_ENERGYDAMAGE, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Energy)}}},"); - dest.WriteLine($" {{type = COMBAT_EARTHDAMAGE, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Earth)}}},"); - dest.WriteLine($" {{type = COMBAT_FIREDAMAGE, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Fire)}}},"); - dest.WriteLine($" {{type = COMBAT_LIFEDRAIN, percent = {GenericToTfsRevScriptSysElemementPercent(monster.LifeDrain)}}},"); - dest.WriteLine($" {{type = COMBAT_MANADRAIN, percent = {GenericToTfsRevScriptSysElemementPercent(monster.ManaDrain)}}},"); - //dest.WriteLine($" {{type = COMBAT_HEALING, percent = {GenericToTfsElemementPercent(monster.XXXX)}}},"); - dest.WriteLine($" {{type = COMBAT_DROWNDAMAGE, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Drown)}}},"); - dest.WriteLine($" {{type = COMBAT_ICEDAMAGE, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Ice)}}},"); - dest.WriteLine($" {{type = COMBAT_HOLYDAMAGE , percent = {GenericToTfsRevScriptSysElemementPercent(monster.Holy)}}},"); - dest.WriteLine($" {{type = COMBAT_DEATHDAMAGE , percent = {GenericToTfsRevScriptSysElemementPercent(monster.Death)}}}"); + dest.WriteLine($" {{type = {CombatDamageNames[CombatDamage.Physical]}, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Physical)}}},"); + dest.WriteLine($" {{type = {CombatDamageNames[CombatDamage.Energy]}, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Energy)}}},"); + dest.WriteLine($" {{type = {CombatDamageNames[CombatDamage.Earth]}, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Earth)}}},"); + dest.WriteLine($" {{type = {CombatDamageNames[CombatDamage.Fire]}, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Fire)}}},"); + dest.WriteLine($" {{type = {CombatDamageNames[CombatDamage.LifeDrain]}, percent = {GenericToTfsRevScriptSysElemementPercent(monster.LifeDrain)}}},"); + dest.WriteLine($" {{type = {CombatDamageNames[CombatDamage.ManaDrain]}, percent = {GenericToTfsRevScriptSysElemementPercent(monster.ManaDrain)}}},"); + //dest.WriteLine($" {{type = { CombatDamageNames[CombatDamage.Healing]}, percent = {GenericToTfsElemementPercent(monster.XXXX)}}},"); + dest.WriteLine($" {{type = {CombatDamageNames[CombatDamage.Drown]}, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Drown)}}},"); + dest.WriteLine($" {{type = {CombatDamageNames[CombatDamage.Ice]}, percent = {GenericToTfsRevScriptSysElemementPercent(monster.Ice)}}},"); + dest.WriteLine($" {{type = {CombatDamageNames[CombatDamage.Holy]} , percent = {GenericToTfsRevScriptSysElemementPercent(monster.Holy)}}},"); + dest.WriteLine($" {{type = {CombatDamageNames[CombatDamage.Death]} , percent = {GenericToTfsRevScriptSysElemementPercent(monster.Death)}}}"); dest.WriteLine("}"); dest.WriteLine(""); @@ -495,12 +495,10 @@ double GenericToTfsRevScriptSysElemementPercent(double percent) public string GenericToTfsRevScriptSysSpells(ref Spell spell) { - string attack; + string attack = $" {{name =\"{spell.Name}\", interval = {spell.Interval}, chance = {spell.Chance * 100:0}"; if (spell.Name == "melee") { - attack = $" {{name =\"combat\", interval = {spell.Interval}, chance = {spell.Chance}"; - if ((spell.MinDamage != null) && (spell.MaxDamage != null)) { attack += $", minDamage = {spell.MinDamage}, maxDamage = {spell.MaxDamage}"; @@ -517,116 +515,76 @@ public string GenericToTfsRevScriptSysSpells(ref Spell spell) attack += $", effect = {magicEffectNames[Effect.DrawBlood]}"; - // Conditions only ever appear on melee damage? - if (spell.Condition != null) + if (spell.Condition != ConditionType.None) { - attack += $", condition = {{type = {ConditioToTFsConstants[(Condition)spell.Condition]}, startDamage = {spell.StartDamage}, interval = {spell.Tick}}}"; + attack += $", condition = {{type = {ConditionToTfsConstant[spell.Condition]}, startDamage = {spell.StartDamage}, interval = {spell.Tick}}}"; } } - else if (spell.Name == "speed") + else { - attack = $" {{name =\"{spell.Name}\", interval = {spell.Interval}, chance = {spell.Chance}"; + if (spell.Name == "speed") + { + attack += $", speed = {{min = {spell.MinSpeedChange}, max = {spell.MaxSpeedChange}}}"; + } + else if (spell.Name == "condition") + { + attack += $", type = {ConditionToTfsConstant[spell.Condition]}, startDamage = {spell.StartDamage}, tick = {spell.Tick}"; + } + else if (spell.Name == "outfit") + { + if (!string.IsNullOrEmpty(spell.MonsterName)) + { + attack += $", monster = \"{spell.MonsterName}\""; + } + else if (spell.ItemId != null) + { + attack += $", item = {spell.ItemId}"; + } + } + else if ((spell.Name == "combat") && (spell.DamageElement != null)) + { + attack += $", type = {CombatDamageNames[(CombatDamage)spell.DamageElement]}"; + } + else if (spell.Name == "drunk") + { + attack += $", drunkenness = {spell.Drunkenness * 100}"; + } - if ((spell.SpeedChange != null) && (spell.Duration != null)) + if ((spell.MinDamage != null) && (spell.MaxDamage != null)) { - attack += $", SpeedChange = {spell.SpeedChange}, Duration = {spell.Duration}"; + attack += $", minDamage = {spell.MinDamage}, maxDamage = {spell.MaxDamage}"; } - } - else if (spell.Name.Contains("invisible")) - { - attack = $" {{name =\"{spell.Name}\", interval = {spell.Interval}, chance = {spell.Chance}"; - if (spell.AreaEffect != null) + else if (spell.MaxDamage != null) { - attack += $", effect = {magicEffectNames[(Effect)spell.AreaEffect]}"; + attack += $", minDamage = {spell.MinDamage}"; } - } - else - { - if (spell.Name.Contains("field") || - spell.Name.Contains("drunk") || - spell.Name.Contains("outfit")) + if (spell.Duration != null) { - attack = $" {{name =\"{spell.Name}\", interval = {spell.Interval}, chance = {spell.Chance}"; + attack += $", duration = {spell.Duration}"; } - else if (spell.Name.Contains("condition")) + if (spell.Range != null) { - string condition = spell.Name.Replace("condition", ""); - attack = $" {{name =\"{condition}\", interval = {spell.Interval}, chance = {spell.Chance}"; + attack += $", range = {spell.Range}"; } - else + if (spell.Radius != null) { - attack = $" {{name =\"combat\", interval = {spell.Interval}, chance = {spell.Chance}"; + attack += $", radius = {spell.Radius}, target = {spell.OnTarget.ToString().ToLower()}"; } - if (spell.Name != "outfit") + if (spell.Length != null) { - if ((spell.MinDamage != null) && (spell.MaxDamage != null)) - { - attack += $", minDamage = {spell.MinDamage}, maxDamage = {spell.MaxDamage}"; - } - else if (spell.MaxDamage != null) - { - attack += $", minDamage = {spell.MinDamage}"; - } - if (spell.DamageElement != null) - { - attack += $", type = {CombatDamageNames[(CombatDamage)spell.DamageElement]}"; - } - if (spell.Range != null) - { - attack += $", range = {spell.Range}"; - } - if (spell.Radius != null) - { - attack += $", radius = {spell.Radius}"; - } - if (spell.Length != null) - { - attack += $", length = {spell.Length}"; - } - if (spell.Spread != null) - { - attack += $", spread = {spell.Spread}"; - } - if (spell.ShootEffect != null) - { - attack += $", ShootEffect = {shootTypeNames[(Animation)spell.ShootEffect]}"; - } - if (spell.AreaEffect != null) - { - attack += $", effect = {magicEffectNames[(Effect)spell.AreaEffect]}"; - } - if (spell.Target != null) - { - attack += $", target = {spell.Target.ToString().ToLower()}"; - } - if (spell.Duration != null) - { - attack += $", duration = {spell.Duration}"; - } - if (spell.Name.Contains("condition")) - { - // Not implemented? - if (spell.Tick != null) - { - attack += $", tick = {spell.Tick}"; - } - // Not implemented? - if (spell.StartDamage != null) - { - attack += $", start = {spell.StartDamage}"; - } - } - if (spell.Name == "outfit") - { - if (!string.IsNullOrEmpty(spell.MonsterName)) - { - attack += $", monster = \"{spell.MonsterName}\""; - } - else if (spell.ItemId != null) - { - attack += $", item = {spell.ItemId}"; - } - } + attack += $", length = {spell.Length}"; + } + if (spell.Spread != null) + { + attack += $", spread = {spell.Spread}"; + } + if (spell.ShootEffect != Animation.None) + { + attack += $", ShootEffect = {shootTypeNames[spell.ShootEffect]}"; + } + if (spell.AreaEffect != Effect.None) + { + attack += $", effect = {magicEffectNames[spell.AreaEffect]}"; } } attack += "}"; diff --git a/app/MonsterConverterTfsXml/SerializerClasses.cs b/app/MonsterConverterTfsXml/SerializerClasses.cs new file mode 100644 index 00000000..c52b738c --- /dev/null +++ b/app/MonsterConverterTfsXml/SerializerClasses.cs @@ -0,0 +1,373 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace MonsterConverterTfsXml +{ + public enum namedImmunityXml + { + physical, + energy, + fire, + poison, + earth = namedImmunityXml.poison, + drown, + ice, + holy, + death, + lifedrain, + manadrain, + paralyze, + outfit, + drunk, + invisible, + invisibility = namedImmunityXml.invisible, + bleed, + NA + } + + enum skullsXml + { + none = 0, + yellow, + green, + white, + red, + black, + orange + } + + [Serializable, XmlRoot("monster")] + public class TFSXmlMonster + { + [XmlAttribute] + public string name; + [XmlAttribute] + public string nameDescription; + [XmlAttribute] + public string namedescription; + [XmlAttribute] + public string race = "blood"; + [XmlAttribute] + public int experience = 0; + [XmlAttribute] + public int speed = 200; + [XmlAttribute] + public int manacost = 0; + [XmlAttribute] + public string skull = "none"; //uses strings, "none", "yellow", "green", "white", "red", "black", "orange" + //[XmlAttribute] + //public int script; //todo: how to handle + + public TFSXmlHealth health; + public Flags flags; + public Look look; + public TargetChange targetchange; + public Attacks attacks; + public Defenses defenses; + public Immunities immunities; + public Voices voices; + public TfsXmlLoot loot; + public Elements elements; + public TFSXmlSummons summons; + } + + [XmlRoot(ElementName = "health")] + public class TFSXmlHealth + { + [XmlAttribute] + public int now = 100; + [XmlAttribute] + public int max = 100; + } + + [XmlRoot(ElementName = "flags")] + public class Flags + { + [XmlElementAttribute] + public MultiAttr[] flag; + } + + public class MultiAttr + { + [XmlAnyAttribute] + public XmlAttribute[] attr; + } + + public class TargetChange + { + [XmlAttribute] + public int interval = 0; //interval and speed are the same, default is 0 + [XmlAttribute] + public int speed = 0; //interval and speed are the same, default is 0 + [XmlAttribute] + public int chance = 0; //default is 0 + } + + public class Look + { + [XmlAttribute] + public int type = 0; + [XmlAttribute] + public int head = 0; //only can exist if type exists + [XmlAttribute] + public int body = 0; //only can exist if type exists + [XmlAttribute] + public int legs = 0; //only can exist if type exists + [XmlAttribute] + public int feet = 0; //only can exist if type exists + [XmlAttribute] + public int addons = 0; //only can exist if type exists + [XmlAttribute] + public int typeex = 0; + [XmlAttribute] + public int mount = 0; + [XmlAttribute] + public int corpse = 0; + } + + public class TfsXmlSpellAttributes + { + [XmlAttribute] + public string key { get; set; } + + [XmlAttribute] + public string value { get; set; } + } + + public class Attacks + { + [XmlElementAttribute] + public Attack[] attack; + } + + public class Attack + { + // only script or name not both + [XmlAttribute] + public string script; + [XmlAttribute] + public string name; + + // Only one should exist, they represent the same information + [XmlAttribute] + public int interval = 0; //defaults to 2000 if missing, default is handled in parsing + [XmlAttribute] + public int speed = 0; //defaults to 2000 if missing, default is handled in parsing + + [XmlAttribute] + public int chance = 100; //defaults to 100 if missing + [XmlAttribute] + public int range = 0; //defaults to 0 if missing + [XmlAttribute] + public int min = 0; //defaults to 0 if missing + [XmlAttribute] + public int max = 0; //defaults to 0 if missing + [XmlAttribute] + public int length = 0; //if length exists spread defaults to 3 + [XmlAttribute] + public int spread = -1; //if length exists spread defaults to 3, default is handled in parsing + [XmlAttribute] + public int radius = 0; + [XmlAttribute] + public int direction; // 0 or 1 script only + [XmlAttribute] + public int target = 0; // Defaults to 0 if missing, used by script too + + [XmlAttribute] + public int speedchange = 0; + [XmlAttribute] + public int minspeedchange = 0; + [XmlAttribute] + public int maxspeedchange = 0; + [XmlAttribute] + public int duration = 10000; + [XmlAttribute] + public int drunkenness = 25; + + [XmlElementAttribute(ElementName = "attribute")] + public TfsXmlSpellAttributes[] attribute { get; set; } + + // the following only exist when attack name is melee + // when melee exists minMax and Max are set to 0 + [XmlAttribute] + public int skill; + [XmlAttribute] + public int attack; + [XmlAttribute] + public int fire; + [XmlAttribute] + public int poison; + [XmlAttribute] + public int energy; + [XmlAttribute] + public int drown; + [XmlAttribute] + public int freeze; + [XmlAttribute] + public int dazzle; + [XmlAttribute] + public int curse; + [XmlAttribute] + public int bleed; //bleed and physical are the same + [XmlAttribute] + public int physical; //bleed and physical are the same + + [XmlAttribute] + public int tick; //only used if a condition is set each type has its own default tick which can be overriden with this attr + [XmlAttribute] + public int start; //Start condition damage + + [XmlAttribute] + public string monster; + [XmlAttribute] + public int item; + } + + public class Defenses + { + [XmlAttribute] + public uint defense; + [XmlAttribute] + public uint armor; + + [XmlElementAttribute(ElementName = "defense")] + public Attack[] defenses; + } + + public class Immunities + { + [XmlElementAttribute] + public Immunity[] immunity; + } + + public class Immunity + { + [XmlAttribute] + public namedImmunityXml name = namedImmunityXml.NA; + [XmlAttribute] + public int physical = 0; //Immune to physical and bleeding condition + [XmlAttribute] + public int energy = 0; + [XmlAttribute] + public int fire = 0; + [XmlAttribute] + public int poison = 0; //poison and earth are the same + [XmlAttribute] + public int earth = 0; //poison and earth are the same + [XmlAttribute] + public int drown = 0; + [XmlAttribute] + public int ice = 0; + [XmlAttribute] + public int holy = 0; + [XmlAttribute] + public int death = 0; + [XmlAttribute] + public int lifedrain = 0; + [XmlAttribute] + public int manadrain = 0; + [XmlAttribute] + public int paralyze = 0; + [XmlAttribute] + public int outfit = 0; // TODO should be true by default? + [XmlAttribute] + public int bleed = 0; // immue to only bleed condition + [XmlAttribute] + public int drunk = 0; + [XmlAttribute] + public int invisible = 0; //invisible and invisibility are the same + [XmlAttribute] + public int invisibility = 0; //invisible and invisibility are the same + } + + public class Voices + { + [XmlAttribute] + public int interval; //interval and speed are the same + [XmlAttribute] + public int speed; //interval and speed are the same + [XmlAttribute] + public int chance; + [XmlElementAttribute] + public VoiceXml[] voice; + } + + public class VoiceXml + { + [XmlAttribute] + public string sentence; + /// + /// Can be 1 or true + /// if it doesnt exist the value is false + /// + [XmlAttribute] + public string yell; + } + + [Serializable, XmlRoot("Loot")] + public class TfsXmlLoot + { + [XmlElementAttribute] + public Item[] item; + } + + public class Item + { + // Only name or ID will be used not both + [XmlAttribute] + public string name; + [XmlAttribute] + public int id; + [XmlAttribute] + public int countmax = 1; //default value is 1 + [XmlAttribute] + public int chance; //chance and chance1 are the same + [XmlAttribute] + public int chance1; //chance and chance1 are the same + + //optional + //[XmlAttribute] + //public int subtype; //used for charges? + //[XmlAttribute] + //public int actionId; + //[XmlAttribute] + //public string test; //used for? //Id guess to override the default item name string? + } + + public class Elements + { + [XmlElementAttribute] + public MultiAttr[] element; + } + + [XmlRoot(ElementName = "summons")] + public class TFSXmlSummons + { + [XmlAttribute] + public int maxSummons; + [XmlElementAttribute] + public TFSXmlSummon[] summon; + } + + [XmlRoot(ElementName = "summon")] + public class TFSXmlSummon + { + [XmlAttribute] + public string name; + [XmlAttribute] + public int interval = 1000; //interval and speed are the same //defaults to 1000 if missing + [XmlAttribute] + public int speed = 1000; //interval and speed are the same //defaults to 1000 if missing + [XmlAttribute] + public double chance = 100; //defaults to 100 if missing + [XmlAttribute] + public int max; + [XmlAttribute] + public bool force; + } +} diff --git a/app/MonsterConverterTfsXml/TfsXmlConverter.cs b/app/MonsterConverterTfsXml/TfsXmlConverter.cs index 41d4bac7..fc9f4831 100644 --- a/app/MonsterConverterTfsXml/TfsXmlConverter.cs +++ b/app/MonsterConverterTfsXml/TfsXmlConverter.cs @@ -4,8 +4,6 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; -using System.Text; -using System.Xml; using System.Xml.Linq; using System.Xml.Serialization; @@ -20,7 +18,7 @@ public class TfsXmlConverter : MonsterConverter const uint MAX_LOOTCHANCE = 100000; const uint ATTACK_INTERVAL_DEFAULT = 2000; - IDictionary magicEffectNames = new Dictionary + private readonly IDictionary magicEffectNames = new Dictionary { {"redspark", Effect.DrawBlood}, {"bluebubble", Effect.LoseEnergy}, @@ -105,7 +103,7 @@ public class TfsXmlConverter : MonsterConverter {"purplesmoke", Effect.PurpleSmoke} }; - IDictionary shootTypeNames = new Dictionary + private readonly IDictionary shootTypeNames = new Dictionary { {"spear", Animation.Spear}, {"bolt", Animation.Bolt}, @@ -159,7 +157,7 @@ public class TfsXmlConverter : MonsterConverter {"simplearrow", Animation.SimpleArrow} }; - IDictionary CombatDamageNames = new Dictionary + private readonly IDictionary combatDamageNames = new Dictionary { {"physical", CombatDamage.Physical}, {"energy", CombatDamage.Energy}, @@ -175,6 +173,35 @@ public class TfsXmlConverter : MonsterConverter //{"undefined", CombatDamage.Undefined} }; + private readonly IDictionary conditionDamageNames = new Dictionary + { + {"physicalcondition", ConditionType.Bleeding}, + {"bleedcondition", ConditionType.Bleeding}, + {"energycondition", ConditionType.Energy}, + {"poisoncondition", ConditionType.Poison}, + {"earthcondition", ConditionType.Poison}, + {"firecondition", ConditionType.Fire}, + {"drowncondition", ConditionType.Drown}, + {"icecondition", ConditionType.Freezing}, + {"freezecondition", ConditionType.Freezing}, + {"holycondition", ConditionType.Dazzled}, + {"dazzledcondition", ConditionType.Dazzled}, + {"cursecondition", ConditionType.Cursed}, + {"deathcondition", ConditionType.Cursed} + }; + + private readonly IDictionary conditionDefaultTick = new Dictionary + { + {ConditionType.Bleeding, 4000}, + {ConditionType.Energy, 10000}, + {ConditionType.Fire, 9000}, + {ConditionType.Poison, 4000}, + {ConditionType.Drown, 5000}, + {ConditionType.Freezing, 8000}, + {ConditionType.Dazzled, 10000}, + {ConditionType.Cursed, 4000}, + }; + public override string FileExt { get => "xml"; } public override bool IsReadSupported { get => true; } @@ -186,8 +213,8 @@ public override bool ReadMonster(string filename, out Monster monster) { XmlSerializer serializer = new XmlSerializer(typeof(TFSXmlMonster)); - serializer.UnknownNode += new XmlNodeEventHandler(serializer_UnknownNode); - serializer.UnknownAttribute += new XmlAttributeEventHandler(serializer_UnknownAttribute); + serializer.UnknownNode += new XmlNodeEventHandler(Serializer_UnknownNode); + serializer.UnknownAttribute += new XmlAttributeEventHandler(Serializer_UnknownAttribute); // A FileStream is needed to read the XML document. FileStream fs = new FileStream(filename, FileMode.Open); @@ -226,7 +253,7 @@ private void xmlToGeneric(TFSXmlMonster tfsMonster, out Monster monster) Health = (uint)tfsMonster.health.max, Experience = (uint)tfsMonster.experience, Speed = (uint)tfsMonster.speed, - Race = tfsToGenericBlood(tfsMonster.race), + Race = TfsToGenericBlood(tfsMonster.race), }; if (!string.IsNullOrEmpty(tfsMonster.nameDescription)) @@ -430,47 +457,47 @@ private void xmlToGeneric(TFSXmlMonster tfsMonster, out Monster monster) { if (x.attr[0].Name == "physicalPercent") { - monster.Physical = tfstoGenericElementalPercent(value); + monster.Physical = TfstoGenericElementalPercent(value); } else if (x.attr[0].Name == "icePercent") { - monster.Ice = tfstoGenericElementalPercent(value); + monster.Ice = TfstoGenericElementalPercent(value); } else if (x.attr[0].Name == "poisonPercent") { - monster.Earth = tfstoGenericElementalPercent(value); + monster.Earth = TfstoGenericElementalPercent(value); } else if (x.attr[0].Name == "earthPercent") { - monster.Earth = tfstoGenericElementalPercent(value); + monster.Earth = TfstoGenericElementalPercent(value); } else if (x.attr[0].Name == "firePercent") { - monster.Fire = tfstoGenericElementalPercent(value); + monster.Fire = TfstoGenericElementalPercent(value); } else if (x.attr[0].Name == "energyPercent") { - monster.Energy = tfstoGenericElementalPercent(value); + monster.Energy = TfstoGenericElementalPercent(value); } else if (x.attr[0].Name == "holyPercent") { - monster.Holy = tfstoGenericElementalPercent(value); + monster.Holy = TfstoGenericElementalPercent(value); } else if (x.attr[0].Name == "deathPercent") { - monster.Death = tfstoGenericElementalPercent(value); + monster.Death = TfstoGenericElementalPercent(value); } else if (x.attr[0].Name == "drownPercent") { - monster.Drown = tfstoGenericElementalPercent(value); + monster.Drown = TfstoGenericElementalPercent(value); } else if (x.attr[0].Name == "lifedrainPercent") { - monster.LifeDrain = tfstoGenericElementalPercent(value); + monster.LifeDrain = TfstoGenericElementalPercent(value); } else if (x.attr[0].Name == "manadrainPercent") { - monster.ManaDrain = tfstoGenericElementalPercent(value); + monster.ManaDrain = TfstoGenericElementalPercent(value); } } } @@ -645,7 +672,7 @@ private void xmlToGeneric(TFSXmlMonster tfsMonster, out Monster monster) } } - private Blood tfsToGenericBlood(string blood) + private Blood TfsToGenericBlood(string blood) { Blood race = Blood.blood; //default @@ -726,7 +753,43 @@ private void XmlSpellsToGeneric(ref Monster monster, Attack[] spells, SpellCateg spell.Interval = ATTACK_INTERVAL_DEFAULT; } - spell.Chance = (uint)attack.chance; + spell.Chance = attack.chance / 100.0; + + if (attack.attribute != null) + { + foreach (var attr in attack.attribute) + { + if (attr.key.ToLower() == "shootEffect".ToLower()) + { + spell.ShootEffect = shootTypeNames[attr.value.ToLower()]; + } + else if (attr.key.ToLower() == "areaEffect".ToLower()) + { + spell.AreaEffect = magicEffectNames[attr.value.ToLower()]; + } + else + { + Console.WriteLine($"Unkown attack attribute {attr.key}"); + } + } + } + + if (attack.range > 0) + { + spell.Range = (uint?)attack.range; + } + + if (attack.length > 0) + { + spell.Length = (uint?)attack.length; + spell.Spread = (attack.spread == -1) ? 3 : (uint?)attack.spread; + } + + if (attack.radius > 0) + { + spell.Radius = (uint?)attack.radius; + spell.OnTarget = (attack.target == 1); + } if (attack.name == "melee") { @@ -743,64 +806,81 @@ private void XmlSpellsToGeneric(ref Monster monster, Attack[] spells, SpellCateg if (attack.fire != 0) { - spell.Condition = Condition.Fire; + spell.Condition = ConditionType.Fire; spell.StartDamage = attack.poison; - spell.Tick = (attack.tick != 0) ? attack.tick : 9000; } else if (attack.poison != 0) { - spell.Condition = Condition.Poison; + spell.Condition = ConditionType.Poison; spell.StartDamage = attack.poison; - spell.Tick = (attack.tick != 0) ? attack.tick : 4000; } else if (attack.energy != 0) { - spell.Condition = Condition.Energy; + spell.Condition = ConditionType.Energy; spell.StartDamage = attack.energy; - spell.Tick = (attack.tick != 0) ? attack.tick : 10000; } else if (attack.drown != 0) { - spell.Condition = Condition.Drown; + spell.Condition = ConditionType.Drown; spell.StartDamage = attack.drown; - spell.Tick = (attack.tick != 0) ? attack.tick : 5000; } else if (attack.dazzle != 0) { - spell.Condition = Condition.Dazzled; + spell.Condition = ConditionType.Dazzled; spell.StartDamage = attack.dazzle; - spell.Tick = (attack.tick != 0) ? attack.tick : 10000; } else if (attack.curse != 0) { - spell.Condition = Condition.Cursed; + spell.Condition = ConditionType.Cursed; spell.StartDamage = attack.curse; - spell.Tick = (attack.tick != 0) ? attack.tick : 4000; } else if (attack.bleed != 0) { - spell.Condition = Condition.Bleeding; + spell.Condition = ConditionType.Bleeding; spell.StartDamage = attack.bleed; - spell.Tick = (attack.tick != 0) ? attack.tick : 4000; } else if (attack.physical != 0) { - spell.Condition = Condition.Bleeding; + spell.Condition = ConditionType.Bleeding; spell.StartDamage = attack.physical; - spell.Tick = (attack.tick != 0) ? attack.tick : 4000; + } + else if (attack.freeze != 0) + { + spell.Condition = ConditionType.Freezing; + spell.StartDamage = attack.freeze; + } + if (spell.Condition != ConditionType.None) + { + spell.Tick = (attack.tick != 0) ? attack.tick : conditionDefaultTick[spell.Condition]; } } else if (attack.name == "speed") { - spell.SpeedChange = attack.speedchange; - spell.Duration = attack.duration; - if (attack.duration == 0) + if (attack.speedchange != 0) { - spell.Duration = 10000; // Default when no duration set + spell.MinSpeedChange = attack.speedchange; + spell.MaxSpeedChange = attack.speedchange; } + else + { + spell.MinSpeedChange = attack.minspeedchange; + spell.MaxSpeedChange = attack.maxspeedchange; + } + } + else if (attack.name == "drunk") + { + spell.Drunkenness = attack.drunkenness / 100.0; } else { + if (attack.name.Contains("condition")) + { + spell.Condition = conditionDamageNames[attack.name]; + spell.Name = "condition"; + spell.Tick = (attack.tick != 0) ? attack.tick : conditionDefaultTick[spell.Condition]; + spell.StartDamage = attack.start; + } + // Always default both if max is included to be explicit // Some spells don't have damage so don't include either of them if (attack.max != 0) @@ -809,44 +889,10 @@ private void XmlSpellsToGeneric(ref Monster monster, Attack[] spells, SpellCateg spell.MaxDamage = attack.max; } - if (attack.attribute != null) - { - foreach (var attr in attack.attribute) - { - if (attr.key.ToLower() == "shootEffect".ToLower()) - { - spell.ShootEffect = shootTypeNames[attr.value.ToLower()]; - } - else if (attr.key.ToLower() == "areaEffect".ToLower()) - { - spell.AreaEffect = magicEffectNames[attr.value.ToLower()]; - } - else - { - Console.WriteLine($"Unkown attack attribute {attr.key}"); - } - } - } - - if (attack.range > 0) - { - spell.Range = (uint?)attack.range; - } - - if (attack.length > 0) - { - spell.Length = (uint?)attack.length; - spell.Spread = (uint?)attack.spread; - if ((spell.Length > 3) && (spell.Spread == 0)) - { - spell.Spread = 3; - } - } - spell.Target = (attack.target == 1); - - if (CombatDamageNames.ContainsKey(spell.Name)) + if (combatDamageNames.ContainsKey(spell.Name)) { - spell.DamageElement = CombatDamageNames[spell.Name]; + spell.DamageElement = combatDamageNames[spell.Name]; + spell.Name = "combat"; } if (!string.IsNullOrEmpty(attack.monster)) @@ -859,12 +905,17 @@ private void XmlSpellsToGeneric(ref Monster monster, Attack[] spells, SpellCateg } } + if ((attack.name == "speed") || (attack.name == "outfit") || (attack.name == "invisible") || (attack.name == "drunk")) + { + spell.Duration = attack.duration; + } + monster.Attacks.Add(spell); } } } - private string generictoTfsBlood(Blood race) + private string GenerictoTfsBlood(Blood race) { string bloodName = "blood"; @@ -894,377 +945,20 @@ private string generictoTfsBlood(Blood race) return bloodName; } - private double tfstoGenericElementalPercent(int percent) + private double TfstoGenericElementalPercent(int percent) { return (1 - ((double)percent / 100)); } - private void serializer_UnknownNode(object sender, XmlNodeEventArgs e) + private void Serializer_UnknownNode(object sender, XmlNodeEventArgs e) { System.Diagnostics.Debug.WriteLine("Unknown Node:" + e.Name + "\t" + e.Text); } - private void serializer_UnknownAttribute(object sender, XmlAttributeEventArgs e) + private void Serializer_UnknownAttribute(object sender, XmlAttributeEventArgs e) { System.Xml.XmlAttribute attr = e.Attr; System.Diagnostics.Debug.WriteLine("Unknown attribute " + attr.Name + "='" + attr.Value + "'"); } } - - #region XML serializer classes - public enum namedImmunityXml - { - physical, - energy, - fire, - poison, - earth = namedImmunityXml.poison, - drown, - ice, - holy, - death, - lifedrain, - manadrain, - paralyze, - outfit, - drunk, - invisible, - invisibility = namedImmunityXml.invisible, - bleed, - NA - } - - enum skullsXml - { - none = 0, - yellow, - green, - white, - red, - black, - orange - } - - [Serializable, XmlRoot("monster")] - public class TFSXmlMonster - { - [XmlAttribute] - public string name; - [XmlAttribute] - public string nameDescription; - [XmlAttribute] - public string namedescription; - [XmlAttribute] - public string race = "blood"; - [XmlAttribute] - public int experience = 0; - [XmlAttribute] - public int speed = 200; - [XmlAttribute] - public int manacost = 0; - [XmlAttribute] - public string skull = "none"; //uses strings, "none", "yellow", "green", "white", "red", "black", "orange" - //[XmlAttribute] - //public int script; //todo: how to handle - - public TFSXmlHealth health; - public Flags flags; - public Look look; - public TargetChange targetchange; - public Attacks attacks; - public Defenses defenses; - public Immunities immunities; - public Voices voices; - public TfsXmlLoot loot; - public Elements elements; - public TFSXmlSummons summons; - } - - [XmlRoot(ElementName = "health")] - public class TFSXmlHealth - { - [XmlAttribute] - public int now = 100; - [XmlAttribute] - public int max = 100; - } - - [XmlRoot(ElementName = "flags")] - public class Flags - { - [XmlElementAttribute] - public MultiAttr[] flag; - } - - public class MultiAttr - { - [XmlAnyAttribute] - public XmlAttribute[] attr; - } - - public class TargetChange - { - [XmlAttribute] - public int interval = 0; //interval and speed are the same, default is 0 - [XmlAttribute] - public int speed = 0; //interval and speed are the same, default is 0 - [XmlAttribute] - public int chance = 0; //default is 0 - } - - public class Look - { - [XmlAttribute] - public int type = 0; - [XmlAttribute] - public int head = 0; //only can exist if type exists - [XmlAttribute] - public int body = 0; //only can exist if type exists - [XmlAttribute] - public int legs = 0; //only can exist if type exists - [XmlAttribute] - public int feet = 0; //only can exist if type exists - [XmlAttribute] - public int addons = 0; //only can exist if type exists - [XmlAttribute] - public int typeex = 0; - [XmlAttribute] - public int mount = 0; - [XmlAttribute] - public int corpse = 0; - } - - public class TfsXmlSpellAttributes - { - [XmlAttribute] - public string key { get; set; } - - [XmlAttribute] - public string value { get; set; } - } - - public class Attacks - { - [XmlElementAttribute] - public Attack[] attack; - } - - public class Attack - { - // only script or name not both - //[XmlAttribute] - //public string script; - [XmlAttribute] - public string name; - - // Only one should exist, they represent the same information - [XmlAttribute] - public int interval = 0; //defaults to 2000 if missing, default is handled in parsing - [XmlAttribute] - public int speed = 0; //defaults to 2000 if missing, default is handled in parsing - - [XmlAttribute] - public int chance = 100; //defaults to 100 if missing - [XmlAttribute] - public int range = 0; //defaults to 0 if missing - [XmlAttribute] - public int min = 0; //defaults to 0 if missing - [XmlAttribute] - public int max = 0; //defaults to 0 if missing - [XmlAttribute] - public int length = 0; //if length exists spread defaults to 3 - [XmlAttribute] - public int spread = 0; //if length exists spread defaults to 3 - [XmlAttribute] - public int radius = 0; - [XmlAttribute] - public int target = 0; // Defaults to 0 if missing - - [XmlAttribute] - public int speedchange = 0; - [XmlAttribute] - public int duration = 0; - - [XmlElementAttribute(ElementName = "attribute")] - public TfsXmlSpellAttributes[] attribute { get; set; } - - // the following only exist when attack name is melee - // when melee exists minMax and Max are set to 0 - [XmlAttribute] - public int skill; - [XmlAttribute] - public int attack; - [XmlAttribute] - public int fire; - [XmlAttribute] - public int poison; - [XmlAttribute] - public int energy; - [XmlAttribute] - public int drown; - [XmlAttribute] - public int freeze; - [XmlAttribute] - public int dazzle; - [XmlAttribute] - public int curse; - [XmlAttribute] - public int bleed; //bleed and physical are the same - [XmlAttribute] - public int physical; //bleed and physical are the same - - [XmlAttribute] - public int tick; //only used if a condition is set each type has its own default tick which can be overriden with this attr - [XmlAttribute] - public int start; //Start condition damage - - [XmlAttribute] - public string monster; - [XmlAttribute] - public int item; - } - - public class Defenses - { - [XmlAttribute] - public uint defense; - [XmlAttribute] - public uint armor; - - [XmlElementAttribute(ElementName = "defense")] - public Attack[] defenses; - } - - public class Immunities - { - [XmlElementAttribute] - public Immunity[] immunity; - } - - public class Immunity - { - [XmlAttribute] - public namedImmunityXml name = namedImmunityXml.NA; - [XmlAttribute] - public int physical = 0; //Immune to physical and bleeding condition - [XmlAttribute] - public int energy = 0; - [XmlAttribute] - public int fire = 0; - [XmlAttribute] - public int poison = 0; //poison and earth are the same - [XmlAttribute] - public int earth = 0; //poison and earth are the same - [XmlAttribute] - public int drown = 0; - [XmlAttribute] - public int ice = 0; - [XmlAttribute] - public int holy = 0; - [XmlAttribute] - public int death = 0; - [XmlAttribute] - public int lifedrain = 0; - [XmlAttribute] - public int manadrain = 0; - [XmlAttribute] - public int paralyze = 0; - [XmlAttribute] - public int outfit = 0; // TODO should be true by default? - [XmlAttribute] - public int bleed = 0; // immue to only bleed condition - [XmlAttribute] - public int drunk = 0; - [XmlAttribute] - public int invisible = 0; //invisible and invisibility are the same - [XmlAttribute] - public int invisibility = 0; //invisible and invisibility are the same - } - - public class Voices - { - [XmlAttribute] - public int interval; //interval and speed are the same - [XmlAttribute] - public int speed; //interval and speed are the same - [XmlAttribute] - public int chance; - [XmlElementAttribute] - public VoiceXml[] voice; - } - - public class VoiceXml - { - [XmlAttribute] - public string sentence; - /// - /// Can be 1 or true - /// if it doesnt exist the value is false - /// - [XmlAttribute] - public string yell; - } - - [Serializable, XmlRoot("Loot")] - public class TfsXmlLoot - { - [XmlElementAttribute] - public Item[] item; - } - - public class Item - { - // Only name or ID will be used not both - [XmlAttribute] - public string name; - [XmlAttribute] - public int id; - [XmlAttribute] - public int countmax = 1; //default value is 1 - [XmlAttribute] - public int chance; //chance and chance1 are the same - [XmlAttribute] - public int chance1; //chance and chance1 are the same - - //optional - //[XmlAttribute] - //public int subtype; //used for charges? - //[XmlAttribute] - //public int actionId; - //[XmlAttribute] - //public string test; //used for? //Id guess to override the fault item name string? - } - - public class Elements - { - [XmlElementAttribute] - public MultiAttr[] element; - } - - [XmlRoot(ElementName = "summons")] - public class TFSXmlSummons - { - [XmlAttribute] - public int maxSummons; - [XmlElementAttribute] - public TFSXmlSummon[] summon; - } - - [XmlRoot(ElementName = "summon")] - public class TFSXmlSummon - { - [XmlAttribute] - public string name; - [XmlAttribute] - public int interval = 1000; //interval and speed are the same //defaults to 1000 if missing - [XmlAttribute] - public int speed = 1000; //interval and speed are the same //defaults to 1000 if missing - [XmlAttribute] - public double chance = 100; //defaults to 100 if missing - [XmlAttribute] - public int max; - [XmlAttribute] - public bool force; - } - #endregion } diff --git a/app/MonsterConverterTibiaWiki/TibiaWikiConverter.cs b/app/MonsterConverterTibiaWiki/TibiaWikiConverter.cs index 74878b89..1f081302 100644 --- a/app/MonsterConverterTibiaWiki/TibiaWikiConverter.cs +++ b/app/MonsterConverterTibiaWiki/TibiaWikiConverter.cs @@ -166,14 +166,14 @@ private static bool ParseAbilities(Monster mon, MatchCollection mc) case var _ when new Regex(@"\[\[haste\]\]").IsMatch(cleanability): { - var spell = new Spell() { Name = "speed", SpellCategory = SpellCategory.Defensive, Interval = 2000, Chance = 15, SpeedChange = 300, AreaEffect = Effect.MagicRed, Duration = 7000 }; + var spell = new Spell() { Name = "speed", SpellCategory = SpellCategory.Defensive, Interval = 2000, Chance = 15, MinSpeedChange = 300, MaxSpeedChange = 300, AreaEffect = Effect.MagicRed, Duration = 7000 }; mon.Attacks.Add(spell); break; } case var _ when new Regex(@"\[\[strong haste\]\]").IsMatch(cleanability): { - var spell = new Spell() { Name = "speed", SpellCategory = SpellCategory.Defensive, Interval = 2000, Chance = 15, SpeedChange = 450, AreaEffect = Effect.MagicRed, Duration = 4000 }; + var spell = new Spell() { Name = "speed", SpellCategory = SpellCategory.Defensive, Interval = 2000, Chance = 15, MinSpeedChange = 450, MaxSpeedChange = 450, AreaEffect = Effect.MagicRed, Duration = 4000 }; mon.Attacks.Add(spell); break; } @@ -201,7 +201,7 @@ private static bool ParseAbilities(Monster mon, MatchCollection mc) return true; } - private static Animation? TibiaWikiToAnimation(string effect) + private static Animation TibiaWikiToAnimation(string effect) { if ((effect == "spear") || (effect == "spears")) { @@ -227,7 +227,10 @@ private static bool ParseAbilities(Monster mon, MatchCollection mc) { return Animation.SmallStone; } - return null; + else + { + return Animation.None; + } } ///