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