コード例 #1
0
 private bool CheckArmorShapeAllowed(Templates.TplArmor armor, AllodsMap.AlmShop.AlmShopShelf rules)
 {
     for (int i = 0; i < 7; i++)
     {
         uint classFlag = 1u << (i + 15);
         for (int j = 0; j < 15; j++)
         {
             uint materialFlag = 1u << j;
             // armor itself is not allowed
             if ((armor.ClassesAllowed[i] & materialFlag) == 0)
             {
                 continue;
             }
             // not allowed in shop
             if (((uint)rules.ItemMaterials & materialFlag) == materialFlag &&
                 ((uint)rules.ItemClasses & classFlag) == classFlag)
             {
                 return(true);
             }
         }
     }
     return(false);
 }
コード例 #2
0
ファイル: MapHuman.cs プロジェクト: igroglaz/UnityAllods
    protected override void OnUpdateItems()
    {
        if (IsHero)
        {
            Item cuirass        = GetItemFromBody(BodySlot.CuirassCloak);
            Item weapon         = GetItemFromBody(BodySlot.Weapon);
            Item shield         = GetItemFromBody(BodySlot.Shield);
            int  isLightArmored = 0;
            if (cuirass != null)
            {
                isLightArmored = UnitClassLoader.HeroMaterials[cuirass.Class.MaterialID];
            }
            // now if we are a mage, then we either have armed or unarmed sprite.
            // if we're a fighter, we should pick appropriate version of the sprite instead.
            UnitClass newClass = null;
            if ((Gender & GenderFlags.Mage) != 0)
            {
                newClass = (weapon != null) ? UnitClassLoader.HeroMageSt[1] : UnitClassLoader.HeroMage[1]; // always heroes. heroes_l mages don't exist!
            }
            else if (weapon == null)
            {
                newClass = UnitClassLoader.HeroUnarmed[isLightArmored];
            }
            else
            {
                Templates.TplArmor weaponKind = weapon.Class.Option;
                switch (weaponKind.AttackType)
                {
                case 1:     // sword
                    if (weaponKind.TwoHanded == 2)
                    {
                        newClass = UnitClassLoader.HeroSwordsman2h[isLightArmored];
                    }
                    else if (shield != null)
                    {
                        newClass = UnitClassLoader.HeroSwordsman_[isLightArmored];
                    }
                    else
                    {
                        newClass = UnitClassLoader.HeroSwordsman[isLightArmored];
                    }
                    break;

                case 2:     // axe
                    if (weaponKind.TwoHanded == 2)
                    {
                        newClass = UnitClassLoader.HeroAxeman2h[isLightArmored];
                    }
                    else if (shield != null)
                    {
                        newClass = UnitClassLoader.HeroAxeman_[isLightArmored];
                    }
                    else
                    {
                        newClass = UnitClassLoader.HeroAxeman[isLightArmored];
                    }
                    break;

                case 3:     // club
                    newClass = (shield != null) ? UnitClassLoader.HeroClubman_[isLightArmored] : UnitClassLoader.HeroClubman[isLightArmored];
                    break;

                case 4:     // pike
                    newClass = (shield != null) ? UnitClassLoader.HeroPikeman_[isLightArmored] : UnitClassLoader.HeroPikeman[isLightArmored];
                    break;

                case 5:     // shooting (bow or crossbow)
                    if (weaponKind.Name.ToLower().Contains("crossbow"))
                    {
                        newClass = UnitClassLoader.HeroCrossbowman[isLightArmored];
                    }
                    else
                    {
                        newClass = UnitClassLoader.HeroArcher[isLightArmored];
                    }
                    break;
                }
            }

            if (newClass != Class)
            {
                Class        = newClass;
                DoUpdateView = true;
            }
        }

        // if not client, recalc stats
        if (NetworkManager.IsClient)
        {
            return;
        }
        // max brms
        short maxBody = 100, maxReaction = 100, maxMind = 100, maxSpirit = 100;

        if ((Gender & GenderFlags.MaleFighter) == GenderFlags.MaleFighter)
        {
            maxBody     = 52;
            maxReaction = 50;
            maxMind     = 48;
            maxSpirit   = 46;
        }
        else if ((Gender & GenderFlags.FemaleFighter) == GenderFlags.FemaleFighter)
        {
            maxBody     = 50;
            maxReaction = 52;
            maxMind     = 46;
            maxSpirit   = 48;
        }
        else if ((Gender & GenderFlags.MaleMage) == GenderFlags.MaleMage)
        {
            maxBody     = 48;
            maxReaction = 46;
            maxMind     = 52;
            maxSpirit   = 50;
        }
        else if ((Gender & GenderFlags.FemaleMage) == GenderFlags.FemaleMage)
        {
            maxBody     = 46;
            maxReaction = 48;
            maxMind     = 50;
            maxSpirit   = 52;
        }

        CoreStats.Body     = Math.Min(CoreStats.Body, maxBody);
        CoreStats.Reaction = Math.Min(CoreStats.Reaction, maxReaction);
        CoreStats.Mind     = Math.Min(CoreStats.Mind, maxMind);
        CoreStats.Spirit   = Math.Min(CoreStats.Spirit, maxSpirit);

        float origHealth = (float)Stats.Health / Stats.HealthMax;
        float origMana   = (float)Stats.Mana / Stats.ManaMax;

        // speed and scanrange
        CoreStats.RotationSpeed = (byte)Template.RotationSpeed;
        if (CoreStats.RotationSpeed < 1)
        {
            CoreStats.RotationSpeed = 1;
        }
        CoreStats.Speed = (byte)Template.Speed;
        if (CoreStats.Speed < 1)
        {
            CoreStats.Speed = 1;
        }
        CoreStats.ScanRange = Template.ScanRange;

        //
        CoreStats.HealthRegeneration = 100;
        CoreStats.ManaRegeneration   = 100;

        // skills
        if (Gender.HasFlag(GenderFlags.Fighter))
        {
            CoreStats.SkillBlade    = (byte)GetSkill(ExperienceSkill.Blade);
            CoreStats.SkillAxe      = (byte)GetSkill(ExperienceSkill.Axe);
            CoreStats.SkillBludgeon = (byte)GetSkill(ExperienceSkill.Bludgeon);
            CoreStats.SkillPike     = (byte)GetSkill(ExperienceSkill.Pike);
            CoreStats.SkillShooting = (byte)GetSkill(ExperienceSkill.Shooting);
        }
        else if (Gender.HasFlag(GenderFlags.Mage))
        {
            CoreStats.SkillFire   = (byte)GetSkill(ExperienceSkill.Fire);
            CoreStats.SkillWater  = (byte)GetSkill(ExperienceSkill.Water);
            CoreStats.SkillAir    = (byte)GetSkill(ExperienceSkill.Air);
            CoreStats.SkillEarth  = (byte)GetSkill(ExperienceSkill.Earth);
            CoreStats.SkillAstral = (byte)GetSkill(ExperienceSkill.Astral);
        }

        //
        // add stats from items
        ItemStats = new UnitStats();
        foreach (Item bodyitem in ItemsBody)
        {
            ItemStats.MergeEffects(bodyitem.Effects);
        }

        Stats.Body     = (short)(CoreStats.Body + ItemStats.Body);
        Stats.Reaction = (short)(CoreStats.Reaction + ItemStats.Reaction);
        Stats.Mind     = (short)(CoreStats.Mind + ItemStats.Mind);
        Stats.Spirit   = (short)(CoreStats.Spirit + ItemStats.Spirit);

        if (Stats.Reaction < 10)
        {
            Stats.Speed = (byte)Stats.Reaction;
        }
        else
        {
            Stats.Speed = (byte)Math.Min((float)Stats.Reaction / 5 + 12, 255f);
        }
        if (Class.ID == 19 || Class.ID == 21)
        {
            Stats.Speed += 10;
        }
        Stats.RotationSpeed = Stats.Speed;

        Stats.Speed         += ItemStats.Speed;
        Stats.RotationSpeed += ItemStats.RotationSpeed;

        //
        float experience_total = GetExperience();
        float fighter_mult     = (Gender & GenderFlags.Fighter) != 0 ? 2 : 1;
        float mage_mult        = (Gender & GenderFlags.Mage) != 0 ? 2 : 1;

        if (CoreStats.HealthMax == -1 || IsHero)
        {
            CoreStats.HealthMax  = (int)(Stats.Body * fighter_mult);
            CoreStats.HealthMax += (int)(Log11(experience_total / 5000f + fighter_mult));
            CoreStats.HealthMax  = (int)((Pow11(Stats.Body) / 100f + 1f) * CoreStats.HealthMax);
        }

        if ((Gender & GenderFlags.Mage) != 0)
        {
            CoreStats.ManaMax  = (int)(Stats.Spirit * mage_mult);
            CoreStats.ManaMax += (int)(Log11(experience_total / 5000f + mage_mult));
            CoreStats.ManaMax  = (int)((Pow11(Stats.Spirit) / 100f + 1f) * CoreStats.ManaMax);
        }
        else
        {
            CoreStats.ManaMax = -1;
        }

        Stats.HealthMax = CoreStats.HealthMax + ItemStats.HealthMax;
        Stats.ManaMax   = CoreStats.ManaMax + ItemStats.ManaMax;

        float fighterSkill = 0;

        if ((Gender & GenderFlags.Fighter) != 0)
        {
            Item weapon = GetItemFromBody(BodySlot.Weapon);
            if (weapon != null && weapon.Class != null && weapon.Class.Option != null)
            {
                ExperienceSkill fsk;
                switch (weapon.Class.Option.AttackType)
                {
                default:
                case 1:
                    fsk = ExperienceSkill.Blade;
                    break;

                case 2:
                    fsk = ExperienceSkill.Axe;
                    break;

                case 3:
                    fsk = ExperienceSkill.Bludgeon;
                    break;

                case 4:
                    fsk = ExperienceSkill.Pike;
                    break;

                case 5:
                    fsk = ExperienceSkill.Shooting;
                    break;
                }
                fighterSkill = GetSkill(fsk);
            }
        }

        Stats.DamageMax = (short)(Stats.Body / 10);
        Stats.DamageMin = (short)(Stats.DamageMax / 2);
        Stats.ToHit     = (short)Math.Pow(1f + (Stats.Reaction / 100f) + (fighterSkill / 100f), 9);

        Stats.DamageMin += ItemStats.DamageMin;
        Stats.DamageMax += (short)(ItemStats.DamageMax + Stats.DamageMin);
        Stats.ToHit     += ItemStats.ToHit;

        Stats.DamageMin = (short)(Stats.DamageMin * (1f + fighterSkill / 20 * Stats.Body / 100f));
        Stats.DamageMax = (short)(Stats.DamageMax * (1f + fighterSkill / 20 * Stats.Body / 100f));

        Stats.DamageMin += ItemStats.DamageBonus;
        Stats.DamageMax += ItemStats.DamageBonus;

        Stats.Defence    = (short)((float)ItemStats.Defence * (Stats.Reaction / 15));
        Stats.Absorbtion = ItemStats.Absorbtion;

        int minProt = Stats.Spirit / 2;
        int maxProt = Math.Min(100, (70 + Stats.Spirit / 2));

        Stats.ProtectionFire   = ItemStats.ProtectionFire;
        Stats.ProtectionWater  = ItemStats.ProtectionWater;
        Stats.ProtectionAir    = ItemStats.ProtectionAir;
        Stats.ProtectionEarth  = ItemStats.ProtectionEarth;
        Stats.ProtectionAstral = ItemStats.ProtectionAstral;

        Stats.ScanRange  = 4f + (float)Math.Pow(1f + Stats.Reaction / 100f, Stats.Mind / 10f);
        Stats.ScanRange += ItemStats.ScanRange;

        for (int i = 0; i < SpellEffects.Count; i++)
        {
            SpellEffects[i].ProcessStats(Stats);
        }

        Stats.ProtectionFire   = (byte)Math.Min(maxProt, Math.Max(minProt, Stats.ProtectionFire));
        Stats.ProtectionWater  = (byte)Math.Min(maxProt, Math.Max(minProt, Stats.ProtectionWater));
        Stats.ProtectionAir    = (byte)Math.Min(maxProt, Math.Max(minProt, Stats.ProtectionAir));
        Stats.ProtectionEarth  = (byte)Math.Min(maxProt, Math.Max(minProt, Stats.ProtectionEarth));
        Stats.ProtectionAstral = (byte)Math.Min(maxProt, Math.Max(minProt, Stats.ProtectionAstral));

        if (Gender.HasFlag(GenderFlags.Fighter))
        {
            Stats.SkillBlade    = (byte)(CoreStats.SkillBlade + ItemStats.SkillBlade);
            Stats.SkillAxe      = (byte)(CoreStats.SkillAxe + ItemStats.SkillAxe);
            Stats.SkillBludgeon = (byte)(CoreStats.SkillBludgeon + ItemStats.SkillBludgeon);
            Stats.SkillPike     = (byte)(CoreStats.SkillPike + ItemStats.SkillPike);
            Stats.SkillShooting = (byte)(CoreStats.SkillShooting + ItemStats.SkillShooting);
        }
        else if (Gender.HasFlag(GenderFlags.Mage))
        {
            CoreStats.SkillFire   = (byte)(CoreStats.SkillFire + ItemStats.SkillFire);
            CoreStats.SkillWater  = (byte)(CoreStats.SkillWater + ItemStats.SkillWater);
            CoreStats.SkillAir    = (byte)(CoreStats.SkillAir + ItemStats.SkillAir);
            CoreStats.SkillEarth  = (byte)(CoreStats.SkillEarth + ItemStats.SkillEarth);
            CoreStats.SkillAstral = (byte)(CoreStats.SkillAstral + ItemStats.SkillAstral);
        }

        //Debug.LogFormat("ItemStats = {0}", ItemStats.ToString());

        CalculateVision();

        DoUpdateInfo = true;
        DoUpdateView = true;
    }
