Skip to content

Chapter 6.1: Dwarf Tower

Teekius edited this page Oct 16, 2025 · 20 revisions

Cheatsheet

1. Before you start

  • You should create a new Spell Type for the Hammer Throw spell in GameData.cff
  • You should create at least one spell using the new Spell Type
  • You should create a unit which will be assigned to a custom tower to run its AI. This unit can be duplicated from one of vanilla units assigned to watchtowers
  • You should add Hammer Throw spell to this unit's spell-list
  • You should create or download 3D assets for the tower
  • You should create JSON file representing tower game data
  • You should create a project (a scroll) that can be used by dwarf workers to construct the tower
  • Edit lua-scripts to register building assets
  • Edit lua-scripts to register tower unit
  • Edit lua-scripts to register Hammer Throw visual effect

2. Mod Registration Phase

  • Register a building within the framework
  • Link the custom building with its game data in JSON file

3. Implementing common handlers

  • Create Spell Type Handler which links Spell Type to respective Spell Job
  • Create Spell Effect Handler which governs spell logic
  • Create Building Done Handler which attaches garrison unit to tower to make it shooting enemies
  • Create Single Target AI Handler which makes the attached unit attack enemies with Hammer Throw spell

Before you start

In this chapter we'll learn how to make a custom building. It's a bit unusual chapter, because before we've been only creating new spell effects or alternating vanilla spells. However, adding custom buildings is also possible with the Spellforce Framework.

For this example we're going to create a custom tower for the Dwarves race. The tower throws hammers at nearby enemies, and we're going to tweak its AI a little bit: the tower will cherry-pick a target with the lowest health and finish them first.

Also, this example is still going to be related with making custom spells, because in Spellforce tower projectiles are simulated with attacking spells in most cases. Hence, we would need to create custom spell type and a spell which deals damage to the target. The damage will have type physical, so we'd also learn how to simulate physical attacks when they're actually made with spell.

Creating custom building requires a lot of preparatory work made with GameData.cff and with other game files. We recommend to visit the second part of this chapter where all necessary steps are explained in detail.

Listing it here, in order to add a custom building to the game you need to complete the following steps:

  • You should create a new Spell Type for the Hammer Throw spell in GameData.cff

  • You should create at least one spell using the new Spell Type

  • You should reserve a number for custom building type. This number can be anything in range 214-256. In this example we'll use 214 as the ID of Dwarf Tower

  • You should download or create 3D-model for the tower

  • You should register assets within lua scripts

  • You should make JSON file representing custom building game data

  • You should create a project that can be used by a worker in order to construct dwarf tower

  • You should create (duplicate from vanilla units assigned to towers) a unit that will run dwarf tower AI in GameData.cff

Because it's a lot of tedious and soporific work, you can simply obtain necessary files in our repository. They come together with prepared game data dependency which you can simply merge with GameData.cff and get spell, project and a unit altogether. Please, make sure there are no conflicts with other mods when merging the files.

You can download prepared GameData here. Please, consult with Chapter 6.2 in case some troubles occur during merging.

Coding part

Describing the process very short, Spellforce towers work in the following way: when the tower is constructed, it's assigned with a unit which runs tower AI. The units knows a spell that simulates tower projectile. We also want our tower to have advanced AI, so within this example we'd explain how to tweak its AI using AI functions. Thus, we have to complete a few steps to get our tower fully working:

  1. Register the custom Spell Type for the Hammer Throw spell.
  2. Register the custom Building Type for the Dwarf Tower building.
  3. Write necessary logic to load essential game data for the Dwarf Tower.
  4. At the moment the Dwarf Tower is built, attach a unit to it that runs tower's AI.
  5. Write Single Target AI handler that will make tower unit behave more smart than its counterparts.

Initialization

The initialization follows virtually the same template as before. You should make the initial declarations (announce global variables for the framework structures, make macros for custom Spell Type and Spell Job) and create three main functions such as DllMain, RegisterMod and InitModule. To ease the initial steps, you could use the default spell template from the repository here.

However, here come a few caveats. When registering a mod, the mod name argument is no longer purely cosmetic. The framework will load JSON representing building game data from a folder that matches the specified mod name.

extern "C" __declspec(dllexport) SFMod *RegisterMod(SpellforceSpellFramework *framework)
{
return framework->createModInfo("Dwarf Tower", "1.0.0", "Author name", "A mod designed to demonstrate creation of custom building which is going to be Dwarf Tower controlled by an advanced AI.");
}

