// Functions
        public override ConvertResult ReadMonster(string filename, out Monster monster)
        {
            try
            {
                CurrentFileName = filename;
                XmlSerializer serializer = new XmlSerializer(typeof(TFSXmlMonster));

                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);

                // Use the Deserialize method to restore the object's state with data from the XML document.
                TFSXmlMonster tfsMonster = (TFSXmlMonster)serializer.Deserialize(fs);

                // convert from xml monster classes to generic class
                xmlToGeneric(tfsMonster, out monster);
                monster.FileName = Path.GetFileNameWithoutExtension(filename);
                // Guess the registered name, they are actually defined in "monsters.xml" but we don't parse that file...
                monster.RegisteredName = monster.FileName.Replace('_', ' ');

                return(new ConvertResult(filename, ConvertError.Success));
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"Error pasring {filename}. Exception {ex.Message}");
                monster = null;
                return(new ConvertResult(filename, ConvertError.Error, ex.Message));
            }
        }
        private void xmlToGeneric(TFSXmlMonster tfsMonster, out Monster monster)
        {
            monster = new Monster()
            {
                Name       = tfsMonster.name,
                Health     = (uint)tfsMonster.health.max,
                Experience = (uint)tfsMonster.experience,
                Speed      = (uint)tfsMonster.speed,
                Race       = TfsToGenericBlood(tfsMonster.race),
            };

            if (!string.IsNullOrEmpty(tfsMonster.nameDescription))
            {
                monster.Description = tfsMonster.nameDescription;
            }
            if (!string.IsNullOrEmpty(tfsMonster.namedescription))
            {
                monster.Description = tfsMonster.namedescription;
            }

            if (tfsMonster.targetchange != null)
            {
                monster.RetargetChance = tfsMonster.targetchange.chance / 100.0;;

                if ((tfsMonster.targetchange.interval != 0) &&
                    (tfsMonster.targetchange.speed == 0))
                {
                    monster.RetargetInterval = (uint)tfsMonster.targetchange.interval;
                }
                else if ((tfsMonster.targetchange.interval == 0) &&
                         (tfsMonster.targetchange.speed != 0))
                {
                    monster.RetargetInterval = (uint)tfsMonster.targetchange.speed;
                }
                else if ((tfsMonster.targetchange.interval != 0) &&
                         (tfsMonster.targetchange.speed != 0))
                {
                    System.Diagnostics.Debug.WriteLine("Warning duplicate target speed and target interval");
                }
            }

            if (tfsMonster.look != null)
            {
                monster.CorpseId         = (uint)tfsMonster.look.corpse;
                monster.OutfitIdLookType = (uint)tfsMonster.look.type;
                monster.LookTypeDetails  = new DetailedLookType()
                {
                    Head   = (ushort)tfsMonster.look.head,
                    Body   = (ushort)tfsMonster.look.body,
                    Legs   = (ushort)tfsMonster.look.legs,
                    Feet   = (ushort)tfsMonster.look.feet,
                    Addons = (ushort)tfsMonster.look.addons,
                    Mount  = (ushort)tfsMonster.look.mount
                };
                monster.ItemIdLookType = (uint)tfsMonster.look.typeex;
            }

            // flags
            if ((tfsMonster.flags != null) &&
                (tfsMonster.flags.flag != null))
            {
                foreach (var x in tfsMonster.flags.flag)
                {
                    int value;
                    if (int.TryParse(x.attr[0].Value, out value))
                    {
                        if (x.attr[0].Name == "summonable")
                        {
                            monster.SummonCost = (uint)tfsMonster.manacost;
                        }
                        else if (x.attr[0].Name == "attackable")
                        {
                            monster.Attackable = value == 1;
                        }
                        else if (x.attr[0].Name == "hostile")
                        {
                            monster.Hostile = value == 1;
                        }
                        else if (x.attr[0].Name == "illusionable")
                        {
                            monster.Illusionable = value == 1;
                        }
                        else if (x.attr[0].Name == "convinceable")
                        {
                            monster.ConvinceCost = (uint)tfsMonster.manacost;
                        }
                        else if (x.attr[0].Name == "pushable")
                        {
                            monster.Pushable = value == 1;
                        }
                        else if (x.attr[0].Name == "canpushitems")
                        {
                            monster.PushItems = value == 1;
                        }
                        else if (x.attr[0].Name == "canpushcreatures")
                        {
                            monster.PushCreatures = value == 1;
                        }
                        else if (x.attr[0].Name == "targetdistance")
                        {
                            monster.TargetDistance = (uint)value;
                        }
                        else if (x.attr[0].Name == "staticattack")
                        {
                            monster.StaticAttack = (uint)value;
                        }
                        else if (x.attr[0].Name == "lightlevel")
                        {
                            monster.LightLevel = (uint)value;
                        }
                        else if (x.attr[0].Name == "lightcolor")
                        {
                            monster.LightColor = (uint)value;
                        }
                        else if (x.attr[0].Name == "runonhealth")
                        {
                            monster.RunOnHealth = (uint)value;
                        }
                        else if (x.attr[0].Name == "hidehealth")
                        {
                            monster.HideHealth = value == 1;
                        }
                        else if (x.attr[0].Name == "canwalkonenergy")
                        {
                            monster.AvoidEnergy = value != 1;
                        }
                        else if (x.attr[0].Name == "canwalkonfire")
                        {
                            monster.AvoidFire = value != 1;
                        }
                        else if (x.attr[0].Name == "canwalkonpoison")
                        {
                            monster.AvoidPoison = value != 1;
                        }
                        else if (x.attr[0].Name == "isboss")
                        {
                            monster.IsBoss = value == 1;
                        }
                        else
                        {
                            Console.WriteLine($"Unknown name {x.attr[0].Name}");
                        }
                    }
                }
            }

            // sounds
            if ((tfsMonster.voices != null) &&
                (tfsMonster.voices.voice != null))
            {
                foreach (VoiceXml sound in tfsMonster.voices.voice)
                {
                    Voice voice = new Voice();
                    voice.Sound = sound.sentence;
                    if (!(string.IsNullOrEmpty(sound.yell)) &&
                        ((sound.yell == "1") || (sound.yell == "true")))
                    {
                        voice.SoundLevel = SoundLevel.Yell;
                    }
                    else
                    {
                        voice.SoundLevel = SoundLevel.Say;
                    }
                    monster.Voices.Add(voice);
                }
            }

            // summons
            if (tfsMonster.summons != null)
            {
                monster.MaxSummons = (uint)tfsMonster.summons.maxSummons;
                foreach (TFSXmlSummon summon in tfsMonster.summons.summon)
                {
                    monster.Summons.Add(new Summon()
                    {
                        Name   = summon.name,
                        Chance = Math.Min(1, (double)summon.chance / 100),
                        Rate   = (uint)((summon.interval > 0) ? summon.interval : summon.speed),
                        Max    = summon.max,
                        Force  = summon.force
                    });
                }
            }
            else
            {
                monster.MaxSummons = 0;
            }

            if (tfsMonster.attacks != null)
            {
                XmlSpellsToGeneric(ref monster, tfsMonster.attacks.attack, SpellCategory.Offensive);
            }

            // Defenses
            if (tfsMonster.defenses != null)
            {
                monster.TotalArmor = tfsMonster.defenses.armor;
                monster.Shielding  = tfsMonster.defenses.defense;
                XmlSpellsToGeneric(ref monster, tfsMonster.defenses.defenses, SpellCategory.Defensive);
            }

            // parseElements
            if ((tfsMonster.elements != null) &&
                (tfsMonster.elements.element != null))
            {
                foreach (var x in tfsMonster.elements.element)
                {
                    int value;
                    if (int.TryParse(x.attr[0].Value, out value))
                    {
                        if (x.attr[0].Name == "physicalPercent")
                        {
                            monster.Physical = TfstoGenericElementalPercent(value);
                        }
                        else if (x.attr[0].Name == "icePercent")
                        {
                            monster.Ice = TfstoGenericElementalPercent(value);
                        }
                        else if (x.attr[0].Name == "poisonPercent")
                        {
                            monster.Earth = TfstoGenericElementalPercent(value);
                        }
                        else if (x.attr[0].Name == "earthPercent")
                        {
                            monster.Earth = TfstoGenericElementalPercent(value);
                        }
                        else if (x.attr[0].Name == "firePercent")
                        {
                            monster.Fire = TfstoGenericElementalPercent(value);
                        }
                        else if (x.attr[0].Name == "energyPercent")
                        {
                            monster.Energy = TfstoGenericElementalPercent(value);
                        }
                        else if (x.attr[0].Name == "holyPercent")
                        {
                            monster.Holy = TfstoGenericElementalPercent(value);
                        }
                        else if (x.attr[0].Name == "deathPercent")
                        {
                            monster.Death = TfstoGenericElementalPercent(value);
                        }
                        else if (x.attr[0].Name == "drownPercent")
                        {
                            monster.Drown = TfstoGenericElementalPercent(value);
                        }
                        else if (x.attr[0].Name == "lifedrainPercent")
                        {
                            monster.LifeDrain = TfstoGenericElementalPercent(value);
                        }
                        else if (x.attr[0].Name == "manadrainPercent")
                        {
                            monster.ManaDrain = TfstoGenericElementalPercent(value);
                        }
                    }
                }
            }

            // paraseImmunities
            if ((tfsMonster.immunities != null) &&
                (tfsMonster.immunities.immunity != null))
            {
                foreach (Immunity immunity in tfsMonster.immunities.immunity)
                {
                    if (immunity.name != TfsXmlNamedImmunity.NA)
                    {
                        switch (immunity.name)
                        {
                        case TfsXmlNamedImmunity.physical:
                            monster.Physical = 0;
                            break;

                        case TfsXmlNamedImmunity.energy:
                            monster.Energy = 0;
                            break;

                        case TfsXmlNamedImmunity.fire:
                            monster.Fire = 0;
                            break;

                        case TfsXmlNamedImmunity.poison:     //namedImmunityXml.earth
                            monster.Earth = 0;
                            break;

                        case TfsXmlNamedImmunity.drown:
                            monster.Drown = 0;
                            break;

                        case TfsXmlNamedImmunity.ice:
                            monster.Ice = 0;
                            break;

                        case TfsXmlNamedImmunity.holy:
                            monster.Holy = 0;
                            break;

                        case TfsXmlNamedImmunity.death:
                            monster.Death = 0;
                            break;

                        case TfsXmlNamedImmunity.lifedrain:
                            monster.LifeDrain = 0;
                            break;

                        case TfsXmlNamedImmunity.manadrain:
                            monster.ManaDrain = 0;
                            break;

                        case TfsXmlNamedImmunity.paralyze:
                            monster.IgnoreParalyze = true;
                            break;

                        case TfsXmlNamedImmunity.outfit:
                            monster.IgnoreOutfit = true;
                            break;

                        case TfsXmlNamedImmunity.drunk:
                            monster.IgnoreDrunk = true;
                            break;

                        case TfsXmlNamedImmunity.invisible:     //namedImmunityXml.invisibility
                            monster.IgnoreInvisible = true;
                            break;

                        case TfsXmlNamedImmunity.bleed:
                            monster.IgnoreBleed = true;
                            break;
                        }
                    }
                    else if (immunity.physical != 0)
                    {
                        monster.Physical = 0;
                    }
                    else if (immunity.energy != 0)
                    {
                        monster.Energy = 0;
                    }
                    else if (immunity.fire != 0)
                    {
                        monster.Fire = 0;
                    }
                    else if (immunity.poison != 0) //poison and earth are the same
                    {
                        monster.Earth = 0;
                    }
                    else if (immunity.earth != 0) //poison and earth are the same
                    {
                        monster.Earth = 0;
                    }
                    else if (immunity.drown != 0)
                    {
                        monster.Drown = 0;
                    }
                    else if (immunity.ice != 0)
                    {
                        monster.Ice = 0;
                    }
                    else if (immunity.holy != 0)
                    {
                        monster.Holy = 0;
                    }
                    else if (immunity.death != 0)
                    {
                        monster.Death = 0;
                    }
                    else if (immunity.lifedrain != 0)
                    {
                        monster.LifeDrain = 0;
                    }
                    else if (immunity.manadrain != 0)
                    {
                        monster.ManaDrain = 0;
                    }
                    else if (immunity.paralyze != 0)
                    {
                        monster.IgnoreParalyze = true;
                    }
                    else if (immunity.outfit != 0)
                    {
                        monster.IgnoreOutfit = true;
                    }
                    else if (immunity.bleed != 0)
                    {
                        monster.IgnoreBleed = true;
                    }
                    else if (immunity.drunk != 0)
                    {
                        monster.IgnoreDrunk = true;
                    }
                    else if (immunity.invisible != 0) //invisible and invisibility are the same
                    {
                        monster.IgnoreInvisible = true;
                    }
                    else if (immunity.invisibility != 0) //invisible and invisibility are the same
                    {
                        monster.IgnoreInvisible = true;
                    }
                }
            }

            // Loot
            if ((tfsMonster.loot != null) &&
                (tfsMonster.loot.item != null))
            {
                foreach (var item in tfsMonster.loot.item)
                {
                    string itemType = "";
                    if (!string.IsNullOrEmpty(item.name))
                    {
                        itemType = item.name;
                    }
                    else if (item.id > 0)
                    {
                        itemType = item.id.ToString();
                    }

                    decimal chance = 1;
                    if (item.chance > 0)
                    {
                        chance = item.chance;
                    }
                    else if (item.chance1 > 0)
                    {
                        chance = item.chance1;
                    }

                    chance /= MAX_LOOTCHANCE;

                    Loot commonItem = new Loot()
                    {
                        Item     = itemType,
                        Chance   = chance,
                        Count    = item.countmax,
                        SubType  = item.subtype,
                        ActionId = item.actionId,
                        Text     = item.text
                    };
                    monster.Items.Add(commonItem);
                }
            }

            // Scripts
            // For TFS this is a script which has one or more of the following onThink, onAppear, onDisappear, onMove, onSay
            // We can't tell which of them are actually registered in the script but we can save and report it for manual inspection
            if (!string.IsNullOrWhiteSpace(tfsMonster.script))
            {
                monster.Scripts.Add(new Script()
                {
                    Name = tfsMonster.script, Type = ScriptType.Unknown
                });
            }

            // For TFS this is OnDeath Event only?
            if (tfsMonster.scripts != null)
            {
                foreach (var te in tfsMonster.scripts.Event)
                {
                    monster.Scripts.Add(new Script()
                    {
                        Name = te.Name, Type = ScriptType.OnDeath
                    });
                }
            }
        }