# Spells

How spells are structured and triggered

# Creating New Spells

New spells are intended to be simple to add, if they do not do esoteric things. In particular spells that deal damage, heal, apply a buff, debuff or absorb shield, or combine these elements, should be fairly simple to implement. To add a new spell, a few steps are required.

**Creating the Database Entry**  
The spell database data/db\_spells.json needs to be appended with an entry for the new spell. This is done by creating a new entry with a new unique ID, and specifying the key and value pairs. Which keys are required depends on the type of spell that is added. More information can be found [here](https://wiki.shiny.space/books/technical/page/spell-dictionary).

The spell also needs to be added to the spell\_list of the unit or interactable that should use the spell, in either the data/db\_stats\_player.json file, the data/db\_stats\_npc.json file, or the data/db\_stats\_interactable.json file.

**Creating the Spell Script**  
Every spell scene needs an attached spell script that handles the triggering of the spell. The script should inherit from the [BaseSpell](https://wiki.shiny.space/books/technical/page/basespell-class) class, which contains some core functionality. The \_ready function of the spell script should set the ID as a string, and call initialize\_base\_spell with the spell ID as the argument. This creates the dictionaries that contain the spell data, and the cooldown timer. The script should further contain a trigger function, which determines the source and target of the spell, and goes through a number of checks that determine whether the spell can actually be used when it is triggered. The standard checks, such as whether the spell is already on cooldown, whether the target is within range, etc., are contained in the [BaseSpell](https://wiki.shiny.space/books/technical/page/basespell-class) class.

After the checks, the start\_cast function of the [BaseSpell](https://wiki.shiny.space/books/technical/page/basespell-class) class can be called, which sets the is\_casting state to true and triggers the gcd if applicable. This requires the cast\_success function to be passed as an argument, as it links the timeout of the cast timer to this function. The cast\_success function then needs to be added to the spell script to determine what happens when the spell is successfully cast. This usually consists of applying the resource cost, sending the combat event, and calling finish\_cast. finish\_cast is a function in the [BaseSpell](https://wiki.shiny.space/books/technical/page/basespell-class) class, which plays an animation, disconnects the cast timer timeout signal from the cast\_success function, starts the spell cooldown if it exists, and sets the is\_casting state to false.

Instant cast spells can forego the call to start\_cast, as the cast finishes immediately. Instead, the trigger function can send a combat event, trigger the gcd, and call finish\_cast. finish\_cast requires the cast\_success callable as an argument, for which the [BaseSpell](https://wiki.shiny.space/books/technical/page/basespell-class) class has a dummy function that does nothing, and is intended for use in this case.

**Note:** Spells that increase the maximum health of a target by applying a buff should in principle also send a second combat event to heal the target for that amount. This is done to avoid reducing the health percentage every time such a buff is applied, and to make sure that a tank increasing his maximum health is also healed for that amount, as doing otherwise might feel clunky. If the heal component is not added, it should be carefully reasoned why not. The heal spell requires a separate spell dictionary to be created, which requires the spelltype key to be set to "heal", and requires the name and can\_crit keys. If no value is prescribed (which can be constructed from the buff data), [more keys](https://wiki.shiny.space/books/technical/page/spell-dictionary) are required to function as a healing spell. Spell ID 8 (Player Test Buff) is an example of a buff that increases maximum HP.

# Spell Dictionary

Spells are the combat skills that players, NPCs and interactables use to affect players and NPCs. They can be divided into damage, heal, dot, hot, buff, debuff, and aura spells. All spell data are contained in the data/db\_spells.json file.

The spell data are stored in two separate dictionaries. The first is spell\_base, which contains the base values as found in the data/db\_spells.json file. This dictionary does not change. The second in spell\_current, which is based on spell\_base, but can be modified by talents or other effects. For example, if a spell gains a 10% increase to critical hit chance from a talent, this is applied to spell\_current. Only effects that are applied to individual spells are handled in this way. Changes to player stats are handled in the stats\_current dictionary in the player script.

**Basic data/db\_spells.json keys** All spells have some common keys that are required in the data dictionary, or are common optional keys for all types of spell. These are:

**Required**  
**name:** The name of the spell, as it appears in the combat log.  
**targetgroup:** The group (player, npc, hostile, etc.) that can be targeted by this spell.

**Optional  
icon:** The icon used for a spell **role:** The player role (i.e., tank, heal, melee, ranged) that uses this variant of the spell. Needed for player spells that are available to more than one role.  
**resource\_cost:** The cost of the main resource to trigger the spell. Can be negative to gain resource.  
**max\_range:** The maximum range of the spell.  
**cooldown:** The cooldown period of the spell in seconds.  
**on\_gcd:** Whether the spell is affected by and triggers the global cooldown.

**Damage Spell Keys** Damage spells require some further keys to function. These are:  
**spelltype:** Must be set to "damage".  
**effecttype:** Must be set to "physical" or "magic", and will use the corresponding modifiers.  
**value\_base:** The stat (e.g., max health, current health, primary, etc.) that is used as a base to calculate the effective damage.  
**value\_modifier:** The multiplicative modifier that is applied to the base value to calculate the effective damage.  
**avoidable:** Whether or not the spell can be avoided. Set to 1 for avoidable, or 0 for not avoidable.  
**can\_crit:** Whether or not the spell can critically hit. Set to 1 for critical hits, and 0 for no critical hits.  
**crit\_chance\_modifier:** Additive constant to the critical hit chance of the spell. Applied to the base critical hit chance of the source.  
**crit\_magnitude\_modifier:** Multiplicative modifier to the effective damage when a hit is critical.

**Healing Spell Keys** Healing spells require some further keys to function. These are:  
**spelltype:** Must be set to "heal".  
**effecttype:** Must be set to "physical" or "magic", and will use the corresponding modifiers.  
**value\_base:** The (e.g., max health, current health, primary, etc.) that is used as a base to calculate the effective healing.  
**value\_modifier:** The multiplicative modifier that is applied to the base value to calculate the effective healing.  
**can\_crit:** Whether or not the spell can critically hit. Set to 1 for critical hits, and 0 for no critical hits.  
**crit\_chance\_modifier:** Additive constant to the critical hit chance of the spell. Applied to the base critical hit chance of the source.  
**crit\_magnitude\_modifier:** Multiplicative modifier to the effective healing when a hit is critical.

**DoT Spell Keys** DoT effects use the same keys as damage spells for the damage events that they trigger. However, they require a few additional keys:  
**ticks:** The number of ticks that a DoT effect has.  
**tickrate:** The time between two ticks, in seconds.  
**unique:** Whether a DoT effect can only be present on a unit once in total (value 1), or once per source (value 0).

**HoT Spell Keys** HoT effects use the same keys as healing spells for the healing events that they trigger. However, they require a few additional keys:  
**ticks:** The number of ticks that a HoT effect has.  
**tickrate:** The time between two ticks, in seconds.  
**unique:** Whether a HoT effect can only be present on a unit once in total (value 1), or once per source (value 0).

**Buff Spell Keys** Buffs do not cause damage or healing events, and therefore require a few different keys to function:  
**auratype:** Must be set to "buff".  
**modifies:** An array of stats that is to be modified. Can contain the same stat multiple times.  
**modify\_type:** An array of the same length as "modifies", containing either "mult" or "add" for each element, which determines whether a stat is affected additively, or multiplicatively.  
**modify\_value:** An array of the same length as "modifies", containing the additive or multiplicative modifier applied to each stat.  
**duration:** The total duration of the buff effect.  
**unique:** Whether a buff effect can only be present on a unit once in total (value 1), or one per source (value 0).

**Debuff Spell Keys**  
Debuffs do not cause damage or healing events, and therefore require a few different keys to function:  
**auratype:** Must be set to "debuff".  
**modifies:** An array of stats that is to be modified. Can contain the same stat multiple times.  
**modify\_type:** An array of the same length as "modifies", containing either "mult" or "add" for each element, which determines whether a stat is affected additively, or multiplicatively.  
**modify\_value:** An array of the same length as "modifies", containing the additive or multiplicative modifier applied to each stat.  
**duration:** The total duration of the debuff effect.  
**unique:** Whether a debuff effect can only be present on a unit once in total (value 1), or one per source (value 0).

**Absorb Spell Keys**  
Absorb effects are essentially a combination of healing and buff effects. The keys they require are:  
**auratype:** Must be set to "absorb".  
**value\_base:** The value that is used as a base to calculate the total absorb amount.  
**value\_modifier:** The multiplicative modifier that is applied to the base value to calculate the total absorb amount.  
**can\_crit:** Whether or not the spell can critically hit. Set to 1 for critical hits, and 0 for no critical hits.  
**crit\_chance\_modifier:** Additive constant to the critical hit chance of the spell. Applied to the base critical hit chance of the source.  
**crit\_magnitude\_modifier:** Multiplicative modifier to the total absorb amount when a hit is critical.  
**duration:** The total duration of the absorb effect. **unique:** Whether an absorb effect can only be present on a unit once in total (value 1), or one per source (value 0).

# BaseSpell Class

Spells inherit from the BaseSpell class, which contains some functionality that is shared between all spells. This class contains the dictionaries that store spell data, the cooldown timer, and the flag that determines whether a spell uses mouseover targeting.

**initialize\_base\_spell** takes the ID of a spell as an argument, and reads the corresponding data from the data/db\_spells.json file. It then creates the spell\_base and spell\_current dictionaries, and connects the signal for the global cooldown, if the spell is on the global cooldown, and adds the cooldown timer node.

**Note:** spell\_base contains the unmodified data read from the data/db\_spells.json file. It does not change. spell\_current contains the spell data, and is modified with changes by things such as talents.

**get\_spell\_target** determines which target the spell is used on. The target can either be the mouseover target, or the selected target of the source. If both are null, an individual spell may set the target to the source in its own script. The current intent is to enable or disable mouseover targeting for each spell individually in a future update.

**is\_illegal\_target** checks whether the target of the spell is a legal target, by checking whether it is in the correct group.

**is\_on\_cd** checks whether the spell is currently on cooldown, by checking the cooldown timer.

**insufficient\_resource** checks whether the source of the spell has sufficient resources to trigger the spell.

**is\_not\_in\_range** checks whether the target of the spell is within the maximum range of the spell.

**is\_not\_in\_line\_of\_sight**  checks whether the target is within line of sight of the source. This is currently not functional.

**trigger\_cd** starts the cooldown timer of the spell, if it is not currently on cooldown with a higher remaining duration than the new cooldown would be. For example, a spell that has 25 seconds of cooldown left would not have a global cooldown applied to it, but a spell with no running cooldown timer would have the global cooldown applied to it.

**trigger\_gcd** causes the spell container to emit the gcd signal, which starts the gcd of all spells that use it, with the duration being specificed in the spell\_container.

**update\_resource** applies the resource cost (or gain) caused by the spell to the source of the spell.

**start\_cast** sets the is\_casting state to true, links the cast timer to the cast\_success function of the spell being cast, starts the cast timer, and causes the gcd signal to be emitted.

**cast\_success** is a dummy function for use with instant spells, and is overridden by spells that have a cast time. This function is then called when the cast timer times out. For spells with cast time, the overriding function in the individual spell script applies resource costs, sends the combat event, and calls finish\_cast.

**finish\_cast** plays the spell success animation, disconnects the cast timer from the cast\_success function, triggers the spell cd, sets the is\_casting state to false, and checks for queued spells that are automatically cast next.

# Spell Container

The spell\_container scene consists of a node of type Node, and is used as a parent node for all individual spell scenes that a unit or interactable has available. The spell\_container script contains a few functions.

**spell\_entrypoint** is the function that receives the request that a spell is to be triggered. It then checks whether the spell is known to the unit or interactable, and triggers the spell if it is known.

**send\_gcd** emits a signal that all child nodes listen to, if they are affected by the gcd. This signal causes these child nodes to start the gcd timer.

**set\_queue** disconnects the cd\_timer timeout signal to the cast\_queued function if it is connected. It then sets the current queued spell ID, and connects the timeout signal of that spells cd\_timer to the cast\_queued function. This causes the spell queue to be handled when the cd of the queued spell expires, i.e. when it can be cast.

**cast\_queued** attempts to cast queued spells when the current cast finishes, or when the cooldown of the queued spell expires.

This script may be expanded in the future to handle changes made to spells, and handle role swaps.

# Auras

Auras are temporary or permanent effects on a unit. They can be DoTs, HoTs, buffs, debuffs or absorbs. Every unit has an aura\_container scene, which has a Node type root node, and contains Node type nodes named dot\_container, hot\_container, buff\_container, debuff\_container, and absorb\_container. The dot and hot containers are populated by aura\_dot scenes, with hots being negative dots, the buff and debuff containers are populated by aura\_buff scenes, with debuffs being negative buffs, and the absorb container is populated by aura\_absorb scenes.

**aura\_dot**  
The aura\_dot scene stores the spell data, source, and target of a dot or hot. In the initialize function, these are stored within the scope of the full script, and the tick timer is added as a child node. In the \_ready function, the timer is started. The tick function is connected to the timeout signal of the tick timer, and triggers the combat event by calling Combat.combat\_event\_entrypoint, and counts the number of ticks that have occurred. If the maximum number of ticks is reached, the aura is removed by calling the remove\_aura function, which stops the tick timer and calls Combat.combat\_event\_aura\_entrypoint with remove=true to remove the scene. If the aura is reapplied while it is still active, the reinitialize function is called, which updates the spell data, stops the tick timer, resets the number of occurred ticks to 0, updates the total number of ticks and the tickrate, and restarts the tick timer.

**aura\_buff**  
The aura\_buff scene stores the spell data, source, and target of a buff or debuff. In the initialize function, these are stored within the scope of the full script, and the expiration timer is added as a child node. In the \_ready function, Combat.buff\_application is called to apply the buff or debuff to the current stats of the target, and the expiration timer is started. When the expiration timer expires, remove\_aura is called, which stops the expiration timer, removes the buff from the target's current stats, and finally remove the buff scene. If a buff is reapplied while it is already active, the reinitialize function is called, which updates the spell data, stops the expiration timer, reapplies the buff, which overwrites old buff values in case they have changed, updates the duration of the expiration timer, and restarts the expiration timer.

**aura\_absorb**  
The aura\_absorb scene stores the spell data, source, target, and remaining absorb value of an absorb. In the initialize function, the spell data, source and target are stored within the scope of the full script, the total absorb value is calculated by a call to Combat.value\_query, and the expiration timer is added as a child node. In the \_ready function, the expiration timer is started, and the array of active absorbs on the affected unit are sorted by increasing duration with a call to sort\_absorbs. When the expiration timer expires, remove\_absorb is called, which sets the remaining absorb value to 0, to avoid any absorbs while the aura is being removed, removes the absorb from the affected unit's array of active absorbs, and finally remove the absorb scene. If an absorb is reapplied while it is already active, the reinitialize function is called, which updates the spell data, stops the expiration timer, calls Combat.value\_query to calculate the new total absorb amount, updates the duration of the expiration timer, restarts the expiration timer, and sorts the active absorbs on the affected target.

# Spell Queue

In order to make spell casting smoother, and not have short gaps between spells when the next is not triggered frame-perfectly, a spell queuing system exists. This system allows spells to be queued towards the end of a currently active cast, or a currently active cooldown, including the gcd.

**queue\_instant** is the queue for instant cast spells that do not trigger a gcd. Multiple of these can be queued, as they do not interfere with other spell casts. When such spells are queued, they are appended to an array, and cast before the spell designated by the queue variable. The order in which instant, non-gcd spells are queued is the order in which they are also cast.

**queue** holds the ID of a single spell that either has a cast time, or triggers a gcd. Only one of these spells can be queued, because the cast time or gcd would interfere with multiple casts. This spell is triggered after the queue\_instant array is emptied of queued spells. The next spell can then be queued towards the end of this spell's cast, or towards the end of the triggered gcd.

The queued spells are cast when the cast\_queued function is called. This happens either when the cd of the queued spell expires, or when the preceding spell calls the finish\_cast function. If the queue is triggered by an expiring cd while a cast is already ongoing, the queued spells are not triggered because the player is already casting, but the queue is not emptied. The queue will only be handled in the call to finish\_cast. If the queue is triggered by finish\_cast, the cd\_timer timeout signal is disconnected to not erroneously trigger the queue twice, and the queue is handled.