For example, for mod called Dwarf Tower JSON file with custom building data must be located in %Spellforce/sfsf/Dwarf Tower/ folder.

Let's register Building Functions pointer in global scope of the mod. It can be done as following:

BuildingFunctions *buildingAPI;.

Also, let's define Spell Type and Spell Job numbers for Hammer Throw custom spell also within the global scope:

#define DWARF_HAMMER_TOWER_LINE 0xf9 // 0xf9 = 249
#define DWARF_HAMMER_TOWER_JOB 0xb0 // 0xb0 = 176

The InitModule function must be extended with registration of buildingAPI. This can be done in the following way.

buildingAPI = sfsf->buildingAPI;

Let's get back to InitModule function and register custom spell which will simulate tower projectile.

SFSpell *dwarf_hammer_tower_spell = registrationAPI->registerSpell(DWARF_HAMMER_TOWER_LINE);

Then we must register the custom building. All vanilla buildings are stored within GameData.cff. But we have to go other way around, because amount of buildings that can be loaded from GameData.cff is hardcoded to number 213 and the most of slots are occupied with vanilla buildings.

It is worth noting that slots 35, 50, 54, 97, 101, 105 are free and slot 37 is taken for unused placeholder ("Elf Druid Guild" using Elf Archer Tower model). Nonetheless, it makes sense to begin learning how to add custom building with other way which allows to introduce custom buildings in excess of hardcoded limit.

Registering building is done in the way similar to how we registered custom spell types previously. The custom building type ID can be anything in range 214-256. For the purposes of this example let's go with number 222.

Let's declare a pointer to SFBuilding structure and assign registerBuilding function result to it.

SFBuilding *dwarf_tower = registrationAPI->registerBuilding(222);

Then we have to link the custom building with its game data stored within JSON in the mod folder. Let's pass the pointer to Dwarf Tower and assign it's a name of JSON. Please note, that you shouldn't specify .json extension, it's added automatically.

registrationAPI->linkBuildingJSON(dwarf_tower, "dwarf_tower_hammer");

Let's move on to the handlers. Spell Type, Spell Effect and Spell End handlers are well known, there is nothing new to tell about them. However, we've got two other handlers.

First of them is Single Target AI handler. This handler is called repeatedly for all targets in sight of a unit which possesses specific spell linked to this handler. The linking is done as following.

registrationAPI->linkSingleTargetAIHandler(dwarf_hammer_tower_spell, &dwarf_hammer_tower_ai_handler);

We pass the pointer to the custom spell and then the pass to function implementing the handler.

Then we have to make a Building Done handler. This handler is called at the moment when building construction is finished. In vanilla this handler is used to attach units to towers at the moment when towers are built. We're going to go the same way.

registrationAPI->linkBuildingDoneHandler(dwarf_tower, &dwarf_hammer_tower_done_handler);

It will be explained below what this handler should consist of.

In the end, the full code of InitModule function 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;
    aiAPI = sfsf->aiAPI;
    buildingAPI = sfsf->buildingAPI;
    logger = sfsf->logAPI;


    SFSpell *dwarf_hammer_tower_spell = registrationAPI->registerSpell(DWARF_HAMMER_TOWER_LINE);
    SFBuilding *dwarf_tower = registrationAPI->registerBuilding(214);
    registrationAPI->linkBuildingJSON(dwarf_tower, "dwarf_tower_hammer"); // we pass filename without extension, '.json' is appended automatically
    registrationAPI->linkTypeHandler(dwarf_hammer_tower_spell, &dwarf_hammer_tower_type_handler);
    registrationAPI->linkEffectHandler(dwarf_hammer_tower_spell, DWARF_HAMMER_TOWER_JOB, &dwarf_hammer_tower_effect_handler);
    registrationAPI->linkEndHandler(dwarf_hammer_tower_spell, &dwarf_hammer_tower_end_handler);
    registrationAPI->linkSingleTargetAIHandler(dwarf_hammer_tower_spell, &dwarf_hammer_tower_ai_handler);
    registrationAPI->linkBuildingDoneHandler(dwarf_tower, &dwarf_hammer_tower_done_handler);
}

Spell Type handler

The Spell Type handler is going to be the simplest ever amongst the Spell Type handlers we've created before. Because the spell is instant, we don't have to initialize any data of its. We only have to associate the instance of a spell with respective Spell Job. After that, the handler is done.