コード例 #3
0
ファイル: ItemClass.cs プロジェクト: igroglaz/UnityAllods
    public static ItemClass GetItemClassBySpecifier(string specifier)
    {
        specifier = specifier.Trim().ToLower();
        string commonspecifier = "common " + specifier;

        foreach (ItemClass cls in Classes)
        {
            string sname = cls.ServerName.ToLower();
            if (sname == specifier || sname == commonspecifier)
            {
                return(cls);
            }
        }

        // try to guess based on specifier. this is used in world.res
        string[] specSplit = specifier.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
        string   searchFor; // this is reused
        // first off, if last word is "shield", then we're looking for something in Templates.Shields
        int gSlot = -1;

        Templates.TplArmor gOption = null;
        int gOptionId = -1;

        if (specSplit[specSplit.Length - 1] == "shield")
        {
            searchFor = "";
            for (int j = 1; j >= 0; j--)
            {
                if (specSplit.Length - 1 - j < 0)
                {
                    continue;
                }

                searchFor = string.Join(" ", specSplit.Skip(specSplit.Length - 1 - j).Take(j + 1).ToArray());
                for (int i = 0; i < TemplateLoader.Templates.Shields.Count; i++)
                {
                    Templates.TplArmor shieldKind = TemplateLoader.Templates.Shields[i];
                    if (shieldKind.Name.ToLower() == searchFor)
                    {
                        gOption   = shieldKind;
                        gOptionId = i;
                        gSlot     = shieldKind.Slot;
                        break;
                    }
                }

                if (gOption != null)
                {
                    // remove the option specifier (leaving only class and material)
                    specSplit = specSplit.Take(specSplit.Length - (j + 2)).ToArray();
                    break;
                }
            }
        }
        // otherwise search in weapons and armor.
        else
        {
            searchFor = "";
            for (int j = 1; j >= 0; j--)
            {
                if (specSplit.Length - 1 - j < 0)
                {
                    continue;
                }

                searchFor = string.Join(" ", specSplit.Skip(specSplit.Length - 1 - j).Take(j + 1).ToArray());
                for (int i = 0; i < TemplateLoader.Templates.Weapons.Count; i++)
                {
                    Templates.TplArmor weaponKind = TemplateLoader.Templates.Weapons[i];
                    if (weaponKind.Name.ToLower() == searchFor)
                    {
                        gOption   = weaponKind;
                        gOptionId = i;
                        gSlot     = weaponKind.Slot;
                        break;
                    }
                }

                if (gOption == null)
                {
                    for (int i = 0; i < TemplateLoader.Templates.Armor.Count; i++)
                    {
                        Templates.TplArmor armorKind = TemplateLoader.Templates.Armor[i];
                        if (armorKind.Name.ToLower() == searchFor)
                        {
                            gOption   = armorKind;
                            gOptionId = i;
                            gSlot     = armorKind.Slot;
                            break;
                        }
                    }
                }

                if (gOption != null)
                {
                    specSplit = specSplit.Take(specSplit.Length - (j + 1)).ToArray();
                    break;
                }
            }
        }

        // here we either have gOption and gSlot or the item is not found.
        if (gOption == null || gSlot < 0)
        {
            return(null);
        }

        // now find material
        int gMaterial = -1;

        searchFor = "";
        for (int j = 1; j >= 0; j--)
        {
            if (specSplit.Length - 1 - j < 0)
            {
                continue;
            }

            searchFor = string.Join(" ", specSplit.Skip(specSplit.Length - 1 - j).Take(j + 1).ToArray());
            for (int i = 0; i < TemplateLoader.Templates.Materials.Count; i++)
            {
                Templates.TplMaterial fmat = TemplateLoader.Templates.Materials[i];
                if (fmat.Name.ToLower() == searchFor)
                {
                    gMaterial = i;
                    break;
                }
            }

            if (gMaterial >= 0)
            {
                specSplit = specSplit.Take(specSplit.Length - (j + 1)).ToArray();
                break;
            }
        }

        if (gMaterial < 0)
        {
            // check if option has "none" material allowed (aka 15)
            if (gOption.IsAllowed(-1, 15))
            {
                gMaterial = 15;
            }
            else
            {
                // there are very special items like "Sonic Beam" that have ONLY option in them
                ItemClass newCls = new ItemClass();
                newCls.ServerName    = gOption.Name;
                newCls.VisualName    = newCls.ServerName;
                newCls.Option        = gOption;
                newCls.IsMagic       = false;
                newCls.IsSpecial     = true;
                newCls.ItemID        = (ushort)gOptionId;
                newCls.UsableMage    = false;
                newCls.UsableFighter = false;
                newCls.Price         = 0;
                return(newCls);
            }
        }

        int gClass = -1; // common by default

        searchFor = string.Join(" ", specSplit);
        for (int i = 0; i < TemplateLoader.Templates.Classes.Count; i++)
        {
            Templates.TplClass fcls = TemplateLoader.Templates.Classes[i];
            if (fcls.Name.ToLower() == searchFor)
            {
                gClass = i;
                break;
            }
        }

        if (gClass < 0)
        {
            gClass = 0;             // default is common. the only default here.
        }
        // how try to find required item.
        Templates.TplClass    reqClass    = TemplateLoader.GetClassById(gClass);
        Templates.TplMaterial reqMaterial = TemplateLoader.GetMaterialById(gMaterial);
        Templates.TplArmor    reqOption;
        if (reqClass == null || reqMaterial == null) // this shouldnt happen tbh
        {
            return(null);
        }

        // do option remap for shields. nival did NOT write "soft helm" or "soft large shield". instead they just wrote "helm" and "large shield".
        reqOption = gOption;

        int oldOptionId = gOptionId;

        if (reqMaterial.Name.ToLower().Contains("wood") && !reqOption.IsAllowed(gClass, gMaterial))
        {
            gOptionId += 2; // small/large -> wooden small/wooden large
        }
        else if (reqMaterial.Name.ToLower().Contains("leather") && !reqOption.IsAllowed(gClass, gMaterial))
        {
            gOptionId += 1; // small/large -> soft small/soft large
        }
        if (oldOptionId != gOptionId)
        {
            reqOption = TemplateLoader.GetOptionByIdAndSlot(gOptionId, gSlot);
            if (reqOption == null)
            {
                return(null);
            }
        }

        for (int i = 0; i < Classes.Count; i++)
        {
            if (Classes[i].Class == reqClass &&
                Classes[i].Material == reqMaterial &&
                Classes[i].Option == reqOption)
            {
                return(Classes[i]);
            }
        }

        return(null);
    }
