API:Spells

From WCell Wiki
Jump to: navigation, search

This article is related to development using the WCell API.

Contents

Example Code

How to start

  • If you are interested into working with Spells, it is strongly recommended to start by creating and considering the Spell Dump
  • Spell-customizations require to be done in the second Initialization pass which means that you will need a public static method like this:
[Initialization(InitializationPass.Second)]
public static void InitMySpells()
{
	// do something here
}
  • All Spells are comprised of up to over 100 fields and are defined in DBC Files.
  • Every spell in the Spell Dump will look a little something like this:
Spell: ClassSkill Fireball Rank 1 (Id: 133) [ClassSkillFireballRank1]
	Line: MageFireball
	Next Rank: ClassSkill Fireball Rank 2 (Id: 143)
	Attributes: NotWhileShapeshifted
	FacingFlags: RequiresInFront
	StartTime: 1500
	InterruptFlags: OnSilence, OnSleep, OnStunned, OnMovement
	ProcChance: 101
	MaxLevel: 5
	BaseLevel: 1
	Level: 1
	Duration: 4000 - 4000 (0)
	Visual: 67
	PowerCostPercentage: 8
	Range: 0 - 35
	ProjectileSpeed: 24
	Priority: 50
	StartRecoveryCategory: 1500
	StartRecoveryTime: 133
	SpellClassSet: Mage
	SpellClassMask: 000000010000000000000008
	DefenseType: Magic
	PreventionType: Magic
	SchoolMask: Fire
	Desc: Hurls a fiery ball that causes $s1 Fire damage and an additional $o2 Fire damage over $d.
	Skill: Fire (Levels: 0, 0, 0)

		Effect: SchoolDamage
			ImplicitTargetA: SingleEnemy
			BasePoints: 13
			DiceSides: 9
			RealPointsPerLevel: 0.6
		Effect: ApplyAura (PeriodicDamage)
			ImplicitTargetA: SingleEnemy
			DiceSides: 1
			Amplitude: 2000
  • The value in [brackets] in the first line behind the Id is the official unique name, as to be found in the SpellId enum.
  • The Line value is the id of the SpellLine, to be found in the SpellLineId enum.

Customizing values

  • You can find out where and how certain fields are being used, by selecting the corresponding field of the Spell class in your IDE and then using the "Lookup references" feature
  • Spell-related values are sometimes wrong by default and require correction
  • You can override values of single spells:
var spell = SpellHandler.Get(SpellId.ClassSkillFireballRank1);
spell.StartTime = 2000;
  • Or apply changes to more than one Spell at a time, using SpellHandler.Apply or SpellLineId.Apply (applies the specified changes to all given Spells or the entire SpellLine (contains all ranks of a Spell)):
SpellLineId.MageConeOfCold.Apply(spell =>
{
	spell.Range.MaxDist = 10;
});

SpellEffects

  • Every Spell usually has 0-3 Effects (but more can be added)
  • You can add and remove SpellEffects, using Spell.AddEffect, Spell.AddAuraEffect, Spell.RemoveEffect etc...
  • There are over 100 types of Effects:
  • All non-default or missing Effects -including Dummy and anything with Script in it's name-, will need to be replaced or overridden
  • The target for every SpellEffect might be different
  • The way in which Targets are selected can be changed, the way the client selects the target, however, can *NOT* be changed (ie. you cannot change whether to target an area or your current selection etc)
  • For more on targeting, see Targeting
  • You can create a custom SpellEffectHandler for effects by setting the SpellEffectHandlerCreator field

Auras

  • All Spells with at least one SpellEffect that applies an Aura (eg. ApplyAura), are considered Auras that will be applied to the corresponding target
  • There are more than 200 different kinds of Effects for Auras: See AuraType.cs
  • AuraEffects with the same Targeting are grouped into one Aura (for example, one Spell can be beneficial to your own party and harmful to enemies at the same time - All aura effects that have ImplicitTarget = SomePartyTargetType will be applied to party members; whereas all aura effects that are against your foes will be grouped into a different kind of aura and applied to them)
  • Every AuraEffect has its own AuraEffectHandler to be found in Spells/Auras/
  • You can create a custom AuraEffectHandler for every effect by setting the AuraEffectHandlerCreator field (see Deserter example)

Aura Duration

  • Auras might have a duration:
    • Auras with a duration: Definitely cease to exist after the 'Duration' of the Spell in milliseconds
    • Auras without duration: Never timeout
    • -> Both types can still be cancelled if certain other conditions apply (eg. when attacked, charges run out etc), depending on how the Spell is defined

Periodic and non-periodic Auras

AuraEffects are said to be periodic if their Amplitude > 0:

  • Periodic AuraEffects are applied repeatedly
    • They "tick" (apply their effects) every Amplitude milliseconds
    • One Aura can only have one Amplitude; for that it uses the first AuraEffect with a positive Amplitude
    • -> Make sure that all periodic effects of an Aura have the same Amplitude (if that is not desired, split the Aura into two Spells)
    • EXAMPLES:
      • Periodically apply damage to a target
      • Continuously remove more and more speed with every tick
  • The non-periodic effects of an Aura are applied when the Aura is added to its target
    • These effects apply an effect that will stay unmodified while the Aura is applied
    • EXAMPLES:
      • Increase or decrease stats on a target
      • Change regeneration
      • Talents that improve other spells

