-
Notifications
You must be signed in to change notification settings - Fork 1
Chapter 2: Shieldwall (under construction)
In this chapter you'll find an explanation on how to create an AoE spell that will provide a bonus modifier to the armor rating of creatures that belong to the same faction as the spellcaster in a given radius. This chapter is written under assumption that you're already familiar with the concepts explained in Chapter 1 and Chapter 0, so we will skip over mod initialization and spell handlers.
1.1. Reserve two numbers for new Spell Types
1.2. Reserve two numbers for new Spell Jobs
1.3. Add Spell Types to GameData.cff
1.4. Create new spells corresponding to new Spell Types
2.1. Initialize the Iterator
2.2. Use the Iterator to retrieve all figure_indices in given radius
3.1. Add the function 'checkCanApply' into the spell effect handler
3.2. Within the refresh handler add a check for whether the figure has an active spell on it
3.3. Prevent a spell from being applied to a target if the refresh handler returned that the target is already affected by the specific spell
4.1. Add bonus modifier to figure's statistic (armor, health, mana, etc.) with an individual spell applied to target (triggered within the AoE spell)
4.2. Record the bonus modifier within the spell XData key SPELL_STAT_MUL_MODIFIER
4.3. Remove the bonus modifier (add the negative value of the same amount to figure's statistic) when the spell ends
The Shieldwall affects specific amount of figures around the spellcaster. Those figures must belong to the same team as the spellcaster. The spell adds a percentile modifier to target's armor rating. The effect lasts for certain amount of time specified in milliseconds. The spell can't affect the targets which are already under its effect.
Also, we want the spell to apply its effect to the spellcaster for free, unless the spellcaster is already affected with the Shieldwall.
From an engine perspective, the Shieldwall will be made of two independent spells. Those spells are governed each by their own spell type.
The first part will implement the spell activation and AoE logic. Let's call this part the Shieldwall Group. The spell should be linked to the scroll which the character uses in order to cast a spell. The first spell will cease to exist immediately after individual effects are applied to targets.
The second part will implement the individual instances of the Shieldwall. Let's call this spell simply Shieldwall. It will apply a buff to figure's armor rating, and remove this buff after a specified amount of time passes.
This tutorial shows how to design a spell which can't be re-applied again to the same target until its effect expired. However, it's possible to implement it in another way. The spell could be made to reset its duration if it's casted to a figure which is being under the effect of Shieldwall already.
This tutorial will use two new spell types (0xf3 = 243, 0xf4 = 244), and two new spell job numbers (0xaa = 170, 0xab = 171) in order to avoid interference with other examples. However, this isn't obligatory requirement. Feel free to work with new spells in a way which fits you personally.
Also, if you have completed Chapter 0, you might re-link the Shieldwall Group to a scroll which you have created before. It doesn't really matter in this case, and it will significantly shorten the preparations, because this scroll already exists and equipped to your character.
In order to make Shieldwall AoE spell centered on the spellcaster, it should have Spell Flags set to 25. Remember that Spell Type needs to have Description ID filled with some number (2017, for example), otherwise it will cause the error on sight.

Spell flags determine in what way the spell can be used within the game. We know only what a few other flags do.
4 (0b00100) - set if spell should be treated as an aura
8 (0b01000) - set if spell should only be allowed to cast on self
16 (0b10000) - set if spell should appear as a buff/debuff on portrait
25 (0b11001) - set for friendly buffs casted with the center on self
Spell UI Handle isn't obligatory, but it gives a spell its own icon visible on a character UI. We don't have own art, so we use other icons of other spells here.
The Shieldwall Group spell should have all parameters set like this. Spell radius operates with the same game units as spell range, so 16 isn't a small value.

The linked spell ID is the ID of the individual Shieldwall spell (usually, +1 of Shieldwall group ID).
The individual Shieldwall spell should look like this.

This part offers a little new to what we know already. You should include sfsf.h and other libraries (stdio, windows for code under Windows OS) to your module. It's necessary declare pointers to the Spellforce framework and functions groups to acces them later. However, we're going to use two new Spell Types and Spell Jobs simultaneously. We're also going to use them a bit more extensively in order to properly simulate spell refresh behaviour. Let's define Spell Types and Spell Jobs with macros.
#define SHIELD_WALL_GROUP_LINE 0xf3
#define SHIELD_WALL_GROUP_JOB 0xaa
#define SHIELD_WALL_LINE 0xf4
#define SHIELD_WALL_JOB 0xabThe full code for this section will be this:
#include "../../src/api/sfsf.h"
#include <windows.h>
#include <stdio.h>
#define SHIELD_WALL_GROUP_LINE 0xf3
#define SHIELD_WALL_GROUP_JOB 0xaa
#define SHIELD_WALL_LINE 0xf4
#define SHIELD_WALL_JOB 0xab
SpellforceSpellFramework *sfsf;
SpellFunctions *spellAPI;
ToolboxFunctions *toolboxAPI;
FigureFunctions *figureAPI;
IteratorFunctions *iteratorAPI;
RegistrationFunctions *registrationAPI;We have to declare three main functions which make the mod operational.
- The function to load the mod as a DLL within the OS (
WINAPI DllMain). - The function to register the mod within the framework (
__declspec(dllexport) *RegisterMod). - The function to initialize the mod
('__declspec(dllexport) *InitModule').
Because we operate with two independent spells instead of one, we should expand the *InitModule function by adding another spell to register.
SFSpell *shield_wall_group_spell = registrationAPI->registerSpell(SHIELD_WALL_GROUP_LINE); // using macro instead of directly setting number
registrationAPI->linkTypeHandler(shield_wall_group_spell, &shield_wall_group_type_handler);
registrationAPI->linkEffectHandler(shield_wall_group_spell, SHIELD_WALL_GROUP_JOB, &melee_group_ability_effect_handler);
registrationAPI->linkRefreshHandler(shield_wall_group_spell, &shield_wall_group_refresh_handler);Refresh handler is something new for us. This handler will be used to check whether we're applying the spell to the target currently affected by previous instance of the Shieldwall. We'll trigger this handler within effect handler linked to Shieldwall Group (melee_group_ability_effect_handler) later then.
It's also worth of mentioning, that we named Shieldwall Group effect handler as melee_group_ability_effect handler. We do so, because this implementation could be used for any other spell which works along the same principle with the Shieldwall.
The registration of individual Shieldwall spell is done as usual. We declare SFSpell pointer and link three handlers (type, effect, end) to it.
SFSpell *shield_wall_spell = registrationAPI->registerSpell(SHIELD_WALL_LINE);
registrationAPI->linkTypeHandler(shield_wall_spell, &shield_wall_type_handler);
registrationAPI->linkEffectHandler(shield_wall_spell, SHIELD_WALL_JOB, &shield_wall_effect_handler);
registrationAPI->linkEndHandler(shield_wall_spell, &shield_wall_end_handler);The full code for InitModule will be this:
extern "C" __declspec(dllexport) void InitModule(SpellforceSpellFramework *framework)
{
sfsf = framework;
spellAPI = sfsf->spellAPI;
toolboxAPI = sfsf->toolboxAPI;
figureAPI = sfsf->figureAPI;
iteratorAPI = sfsf->iteratorAPI;
registrationAPI = sfsf->registrationAPI;
// we use its own pointer for each of spells
SFSpell *shield_wall_group_spell = registrationAPI->registerSpell(SHIELD_WALL_GROUP_LINE);
registrationAPI->linkTypeHandler(shield_wall_group_spell, &shield_wall_group_type_handler);
registrationAPI->linkEffectHandler(shield_wall_group_spell, SHIELD_WALL_GROUP_JOB, &melee_group_ability_effect_handler);
registrationAPI->linkRefreshHandler(shield_wall_group_spell, &shield_wall_group_refresh_handler);
// we use its own pointer for each of spells
SFSpell *shield_wall_spell = registrationAPI->registerSpell(SHIELD_WALL_LINE);
registrationAPI->linkTypeHandler(shield_wall_spell, &shield_wall_type_handler);
registrationAPI->linkEffectHandler(shield_wall_spell, SHIELD_WALL_JOB, &shield_wall_effect_handler);
registrationAPI->linkEndHandler(shield_wall_spell, &shield_wall_end_handler);
}Also, it's necessary to declare methods for every handler mentioned above except for refresh handler. You can use the example code from the previous chapter as a template. It will be explained how to declare the refresh handler below specifically.
Before getting to AoE implementation, let's quickly initialize the spell.
// we pull the pointer for this instance of spell
SF_GdSpell *spell = &_this->active_spell_list[spell_index];
uint16_t source_index = _this->active_spell_list[spell_index].source.entity_index;
// we load the spell parameters from GameData.cff
SF_CGdResourceSpell spell_data;
spellAPI->getResourceSpellData(_this->SF_CGdResource, &spell_data, spell->spell_id);We loaded spell data. Let's add visuals to spell.
// we declare structure for relative position of visual effect
SF_CGdTargetData relative_data;
figureAPI->getPosition(_this->SF_CGdFigure, &relative_data.position, source_index);
relative_data.entity_type = 4;
relative_data.entity_index = 0;
uint32_t unused;Because the spell is AoE, there will be more code than for Ignite spell which affected a single target. The parameters which we passed an empty SF_Rectangle aux_data before, will get a proper SF_Rectange now. It will make visual effect fill entire area instead of showing only upon the spellcaster.
// we declare structure to specify the area affected by the AoE effect
SF_Rectangle hit_area;
// we declare structure to store the center of the spell
SF_Coord cast_center;
// we get XY coordinates of the spell center with API function and store them into SF_coord struct cast_center
// it's important to mention that in such functions we usually address the structure pointer, not the structure itself
figureAPI->getPosition(_this->SF_CGdFigure, &cast_center, source_index);We've got spell center coordinates. We pull spell radius from spell parameters stored in GameData.cff. Now let's get a squared circle which the spell visual effect would fill in.
// we get coordinates of area affected and record them as a squared circle
// spell_data.params[0] stands for a spell radius
spellAPI->getTargetsRectangle(_this, &hit_area, spell_index, spell_data.params[0], &cast_center);The spell radius is measured in the same units as the spell range. Hence, we can pull radius for visuals from spell parameters too.
Let's apply the visual effect using all data we collected above: cast center and hit_area as a rectangle encompassing targets within. Worth of mention, that we also used empty (having {0,0} vertices) rectangle in the previous chapter. It was called aux_data, and probably you wondered why did we need that at all.
spellAPI->addVisualEffect(_this, spell_index, kGdEffectSpellHitWorld, &unused, &relative_data, _this->OpaqueClass->current_step, 0x19, &hit_area);The visual effect is applied. It's time to move to applying buff to figure's armor now.
The Spellforce provides internal methods to work with areas of effect. Those methods are opaque for us. They can be used via an Iterator class (CGdFigureIterator) with the framework. Because those methods are opaque, we just declare iterator, pass it coordinates defining specific area, and it returns us figures' indices in exchange.
CGdFigureIterator figure_iterator;We declared an iterator. Let's initialize it with setupFigureIterator function of iteratorAPI.
iteratorAPI->setupFigureIterator(&figure_iterator, _this);Let's assign iterator area. We use iteratorSetArea function for this. We pass it iterator variable, center of the spell and spell radius which we loaded from game files before.
iteratorAPI->iteratorSetArea(&figure_iterator, &cast_center, spell_data.params[0]);The iterator is operational from now. Basically, it returns us an array of figures indices. We can navigate through the array using iteratorAPI->getNextFigure command which returns the next value in the array. It works somehow like that.
target_index = iteratorAPI->getNextFigure(&figure_iterator);
If there are no more figures in the area, getNextFigure will return 0. Hence, it could be used in a way like this:
target_index = iteratorAPI->getNextFigure(&figure_iterator);
while (target_index != 0)
{
//do something to current figure
target_index = iteratorAPI->getNextFigure(&figure_iterator);
}After target_index reached 0, it means that the iterator has stopped working, and can be disposed. We'll return to it a bit later.
As a side observation, it appears that the iterator organizes the figures by area in a different sequence than simply going from the nearest to the farthest. Testing suggests that the iterator adheres to the internal arrangement of the figures, moving in a cycle from the one with the lowest figure_index to the one with the highest figure_index.
Because of that, we recommend making spellcaster the first figure targeted by the Shieldwall for purposes of this example. It will make sure that the spellcaster will always be affected by the spell, even if Iterator prioritizes other figures over source figure. Let's add band-aid to it now.
uint16_t target_index = source_index;Let's load figures amount from game data.
uint16_t figure_count = spell_data.params[1];We setup the iterator. We can use it in a cycle in order to add individual Shieldwall effect to valid figures.
while (target_index != 0 && figure_count != 0)We keep checking figures in radius as long as we didn't exceed figures limit and as long as there are valid figures in iterator radius.
{
if (((int16_t)(_this->SF_CGdFigure->figures[target_index].owner) == (int16_t)(_this->SF_CGdFigure->figures[source_index].owner)) &&
(((uint8_t)(_this->SF_CGdFigure->figures[target_index].flags) & 0xa) == 0) &&
(toolboxAPI->isTargetable(_this->SF_CGdFigureToolBox, target_index)))We should check whether the figure we're going to apply effect to belongs to the spellcaster team, whether it's alive and whether it can be targeted with spells.
Notice we check two former flags directly via figure structure rather than with API functions as we did in the previous chapter. Both ways are okay.
{
spell->target.entity_index = target_index;
if (spellAPI->checkCanApply(_this, spell_index))The checkCanApply function calls the refresh handler. This handler will be explained below, for now it's important to notice, that checkCanApply will return 0 if the target is currently affected by the Shieldwall effect, and 1 if it's currently unaffected.
We will need to use target_index in refresh handler to know what figure we're currently checking for being affected by the Shieldwall.
The situation gets complicated, because the target index is a local variable of the current effect handler and we wouldn't be able to access it within the refresh handler. However, because we pass the same spell which triggered the effect handler to the refresh handler, we could deliver target_index within this spell.
Let's finalize our AoE cycle before moving to the refresh handler logic.
Let's assume for a moment that all checks were successful! The target is alive, belongs to spellcaster faction, and is targetable. Also, the refresh handler returned that the target is currently unaffected by the Shieldwall spell. It means that we should apply the individual Shieldwall spell to a figure. It's done in the following way:
{
SF_CGdTargetData source = {spell->source.entity_type, source_index, {0, 0}};
SF_CGdTargetData target = {spell->source.entity_type, target_index, {0, 0}};We declare structs for source of the spell (spell caster) and the spell target.
spellAPI->addSpell(_this, spell_data.params[3], _this->OpaqueClass->current_step, &source, &target, 0);We add the Shiedwall spell to a target with SpellAPI function addSpell. To know which spell we should use to add individual Shieldwall to a target, we load its number from game data with accessing spell_data.params[3]. This parameter should correspond to the Shieldwall spell ID as it's set in GameData.cff.
_this->OpaqueClass->current_step corresponds to game internal tick (not spell tick) which the new spell will start with.
The last parameter is unused, leave it to be 0.
if (target_index != source_index)
figure_count--;
}
}We also should record that we successfully applied the spell to a target with decreasing figures limit by 1. The only exception is when target index is equal to source index, the spellcaster is supposed to get spell effect for free.
When figures limit reaches 0, it will break the cycle and end the Shieldwall Group effect.
However, if the loop hasn't finished yet, let's continue searching for the next target within the iterator.
target_index = iteratorAPI->getNextFigure(&figure_iterator);
}When all figures in radius were checked or all usages of spell was spent, let's stop the spell.
spellAPI->setEffectDone(_this, spell_index, 0);Finally, we should release the memory which we allocated for iterator
iteratorAPI->disposeFigureIterator(&figure_iterator);
}The refresh handler should be called whenever we're risking to cast the persistent spell on a same target more than once. It's necessary to do this to avoid stacking the spell over the same figure over and over. To put it simply, the refresh handler is automatically called whenever we call the checkCanApply command. These functions share the same spell global object (we know it as *_this) and the same spell index. It allows some versatility in manipulating the same spell they share.
The refresh handler is declared in the following way:
int __thiscall shield_wall_group_refresh_handler(SF_CGdSpell *_this, uint16_t spell_index)
{
if () // we'll implement the specific condition which prevents from being applied later
return 0;
else // if we don't meet this condition, we're free to apply the spell to a target
return 1;
}Let's declare usual spell pointer and also immediately get target index which we carried from Shieldwall Group effect handler.
int __thiscall shield_wall_group_refresh_handler(SF_CGdSpell *_this, uint16_t spell_index) //we casted shieldwall group again before the previous expired
{
SF_GdSpell *spell = &_this->active_spell_list[spell_index];
uint16_t target_index = spell->target.entity_index;Then we should declare spell_data structure to pull spell parameters from game files into it.
SF_CGdResourceSpell spell_data;
spellAPI->getResourceSpellData(_this->SF_CGdResource, &spell_data, spell->spell_id); The situation gets more complicated in our case, because the Shieldwall affecting the target figure is implemented with another spell than the Shieldwall Group. We need to know how this another spell looks like in order to check the figure for being affected by it, so let's load its spell data too.
SF_CGdResourceSpell spell_data_2;
spellAPI->getResourceSpellData(_this->SF_CGdResource, &spell_data_2, spell_data.params[3]); // we know individual Shieldwall spell id by link to it stored within Shieldwall Group spell parametersWe can check for whether the target is affected with a spell with the function toolboxAPI->hasSpellOnIt. It requires spell_line_id to identify the spell. Gladly, we can pull it from the spell data of the individual Shieldwall.
if (toolboxAPI->hasSpellOnIt(_this->SF_CGdFigureToolBox, target_index, spell_data_2.spell_line_id) == 1)If the toolboxAPI->hasSpellOnIt returns TRUE, it means that the target is affected by the specified spell. In this case we should prevent the Shieldwall from being applied. Let's return FALSE to checkCanApply.
{
return 0;
}However, if toolboxAPI->hasSpellOnIt returns FALSE, it means the target is currently unaffected. We can add the individual Shieldwall spell to it.
else
{
return 1;
}With this, the logic for the Shieldwall Group is complete. The Shieldwall Group will add a spell to multiple targets and to the spellcaster for free, as long as they aren't affected with the Shieldwall currently.
The full code for the refresh handler will be this:
int __thiscall shield_wall_group_refresh_handler(SF_CGdSpell *_this, uint16_t spell_index) //we casted shieldwall group again before the previous expired
{
SF_GdSpell *spell = &_this->active_spell_list[spell_index];
// we declare target index with value which we stored into SHIELDWALL GROUP spell above
uint16_t target_index = spell->target.entity_index;
// we declare spell_data for SHIELDWALL GROUP spell, because we need to pull the spell id of SHIELDWALL spell linked with SHIELDWALL GROUP spell
SF_CGdResourceSpell spell_data;
spellAPI->getResourceSpellData(_this->SF_CGdResource, &spell_data, spell->spell_id);
// we declare own spell_data for SHIELDWALL spell
SF_CGdResourceSpell spell_data_2;
spellAPI->getResourceSpellData(_this->SF_CGdResource, &spell_data_2, spell_data.params[3]);
// we check whether the figure has the SHIELDWALL spell applied to it already
// method hasSpellOnIt accepts spell_line_id property of spell data as argument in order to idenfity the spell
if (toolboxAPI->hasSpellOnIt(_this->SF_CGdFigureToolBox, target_index, spell_data_2.spell_line_id))
// the SHIELDWALL spell already exists on the target
{
return 0;
}
else
// the target isn't affected by the SHIELDWALL spell
{
return 1;
}
}Speaking from the perspective of code, the individual Shieldwall spell isn't very different from the Ignite spell from the previous chapter. It's called by the Spell Type handler. It's logic is implemented within the Spell Effect handler. It uses the Spell End handler as backup exit. Let's make the Spell type handler first.
void __thiscall shield_wall_type_handler(SF_CGdSpell *_this, uint16_t spell_index)
{
// we associate spell type with a spell job
_this->active_spell_list[spell_index].spell_job = SHIELD_WALL_JOB;
spellAPI->setXData(_this, spell_index, SPELL_TICK_COUNT_AUX, 0);
// SPELL_STAT_MUL_MODIFIER will store a percentage by which the target's armor was increased
// the percentage will be individual for every figure depending on its previous armor rating
spellAPI->setXData(_this, spell_index, SPELL_STAT_MUL_MODIFIER, 0);
}This looks familiar. However, the spell got new XData key SPELL_STAT_MUL_MODIFIER. We will use this key to know how many the spell affected the target's armor class, and to know how many we should substract from it when the spell ends. SPELL_STAT_MUL_MODIFIER is a percentile value stored as an integer. It means this calculates percents from 1 too 100 instead of 0 to 1.
The SPELL_TICK_COUNT_AUX is also going to be used in a different way. Before the spell lasted for a specified amount of ticks, every tick lasted for specified amount of time. Currently, we don't need that many ticks. We will use only tick 0 to mark the spell beginning, and tick 1 to mark the spell end.
It's time to proceed to Spell Effect handler.
void __thiscall shield_wall_effect_handler(SF_CGdSpell *_this, uint16_t spell_index)
{
SF_GdSpell *spell = &_this->active_spell_list[spell_index];
//the SHIELDWALL spell is directly applied to a figure
//we get the target index of affected figure
//however, it's worth of mentioning, that the source_index would return the index of a figure which casted SHIELDWALL GROUP (initial component implementing AoE logic of the spell) in case we want to do something with a caster
uint16_t target_index = spell->target.entity_index;Let's add the ticks counter.
uint32_t current_tick = spellAPI->getXData(_this, spell_index, SPELL_TICK_COUNT_AUX);
spellAPI->addToXData(_this, spell_index, SPELL_TICK_COUNT_AUX, 1);We get the current tick (should be 0 when the effect is called the first time), and increase the amount of ticks passed by one. The next time the Spell Effect handler is triggered, it will be considered to be ending (technically, in this lazy implementation it will count ticks from 0 to 2).
Let's load spell data from GameData.cff.
SF_CGdResourceSpell spell_data;
spellAPI->getResourceSpellData(_this->SF_CGdResource, &spell_data, spell->spell_id);And let's immediately pull ticks interval (spell duration) from there.
uint16_t ticks_interval = spell_data.params[1];We initialized ticks timer and spell data. The algorithm will be similar to how we applied persistent effect in Ignite spell, but not the same.
For example, we don't check for target flags like alive, targetable, belonging to the same team with the spellcaster, because we've already checked for them within the Shieldwall Group effect which is supposed to be the only way to trigger the Shieldwall spell over a single target.
if (current_tick == 0)This is the first tick. Within this tick, we're going to add bonus modifier to a target's armor and then make the spell to wait for specified amount of time.
{
uint16_t recalc_value = spell_data.params[0];First let's know the bonus modifier value. We pull it from spell data. It should be percentile value expressed in range from 1 to 100.
figureAPI->addBonusMultToStatistic(_this->SF_CGdFigure, ARMOR, target_index, recalc_value);We apply the bonus we've obtained from spell data to figure's statistic.
spellAPI->setXData(_this, spell_index, SPELL_STAT_MUL_MODIFIER, recalc_value);We record modifier directly within the spell, to remove the same percentage when the spell ends
Then we disable the spell from being triggered for a specified number of internal game ticks. This time correspond with the tick interval set in milliseconds in game data.
_this->active_spell_list[spell_index].to_do_count = (uint16_t)((ticks_interval * 10) / 1000);
return;
}The very first tick is finished. Now we should add a check for the next tick and remove the spell together with the bonus modifier.
else
{
uint16_t recalc_value = spellAPI->getXData(_this, spell_index, SPELL_STAT_MUL_MODIFIER);
// we add negative value of this multiplier to figure's statistic
figureAPI->addBonusMultToStatistic(_this->SF_CGdFigure, ARMOR, target_index, -recalc_value);
spellAPI->setEffectDone(_this, spell_index, 0);
return;
}To make the spell behaviour completely safe, let's also implement the Spell End handler for the cases when spell wasn't finished normally for unknown reasons.
void __thiscall shield_wall_end_handler(SF_CGdSpell *_this, uint16_t spell_index)
{
SF_GdSpell *spell = &_this->active_spell_list[spell_index];
uint16_t target_index = spell->target.entity_index;
// we pull the percentage by which the target's armor rating was increased
uint16_t recalc_value = spellAPI->getXData(_this, spell_index, SPELL_STAT_MUL_MODIFIER);
// we remove the bonus to target's armor rating by adding negative amount of this value
figureAPI->addBonusMultToStatistic(_this->SF_CGdFigure, ARMOR, target_index, -recalc_value);
spellAPI->removeDLLNode(_this, spell_index); // we remove spell from the list of active spells over the target
spellAPI->setEffectDone(_this, spell_index, 0); // we end a spell
}The spell is ready. It can be tested on any map which allows summoning additional units. The affected units will get percentile bonus to their armor rating for the duration of the spell. The targets already having the active effect on them, won't be affected.
The full source code of this example is available here.