コード例 #4
0
    public override void UpdateItems()
    {
        if (IsHero)
        {
            Item cuirass        = GetItemFromBody(BodySlot.CuirassCloak);
            Item weapon         = GetItemFromBody(BodySlot.Weapon);
            Item shield         = GetItemFromBody(BodySlot.Shield);
            int  isLightArmored = 0;
            if (cuirass != null)
            {
                isLightArmored = UnitClassLoader.HeroMaterials[cuirass.Class.MaterialID];
            }
            // now if we are a mage, then we either have armed or unarmed sprite.
            // if we're a fighter, we should pick appropriate version of the sprite instead.
            UnitClass newClass = null;
            if ((Gender & GenderFlags.Mage) != 0)
            {
                newClass = (weapon != null) ? UnitClassLoader.HeroMageSt[1] : UnitClassLoader.HeroMage[1]; // always heroes. heroes_l mages don't exist!
            }
            else if (weapon == null)
            {
                newClass = UnitClassLoader.HeroUnarmed[isLightArmored];
            }
            else
            {
                Templates.TplArmor weaponKind = weapon.Class.Option;
                switch (weaponKind.AttackType)
                {
                case 1:     // sword
                    if (weaponKind.TwoHanded == 2)
                    {
                        newClass = UnitClassLoader.HeroSwordsman2h[isLightArmored];
                    }
                    else if (shield != null)
                    {
                        newClass = UnitClassLoader.HeroSwordsman_[isLightArmored];
                    }
                    else
                    {
                        newClass = UnitClassLoader.HeroSwordsman[isLightArmored];
                    }
                    break;

                case 2:     // axe
                    if (weaponKind.TwoHanded == 2)
                    {
                        newClass = UnitClassLoader.HeroAxeman2h[isLightArmored];
                    }
                    else if (shield != null)
                    {
                        newClass = UnitClassLoader.HeroAxeman_[isLightArmored];
                    }
                    else
                    {
                        newClass = UnitClassLoader.HeroAxeman[isLightArmored];
                    }
                    break;

                case 3:     // club
                    newClass = (shield != null) ? UnitClassLoader.HeroClubman_[isLightArmored] : UnitClassLoader.HeroClubman[isLightArmored];
                    break;

                case 4:     // pike
                    newClass = (shield != null) ? UnitClassLoader.HeroPikeman_[isLightArmored] : UnitClassLoader.HeroPikeman[isLightArmored];
                    break;

                case 5:     // shooting (bow or crossbow)
                    if (weaponKind.Name.ToLower().Contains("crossbow"))
                    {
                        newClass = UnitClassLoader.HeroCrossbowman[isLightArmored];
                    }
                    else
                    {
                        newClass = UnitClassLoader.HeroArcher[isLightArmored];
                    }
                    break;
                }
            }

            if (newClass != Class)
            {
                Class        = newClass;
                DoUpdateView = true;
            }
        }

        // if not client, recalc stats
        // actually, let client do this too, we send it the required info anyway
        // max brms
        short maxBody = 100, maxReaction = 100, maxMind = 100, maxSpirit = 100;

        if ((Gender & GenderFlags.MaleFighter) == GenderFlags.MaleFighter)
        {
            maxBody     = 52;
            maxReaction = 50;
            maxMind     = 48;
            maxSpirit   = 46;
        }
        else if ((Gender & GenderFlags.FemaleFighter) == GenderFlags.FemaleFighter)
        {
            maxBody     = 50;
            maxReaction = 52;
            maxMind     = 46;
            maxSpirit   = 48;
        }
        else if ((Gender & GenderFlags.MaleMage) == GenderFlags.MaleMage)
        {
            maxBody     = 48;
            maxReaction = 46;
            maxMind     = 52;
            maxSpirit   = 50;
        }
        else if ((Gender & GenderFlags.FemaleMage) == GenderFlags.FemaleMage)
        {
            maxBody     = 46;
            maxReaction = 48;
            maxMind     = 50;
            maxSpirit   = 52;
        }

        CoreStats.Body     = Math.Min(CoreStats.Body, maxBody);
        CoreStats.Reaction = Math.Min(CoreStats.Reaction, maxReaction);
        CoreStats.Mind     = Math.Min(CoreStats.Mind, maxMind);
        CoreStats.Spirit   = Math.Min(CoreStats.Spirit, maxSpirit);

        //
        // add stats from items
        ItemStats = new UnitStats();
        foreach (Item bodyitem in ItemsBody)
        {
            ItemStats.MergeEffects(bodyitem.Effects);
        }

        //Debug.LogFormat("ItemStats = {0}", ItemStats.ToString());

        Stats = new UnitStats(CoreStats);
        Stats.MergeStats(ItemStats);
        Stats.DamageMax += Stats.DamageMin; // allods use damagemax this way
        // CoreStats = only BRMS used
        //Debug.LogFormat("Body = {0}, {1}, {2}", Stats.Body, CoreStats.Body, ItemStats.Body);

        /*
         *(_WORD *)(this + 150) = (signed __int64)(sub_53066C((double)*(signed int *)(this + 304) / 5000.0 + 1.0)
         * (double)((v34 != 0) + 1)
         + (double)*(_WORD *)(this + 150));
         *(_WORD *)(v28 + 150) = (signed __int64)((sub_53064C(*(_WORD *)(v28 + 132)) / 100.0 + 1.0)
         * (double)*(_WORD *)(v28 + 150));
         *
         * sub_53066C(a1):
         * log(a1) / log(1.1)
         *
         * sub_53064C(a1):
         * pow(1.1, a1)
         *
         * health = body * (is_fighter ? 2 : 1)
         * health += sub_53066C(experience_total) / 5000.0 + 1 * (is_fighter ? 2 : 1)
         * health *= sub_53064C(body) / 100.0 + 1.0
         */

        float experience_total = 7320;
        float fighter_mult     = (Gender & GenderFlags.Fighter) != 0 ? 2 : 1;
        float mage_mult        = (Gender & GenderFlags.Mage) != 0 ? 2 : 1;

        Stats.HealthMax  = (int)(Stats.Body * fighter_mult);
        Stats.HealthMax += (int)(Log11(experience_total / 5000f + fighter_mult));
        Stats.HealthMax  = (int)((Pow11(Stats.Body) / 100f + 1f) * Stats.HealthMax);

        if ((Gender & GenderFlags.Mage) != 0)
        {
            Stats.ManaMax  = (int)(Stats.Spirit * mage_mult);
            Stats.ManaMax += (int)(Log11(experience_total / 5000f + mage_mult));
            Stats.ManaMax  = (int)((Pow11(Stats.Spirit) / 100f + 1f) * Stats.ManaMax);
        }
        else
        {
            Stats.ManaMax = -1;
        }

        //

        /*
         *  if ( *(v28 + 134) >= 12 )
         *(v28 + 140) = *(v28 + 134) / 5 + 12;       // speed
         *  else
         *(v28 + 140) = *(v28 + 134);
         *  if ( *(v28 + 14) == 19 || *(v28 + 14) == 21 ) // ManHorse_LanceShield, ManHorse_SwordShield
         *(v28 + 140) += 10;
         */

        if (Stats.Reaction < 12)
        {
            Stats.Speed = (byte)Stats.Reaction;
        }
        else
        {
            Stats.Speed = (byte)Math.Min(Stats.Reaction / 5 + 12, 255);
        }
        if (Class.ID == 19 || Class.ID == 21) // horseman
        {
            Stats.Speed += 10;
        }
        Stats.RotationSpeed = Stats.Speed;

        DoUpdateInfo = true;
        DoUpdateView = true;
    }