// Functions
        public 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);

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

            return(true);
        }
        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 = (uint)tfsMonster.targetchange.chance;

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

            // attack strategies
            if (tfsMonster.targetstrategies != null)
            {
                monster.AttackMinHealthPercent  = tfsMonster.targetstrategies.health;
                monster.AttackMostDamagePercent = tfsMonster.targetstrategies.damage;
                monster.AttackNearestPercent    = tfsMonster.targetstrategies.nearest;
                monster.AttackRandomPercent     = tfsMonster.targetstrategies.random;
            }

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

            #region 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);
                        }
                    }
                }
            }
            #endregion

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                        case namedImmunityXml.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;
                    }
                }
            }
            #endregion

            // 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
                    };
                    monster.Items.Add(commonItem);
                }
            }
        }