NOTE: Remove is only called once for both types of effects


Targeting

  • WCell's Spell targeting system is generic enough to select any type of targets during a SpellCast
  • The system allows the developer to set different targeting methods for AI and player casters
  • The system also allows to simply give the spell a set of initial targets and skip automatic target finding
  • It is important to understand that targeting is done per SpellEffect:
    • Consider an AoE fireball: Strong damage against the main target (effect #1) and it also incinerates everyone in a few yards radius (effect #2)
    • Clearly, the two effects have different sets of targets
    • However, many spells only have a single set of targets, in which case you can use the Spell.Override* convenience methods to set the same targeting parameters for all effects (OverrideCustomTargetDefinitions, OverrideAITargetEvaluators etc)
  • Every SpellEffect has 2 x 3 fields that decide which targets to select:
    • 2 x ImplicitTargeType: ImplicitTargetA and ImplicitTargetB are already given for every spell. The enum can be found in: Core/WCell.Constants/Spells/
    • 2 x TargetHandlerDefinition: CustomTargetHandlerDefintion and AITargetHandlerDefintion define specifically what targets to select
    • 2 x TargetEvaluator:CustomTargetEvaluator and AITargetEvaluator enables you to choose a limited amount of targets from all those that fit the constraints
  • Make sure to set equal parameters for all effects that have the same targets!


ImplicitSpellTargetType

  • The value of ImplicitTargetA and ImplicitTargetB of each effect can be found in the spell dump
  • Most effects only have one of them set, but you can set both
    • For example, some spells set the first target to Self and the second target to SingleFriend
  • These ImplicitSpellTargetTypes map to a set of TargetHandlerDefinitions in DefaultTargetDefinitions
  • If a TargetHandler is given, these values are ignored
  • If you can make the spell work, using this set of default target-types, you don't need to look into the other options


TargetHandlerDefinitions

  • IMPORTANT: AITargetHandlerDefintion is only used when an AI-controlled Object or Unit casts a spell and CustomTargetHandlerDefintion in any other case (including player casts and trigger spells)
    • You do not need to set both, if you are working on a pure AI spell or pure Player spell - Most spells are not shared by players and NPCs!
    • However, AI casts can also happen if AI takes over a Character
  • TargetHandlerDefinitions consist of two things: An Adder and a Filter:


Adders

  • Adders add all targets of a spell to the SpellTargetCollection of a SpellEffectHandler
  • There are two types of Adders:
    • Single-target: Those that add only the caster, his target, his pet etc
    • Multi-target: For AoE spells
  • Only targets that pass the default Spell-related checks and the given filter (if a filter is given), are added
  • You can either select Adders from the DefaultTargetAdders class, or provide your own

Filters

  • You can use any amount of filters
  • Targets are only selected, if they pass all filters
  • You can either select Filters from the DefaultTargetFilters class, or provide your own

TargetEvaluators

  • IMPORTANT: AITargetEvaluator is only used when AI casts a spell and CustomTargetEvaluator in any other case (including player casts and trigger spells)
  • Evaluators are usually only used for AI casters because players can evaluate and choose targets themselves
  • If an evaluator is given and the max amount of targets has already been found, iteration continues and the Evaluator is used to replace the worst currently found target with a better one
  • Evaluators can of course, only be used in combination with multi-target Adders (since, if only one choice is available, it cannot be compared to any other)
    • Keep in mind: The AddRandom* adders are single-target - To make use of the evaluator, you usually want to use either of the AddArea* Adders from DefaultTargetAdders (or provide your own)
  • Also: Evaluators in combination with the AddArea* Adders are useless for effects that do not have a maximum amount of targets (by setting SpellEffect.MaxTargets or Spell.MaxTargetEffect)
  • You can either select Evaluators from the DefaultTargetEvaluators class, or provide your own
  • You can use the DefaultTargetEvaluators.RandomEvaluator to select a random set from all possible targets

Custom Targeting Examples

These are examples that show how to set targeting behavior for AI in single-effect spells.

Add the enemy that is nearest to the caster:

spell.MaxTargets = 1;
spell.OverrideAITargetDefinitions(
	DefaultTargetAdders.AddAreaSource, 									// Adder
	DefaultTargetEvaluators.NearestEvaluator, 							        // Evaluator
	DefaultTargetFilters.IsHostile);		                                                        // Filters

Add single random player near caster:

spell.MaxTargets = 1;
spell.OverrideAITargetDefinitions(
	DefaultTargetAdders.AddAreaSource, 									// Adder
	DefaultTargetEvaluators.RandomEvaluator, 							        // Evaluator
	DefaultTargetFilters.IsHostile, DefaultTargetFilters.IsPlayer);		                                // Filters

Add the two most wounded friendly units near caster:

spell.MaxTargets = 2;
spell.OverrideAITargetDefinitions(
	DefaultTargetAdders.AddAreaSource, 									// Adder
	DefaultTargetEvaluators.MostWounded, 								        // Evaluator
	DefaultTargetFilters.IsFriendly);									// Filters

Add any 5 wounded friendly units near caster:

spell.MaxTargets = 5;
spell.OverrideAITargetDefinitions(
	DefaultTargetAdders.AddAreaSource, 									// Adder
	DefaultTargetEvaluators.AnyWounded, 								        // Evaluator
	DefaultTargetFilters.IsFriendly);									// Filters

Add caster and every friendly with the same current combat target (aggressor):

spell.OverrideAITargetDefinitions(
	DefaultTargetAdders.AddAreaSource, 									// Adder
	DefaultTargetFilters.IsFriendly, IsCasterOrFightingCastersTarget);		                        // Filters

// ...

void IsCasterOrFightingCastersTarget(SpellEffectHandler effectHandler, WorldObject target, ref SpellFailedReason failedReason)
{
	var caster = effectHandler.Cast.CasterUnit as NPC;
	var npc = target as NPC
	if (caster == null || npc == null || 
		(npc != caster && npc.ThreatCollection.Aggressor != caster.ThreatCollection.Aggressor))
	{
		// the reason doesn't matter when casting AoE spells
		failedReason = SpellFailedReason.Error;
	}
}

Note that this one does not have an evaluator - even if it had one, it would not do anything, if the spell does not set a max amount of targets.

Spell Fix Examples

Fireball

  • The above mentioned Fireball has two default effects: SchoolDamage and ApplyAura (PeriodicDamage)
  • Both effects are negative and will only be applied on your currently selected target, if it is hostile
  • SchoolDamage will simply apply its EffectValue as damage of the given school to the enemy, on impact
  • The Periodic Damage Aura will periodically damage the enemy every <Amplitude> milliseconds over a total of the Spell's Duration

Deserter (Id: 26013)

  • Deserters are not allowed in Battlegrounds for a small amount of time
  • But the Deserter spell only left a visible Aura and did nothing
  • Here is how we fixed it, step by step:

1. Look at the Spell definition

  • According to the Spell Dump the Deserter spell is defined as follows:
Spell: Deserter (Id: 26013) [Deserter]
	Attributes: Attr_8_0x100, CastableWhileDead, CastableWhileMounted, Attr_26_0x4000000, CastableWhileSitting, UnaffectedByInvulnerability
	AttributesEx: AttrEx_3_0x8, RemainStealthed, Negative
	AttributesExB: AttrExB_0_0x1, AttrExB_2_0x4, AttrExB_14_0x4000, DoesNotNeedShapeshift
	AttributesExC: PersistsThroughDeath, AttrExC_28_0x10000000
	AttributesExD: AttrExD_2_0x4
	ProcChance: 101
	Duration: 900000 - 900000 (0)
	Range: 0 - 100
	SchoolMask: Arcane

		Effect: ApplyAura (Dummy)
			ImplicitTargetA: Duel

2. What is it supposed to do?

  • It is supposed to flag its wearer as Deserter during its duration

3. What *does* it do?

  • The spell only applies one dummy aura-effect (as we can see it in the Spell definition above)
  • We know, we have to do something because by default dummy-effects don't do anything and need special attention

4. Consider your options

  • We usually want to re-use already existing code, but there is no default Aura-type that we could replace the Dummy with
  • The only remaining option is to create a custom AuraEffectHandler

5. Write the code

  • First we create a custom AuraDeserterHandler class (which extends AuraEffectHandler):
			public class AuraDeserterHandler : AuraEffectHandler
			{
				protected override void Apply()
				{
					var chr = Owner as Character;
					if (chr != null)
					{
						chr.Battlegrounds.IsDeserter = true;
					}
				}
	
				protected override void Remove(bool cancelled)
				{
					var chr = m_aura.Auras.Owner as Character;
					if (chr != null)
					{
						chr.Battlegrounds.IsDeserter = false;
					}
				}
			}
  • Then we set the dummy-effect's AuraEffectHandlerCreator-field:
			SpellHandler.Apply(spell => {
				spell.Effects[0].AuraEffectHandlerCreator = () => new AuraDeserterHandler();
			}, 
			SpellId.Deserter);

6. A little bit of magic

  • Most spells can be fixed very easily, some however need a little bit of work (in the core or addons) to support the feature
  • As you can see, the AuraDeserterHandler above does nothing but toggling one single bool property: IsDeserter
  • In order to actually make this thing work we had to write the logic for that property first:
		public bool IsDeserter
		{
			get { return m_isDeserter; }
			set
			{
				m_isDeserter = value;
				if (m_isDeserter)
				{
					CancelAllRelations();
				}
			}
		}
  • The property sets a single boolean value which the Battleground code uses at some points to prevent deserters from doing anything BG-related
  • Also when activating the deserter flag, it will cancel all existing Battleground relations (queues, invitations etc)

What this code exactly does goes beyond Spell-handling and is only interesting for people who are interested into Battleground-core development.

However, as you can see: Creating or modifying any kind of spell to do almost anything, is not hard at all!

Personal tools