void __thiscall dwarf_hammer_tower_type_handler(SF_CGdSpell *_this, uint16_t spell_index)`
{
    _this->active_spell_list[spell_index].spell_job = DWARF_HAMMER_TOWER_JOB;
}

Common logic

Spell Effect handler

The Spell Effect handler is going to be pretty simple. In this example the spell is going to be instant, hence there is no need to bother with spell tick count unless like in previous example. We're going to just calculate damage and deliver it to a target.

First, let's declare variables to ease the work with the handler and load spell parameters from GameData.cff.

void __thiscall dwarf_hammer_tower_effect_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;
    uint16_t source_index = spell->source.entity_index;

    SF_SpellEffectInfo effect_info;
    effect_info.spell_id = spell->spell_id;
    effect_info.job_id = spell->spell_job;

    SF_CGdResourceSpell spell_data;
    spellAPI->getResourceSpellData(_this->SF_CGdResource, &spell_data, spell->spell_id);

Before applying damage to a target, let's make sure that target is alive, hostile and doesn't belong to neutral faction. Though AI handler is going to filter the most of unsuitable targets out, it's better to make additional check to be absolutely sure about how the spell works:

    if ((figureAPI->isAlive(_this->SF_CGdFigure, target_index))
        || (toolboxAPI->isTargetable(_this->SF_CGdFigureToolBox, target_index))
        || (toolboxAPI->figuresCheckHostile(_this->SF_CGdFigureToolBox, source_index, target_index))
        || ((int16_t)(_this->SF_CGdFigure->figures[target_index].owner) != -1))
    {

Let's apply visual effect to the target:

                uint32_t unused;
                SF_CGdTargetData relative_data;
                relative_data.position.X = 0;
                relative_data.position.Y = 0;
                relative_data.entity_type = 1;
                relative_data.entity_index = target_index;

                SF_Rectangle aux_data;
                aux_data.partA = 0;
                aux_data.partB = 0;

                spellAPI->addVisualEffect(_this, spell_index, kGdEffectSpellHitTarget, &unused, &relative_data, _this->OpaqueClass->current_step, 10, &aux_data);

Finally, it's time to deliver damage to the target. Let's obtain damage from spell parameters.

uint16_t damage = spell_data.params[0];

Because we're simulating physical attack (tower getting hit by throwing hammer), damage must be reduced by armor rating of a target. In order the necessary adjustment, we can use new function of toolbox API named getPhysDamageReduction. It automatically compares attacker and defender statistics, and returns the multiplier.

The last argument for getPhysDamageReduction function is action ID, which can be either action type ID (10000 and 10001 for melee attack, 10002 for ranged attack) or Spell Type ID. Some vanilla spells are hardcoded to completely ignore armor (e. g. Rock Bullet with ID 139), hence this argument is how the function recognizes the damage source.

We should pass Hammer Throw Spell Type ID as source of damage.

uint16_t reduction_percent = toolboxAPI->getPhysDamageReduction(_this->SF_CGdFigureToolBox, source_index, target_index, DWARF_HAMMER_TOWER_LINE);

The obtained value must be applied according to the following formula:

damage = ((uint32_t)(damage * reduction_percent + 5000)) / 10000;

We've got the final damage. Let's apply it to the target.

toolboxAPI->dealDamage(_this->SF_CGdFigureToolBox, source_index, target_index, damage, 0, 0, 0);

After that we stop handler, since there is nothing more to do.

                spellAPI->setEffectDone(_this, spell_index, 0);
                return;
     }
}

The full code of Spell Effect handler will look like this:

    SF_GdSpell *spell = &_this->active_spell_list[spell_index];
    uint16_t target_index = spell->target.entity_index;
    uint16_t source_index = spell->source.entity_index;


    SF_SpellEffectInfo effect_info;
    effect_info.spell_id = spell->spell_id;
    effect_info.job_id = spell->spell_job;


    SF_CGdResourceSpell spell_data;
    spellAPI->getResourceSpellData(_this->SF_CGdResource, &spell_data, spell->spell_id);

    // we've got a lot of technical conditions which can prevent spell cast, so if we don't meet at least one of them, spell fails
    if ((figureAPI->isAlive(_this->SF_CGdFigure, target_index))
        || (toolboxAPI->isTargetable(_this->SF_CGdFigureToolBox, target_index))
        || (toolboxAPI->figuresCheckHostile(_this->SF_CGdFigureToolBox, source_index, target_index))
        || ((int16_t)(_this->SF_CGdFigure->figures[target_index].owner) != -1))
     {

                uint32_t unused;

                SF_CGdTargetData relative_data;
                relative_data.position.X = 0;
                relative_data.position.Y = 0;
                relative_data.entity_type = 1;
                relative_data.entity_index = target_index;


                SF_Rectangle aux_data;
                aux_data.partA = 0;
                aux_data.partB = 0;


                spellAPI->addVisualEffect(_this, spell_index, kGdEffectSpellHitTarget, &unused, &relative_data, _this->OpaqueClass->current_step, 10, &aux_data);
                uint16_t damage = spell_data.params[0];
                uint16_t reduction_percent = toolboxAPI->getPhysDamageReduction(_this->SF_CGdFigureToolBox, source_index, target_index, DWARF_HAMMER_TOWER_LINE);
                damage = ((uint32_t)(damage * reduction_percent + 5000)) / 10000;
                toolboxAPI->dealDamage(_this->SF_CGdFigureToolBox, source_index, target_index, damage, 0, 0, 0);
                spellAPI->setEffectDone(_this, spell_index, 0);
                return;
     }
}

Spell End handler

The Spell End handler is nothing special, since the spell is instant. We remove DLL node and terminate the effect manually in case it somehow managed to not get finished as part of Spell Effect handler.

void __thiscall dwarf_hammer_tower_end_handler(SF_CGdSpell *_this, uint16_t spell_index)
{
    spellAPI->removeDLLNode(_this, spell_index);
    spellAPI->setEffectDone(_this, spell_index, 0);
}

Building Done handler

This handler is called only at the moment when construction of the building is finished. It can be used for various purposes: initializing building parameters, controlling building animations and attaching a unit that is necessary to run tower's AI to tower.

To familiarize with all functions which this handler provides, you could check for vanilla Building Done handlers here.

The handler itself is going to be surprisingly short. It gives us Building Toolbox global object and the index of a building which was just constructed. We run attachTowerUnit function from buildingAPI group and link unit ID to building index.

In this case, we should provide unit ID according to GameData.cff instead of figure index. The figure for virtual unit will be created automatically with this function. The full handler code will look like this:

void __thiscall dwarf_hammer_tower_done_handler(SF_CGdBuildingToolbox *_this,uint16_t building_index)
{
    buildingAPI->attachTowerUnit(_this, building_index, 0xbb5); // 0xbb5 in hex stands for 2997, the ID of unit which is going to simulate tower garrison
}

Single Target AI handler

We want the Dwarf Tower to aim at the enemies with the lowest health first. It means we should work with AI handlers, but even if we didn't want to make our tower that smart, we'd still need to touch AI handlers in order to make tower unit use the spell.

Every spell in order to be used by units needs to be linked with at least a single AI handler which controls in what circumstances the spell must be used. For spell controlling hammer throw, we'll need only one AI handler.

It's going to be Single Target AI handler. It's getting called when a unit encounters other units at line of sight, and decides what to do against/with every unit in the list separately. It works for all units and buildings in sight, doesn't matter hostile, friendly or neutral.

The handler is declared in the following way:

uint32_t __thiscall dwarf_hammer_tower_ai_handler(SF_CGdBattleDevelopment *_this, uint16_t target_index, uint16_t spell_line, SF_CGdResourceSpell *spell_data)
{

When the handler is called, it provides us a set of parameters. The first is Battle Development global object. It contains the index of the figure for which the handler is triggered, and lists of figure/building entities, grouped respectively as ally figures, enemy figures, ally buildings, and other buildings.

Then it's the index of the target figure at which the spell is going to be aimed, and it's the spell line (Spell Type) of a spell for which the handler was called.

When the handler is finished, it must return some integer value determining the priority of casting the spell in the given circumstances. 0 means the spell won't be applied. 1 stands for the highest priority, and the higher the number, the lesser the priority is. Then handlers for other spells available will be triggered, and the spell which got the lowest number (but not 0) will be cast.

It's also worth of noting, that things such as whether unit has enough of mana to cast or whether spell cooldown is off, are handled by game engine automatically. Gladly, you don't need to bother with those details.

Before proceeding to the neat part, let's cut off scenarios when this spell shouldn't be used at all.

    if ((_this->battleData.enemy_figures.entityCount == 0)||
        (_this->battleData.current_figure == target_index))
    {
        return 0;
    }

We don't need further calculations at two cases: when the tower sees no enemies around, hence enemy figures entity count is equal to 0. We also don't need to do any calculations, if the handler is checked for the target with the same index as the spell source (current_figure in handler terminology).

In those cases we return 0 and interrupt the handler.

If we didn't get into one of dead-ends, we can proceed to the actual AI part. To find which figure has the lowest health possible, we'd need to perform typical sorting from highest value to the lowest. Because we need to remember both figure index and its health, it will be convenient to store them within the structure.

    struct {
                uint16_t figure_index = 0; 
                uint32_t health = 0x7fff;  

           } lowest_hp_figure;

If you remember from Chapter 4: Iceblade, 0x7fff is the highest health which a unit theoretically can have. In fact, usually units have much lesser health, so this value will definitely get overwritten.

Let's begin searching through enemy figures in the list provided to us by Battle Development.

    for (uint16_t i = 0; i < _this->battleData.enemy_figures.entityCount; i++)
    {

The AI Handler returns all figures at sight. However, the spell radius can be much smaller than tower's sight. Let's also take this in consideration, when we determine the eligible target.

        uint32_t max_radius = spell_data->max_range;
        uint32_t min_radius = spell_data->min_range
        SF_Coord tower_position = _this->battleData.CGdFigure->figures[_this->battleData.current_figure].position;

Battle Data has its own set of indexes for units which got caught in sight. However, we don't know their other data. We can access their data with Figure global object, but to use that we need to obtain the unit's real figure index.

We can do it in the following manner.

uint16_t enemy_figure = _this->battleData.enemy_figures.data[i].entity_index;

We access property entity_index of array data of enemy_figures entity list of Battle Data. Sounds a bit elongated, but that's how it works. Since now enemy_figure will be what we used to call target_index in other functions.

NB: we can't name this variable target_index, because target_index is reserved for the figure at which the spell will be fired if we decide so. On the contrary, enemy_figure will be used only for the figure which health and other data we're currently considering.

The toolbox API provides function getDistance which can be used to calculate the distance between two points in the game world. Let's use it to calculate distance between tower and its supposed target.

        SF_Coord target_position = _this->battleData.CGdFigure->figures[enemy_figure].position;
        uint32_t distance = toolboxAPI->getDistance (&tower_position, &target_position);

Let's get enemy figure's health. Unfortunately, there is no way to access figure's current health directly. We have to get its base value and then deduce the current value by applying figure's current health percent to it.

uint16_t enemy_health = _this->battleData.CGdFigure->figures[enemy_figure].health.base_val * (figureAPI->getCurrentHealthPercent(_this->battleData.CGdFigure, enemy_figure)) / 100;

Let's sum up all conditions we require from the target:

  1. It must has the health lower than the the lowest health we've determined before.
  2. It must not have the same index as the source of a spell, because the tower can't attack itself.
  3. It must be within spell range.
  4. It must be still alive, because sometimes the handler catches dead figures which are still on the map.

        if ((enemy_health < lowest_hp_figure.health) && // we seek for the figure with the health lower than the health we assumed as minimal
            (lowest_hp_figure.figure_index != _this->battleData.current_figure) && // we ignore tower's own health, because it's forbidden from attacking itself
            (distance >= min_radius) && (distance <= max_radius) &&  // we ignore all figures outside of spell range
            (figureAPI->isAlive(_this->battleData.CGdFigure, enemy_figure))) // we check for the target figure to be alive
            {
                lowest_hp_figure.figure_index = enemy_figure;
                lowest_hp_figure.health = enemy_health;
            }

    }

If the new figure meets all conditions, we remember its figure index and its health, and keep going through the list.

When we considered all possible figures, let's decide whether the spell must be applied or not.

If the handler's target is the same as the figure with the lowest health in range of the spell, then we must cast the spell. We return 2 as priority.

    if (lowest_hp_figure.figure_index == target_index)
    {
        return 2;
    }

If the handler's target isn't the same as the figure with the lowest health, we deny casting with returning 0.

    else
    {
        return 0;
    }
}

The game will automatically the handler for another target at sight. When it will be the proper target, the tower will cast Hammer Throw on it.

The coding part is completed. The custom building is fully implemented, and it has got advanced AI comparing to vanilla towers. If all assets are present, the mod is ready for testing. If there were some complications with adding assets, we've prepared tutorial in which we explained what assets are necessary for new building and how to add them into the game. Feel free to read!

The full source code of this example can be downloaded here.

Clone this wiki locally