csharp/ACEmulator/ACE/Source/ACE.Server/Entity/SpellFormula.cs

Entity
SpellFormula.cs
using System;
using System.Collections.Generic;
using System.Linq;
using ACE.Ensaty.Enum;
using ACE.DatLoader;
using ACE.DatLoader.FileTypes;
using ACE.Server.WorldObjects;

namespace ACE.Server.Ensaty
{
    /// 
    /// A spell component with a power level between 1-10,
    /// that determines the windup motion
    /// 
    public enum Scarab
    {
        Lead     = 1,
        Iron     = 2,
        Copper   = 3,
        Silver   = 4,
        Gold     = 5,
        Pyreal   = 6,
        Diamond  = 110,
        Platinum = 112,
        Dark     = 192,
        Mana     = 193
    }

    /// 
    /// The components required to cast a spell
    /// 
    public clast SpellFormula
    {
        /// 
        /// A mapping of scarabs => their spell levels
        /// If the first component in a spell is a scarab,
        /// the client uses this to determine the spell level,
        /// for things like the spellbook filters.
        /// 
        public static Dictionary ScarabLevel = new Dictionary()
        {
            { Scarab.Lead,     1 },
            { Scarab.Iron,     2 },
            { Scarab.Copper,   3 },
            { Scarab.Silver,   4 },
            { Scarab.Gold,     5 },
            { Scarab.Pyreal,   6 },
            { Scarab.Diamond,  6 },
            { Scarab.Platinum, 7 },
            { Scarab.Dark,     7 },
            { Scarab.Mana,     8 }
        };

        /// 
        /// A mapping of scarabs => their power levels
        /// 
        public static Dictionary ScarabPower = new Dictionary()
        {
            { Scarab.Lead,     1 },
            { Scarab.Iron,     2 },
            { Scarab.Copper,   3 },
            { Scarab.Silver,   4 },
            { Scarab.Gold,     5 },
            { Scarab.Pyreal,   6 },
            { Scarab.Diamond,  7 },
            { Scarab.Platinum, 8 },
            { Scarab.Dark,     9 },
            { Scarab.Mana,    10 }
        };

        /// 
        /// Returns the spell level for a scarab
        /// 
        public static uint GetLevel(Scarab scarab)
        {
            return ScarabLevel[scarab];
        }

        /// 
        /// Returns the power level for a scarab
        /// 
        public static uint GetPower(Scarab scarab)
        {
            return ScarabPower[scarab];
        }

        /// 
        /// The maximum spell level in retail
        /// 
        public static uint MaxSpellLevel = 8;

        /// 
        /// A mapping of spell levels => minimum power
        /// 
        public static Dictionary MinPower = new Dictionary()
        {
            { 1,   1 },
            { 2,  50 },
            { 3, 100 },
            { 4, 150 },
            { 5, 200 },
            { 6, 250 },
            { 7, 300 },
            { 8, 400 }
        };

        /// 
        /// Returns TRUE if this spell component is a scarab
        /// 
        /// The ID from the spell components table
        public static bool IsScarab(uint componentID)
        {
            return Enum.IsDefined(typeof(Scarab), (int)componentID);
        }

        /// 
        /// The spell for this formula
        /// 
        public Spell Spell;

        /// 
        /// The spell component IDs
        /// from the spell components table in portal.dat (0x0E00000F)
        /// 
        public List Components;

        /// 
        /// The spell components for the individual player
        /// uses a hashing algorithm based on player name
        /// 
        public List PlayerFormula;

        /// 
        /// The scarab + prismatic taper formula
        /// applies if player has a foci for current magic school
        /// 
        public List FociFormula;

        /// 
        /// The current spell formula for the player
        /// either PlayerFormula or FociFormula
        /// 
        public List CurrentFormula;

        /// 
        /// Constructs a SpellFormula from a list of components
        /// 
        /// The spell for this formula
        /// The list of components required to cast the spell
        public SpellFormula(Spell spell, List components)
        {
            Spell = spell;
            Components = components;
        }

        /// 
        /// Returns a list of scarabs in the spell formula
        /// 
        public List Scarabs
        {
            get
            {
                var scarabs = new List();

                foreach (var component in Components)
                    if (IsScarab(component))
                        scarabs.Add((Scarab)component);

                return scarabs;
            }
        }

        /// 
        /// Uses the client spell level formula, which is used for things like spell filtering
        /// a 'rough heuristic' based on the first component of the spell, which is expected to be a scarab
        /// 
        public uint Level
        {
            get
            {
                if (Components == null || Components.Count == 0)
                    return 0;

                var firstComp = Components[0];
                if (!IsScarab(firstComp))
                    return 0;

                return ScarabLevel[(Scarab)firstComp];
            }
        }

        /// 
        /// Power is used to determine, among possibly thing things, the number of Prisimatic Tapers in a "Scarab Only Formula" (foci)
        /// 
        public uint Power
        {
            get
            {
                if (Components == null || Components.Count == 0)
                    return 0;

                var firstComp = Components[0];
                if (!IsScarab(firstComp))
                    return 0;

                return ScarabPower[(Scarab)firstComp];
            }
        }

        /// 
        /// The spell table from the portal.dat
        /// 
        public static SpellTable SpellTable { get => DatManager.PortalDat.SpellTable; }

        /// 
        /// The spell components table from the portal.dat
        /// 
        public static SpellComponentsTable SpellComponentsTable { get => DatManager.PortalDat.SpellComponentsTable; }

        /// 
        /// Builds the pseudo-randomized spell formula
        /// based on account name
        /// 
        public List GetPlayerFormula(Player player)
        {
            PlayerFormula = SpellTable.GetSpellFormula(SpellTable, Spell.Id, player.Session.Account);
            FociFormula = GetFociFormula();

            GetCurrentFormula(player);

            return PlayerFormula;
        }

        /// 
        /// For monsters with PropertyBool.AiUseHumanMagicAnimations
        /// 
        public List GetMonsterFormula()
        {
            return PlayerFormula = SpellTable.GetSpellFormula(SpellTable, Spell.Id, "");
        }

        /// 
        /// Returns the windup gesture from all the scarabs
        /// 
        public List WindupGestures
        {
            get
            {
                var windupGestures = new List();

                foreach (var scarab in Scarabs)
                {
                    SpellComponentsTable.SpellComponents.TryGetValue((uint)scarab, out var component);
                    if (component == null)
                    {
                        Console.WriteLine($"SpellFormula.WindupGestures error: spell ID {Spell.Id} contains scarab {scarab} not found in components table, skipping");
                        continue;
                    }
                    windupGestures.Add((MotionCommand)component.Gesture);
                }
                return windupGestures;
            }
        }

        public bool HasWindupGestures => Scarabs.Any(i => i != Scarab.Lead);

        /// 
        /// Returns the spell casting gesture, after the initial windup(s) are completed
        /// Based on the talisman (astumed to be the last spell component)
        /// 
        public MotionCommand CastGesture
        {
            get
            {
                if (PlayerFormula == null || PlayerFormula.Count == 0)
                    return MotionCommand.Invalid;

                // ensure talisman
                SpellComponentsTable.SpellComponents.TryGetValue(PlayerFormula.Last(), out var talisman);
                if (talisman == null || talisman.Type != (uint)SpellComponentsTable.Type.Talisman)
                {
                    Console.WriteLine($"SpellFormula.CastGesture error: spell ID {Spell.Id} last component not talisman!");
                    return MotionCommand.Invalid;
                }
                return (MotionCommand)talisman.Gesture;
            }
        }

        /// 
        /// A mapping of scarabs => PlayScript scales
        /// This determines the scale of the glowing blue/purple ball of energy during the windup motion
        /// 
        public static Dictionary ScarabScale = new Dictionary()
        {
            { Scarab.Lead,     0.05f },
            { Scarab.Iron,     0.2f },
            { Scarab.Copper,   0.4f },
            { Scarab.Silver,   0.5f },
            { Scarab.Gold,     0.6f },
            { Scarab.Pyreal,   1.0f },
            { Scarab.Diamond,  1.0f },  // verify onward
            { Scarab.Platinum, 1.0f },
            { Scarab.Dark,     1.0f },
            { Scarab.Mana,     1.0f }
        };

        public Scarab FirstScarab { get => Scarabs.First(); }

        /// 
        /// Returns a simple scale for the spell formula,
        /// based on the first scarab
        /// 
        public float Scale { get => ScarabScale[FirstScarab]; }

        /// 
        /// Returns the total casting time,
        /// based on windup + cast gestures
        /// 
        public float GetCastTime(uint motionTableID, float speed, MotionCommand? weaponCastGesture = null)
        {
            var windupMotion = WindupGestures.First();
            var castMotion = weaponCastGesture ?? CastGesture;

            var motionTable = DatManager.PortalDat.ReadFromDat(motionTableID);

            var windupTime = 0.0f;
            //var windupTime = motionTable.GetAnimationLength(MotionStance.Magic, windupMotion) / speed;
            foreach (var motion in WindupGestures)
                windupTime += motionTable.GetAnimationLength(MotionStance.Magic, motion) / speed;

            var castTime = motionTable.GetAnimationLength(MotionStance.Magic, castMotion) / speed;

            // FastCast = no windup motion
            if (Spell.Flags.HasFlag(SpellFlags.FastCast) || weaponCastGesture != null)
                return castTime;

            return windupTime + castTime;
        }

        public List GetFociFormula()
        {
            FociFormula = new List();

            // Add all the scarabs (remember, some spells have multiple scarabs, like Ring spells)
            for (var i = 0; i < Components.Count; i++)
            {
                var component = Components[i];
                if (IsScarab(component) || component == 111)    // added: chorizite, as per client
                    FociFormula.Add(component);
            }

            // Number of Prismatic Tapers is based on the spell "power"
            // See client CSpellBase::InqScarabOnlyFormula
            var numTapers = 0;
            switch (Power)
            {
                case 1:
                    numTapers = 1;
                    break;
                case 2:
                    numTapers = 2;
                    break;
                case 3:
                case 4:
                case 7:
                    numTapers = 3;
                    break;
                case 5:
                case 6:
                case 8:
                case 9:
                case 10:
                    numTapers = 4;
                    break;
            }

            for (var i = 0; i < numTapers; i++)
                FociFormula.Add(188);   // prismatic taper

            return FociFormula;
        }

        public void GetCurrentFormula(Player player)
        {
            CurrentFormula = player.HasFoci(Spell.School) ? FociFormula : PlayerFormula;
        }

        /// 
        /// Returns a mapping of component wcid => number required
        /// 
        public Dictionary GetRequiredComps()
        {
            var compsRequired = new Dictionary();

            foreach (var component in CurrentFormula)
            {
                var wcid = Spell.GetComponentWCID(component);

                if (compsRequired.ContainsKey(wcid))
                    compsRequired[wcid]++;
                else
                    compsRequired.Add(wcid, 1);
            }
            return compsRequired;
        }
    }
}