They are linked via the header script 2da_constants_h. This is a header file (meaning that it's not actually executed by the game and you don't compile it) that contains all the ability IDs in the game, and the declarations where these IDs are given representative names. This file is "imported" into spell_aoe_instant via the "#include" commands at the top of the script. spell_aoe_instant includes abi_templates, which in turns includes 2da_constants_h. By this link the game knows that when you type "ABILITY_SPELL_FIREBALL", it's supposed to read that as "10003".
If you rightclick ABILITY_SPELL_FIREBALL in spell_aoe_instant, and select "Go to definition", it'll take you to where that name is declared.
You can simply use the IDs directly, such as 10003 for Fireball, and so on. The custom names are just so we can more easily understand the source code.
So when making a custom spell, once you've made a custom spell entry in the abi table and given it a unique ID, you can use that ID directly in the ability scripts. If you want to give it a representative name, such as ABILITY_SPELL_FIREBALL_2, you have to declare this somewhere - either in 2da_constants_h (in which case you may want to save the file), or right in the file spell_aoe_constants.nss, by writing it like this
const int ABILITY_SPELL_FIREBALL_2 = 250000;
and put that line at the top of the script. spell_aoe_instant already has a similar declaration "const int MAX_AOE_TARGETS = 30;", so you can put your declaration next to it. For one simple custom spell I think it's fine to do this. But for a larger scale mod you may want to be more structured and put all custom name declarations somewhere else, such as 2da_constants_h.
Hmm... so I choose the simpler way and just placed the ID in the duplicated codes. It actually worked, as now the spell has the name and description of a Fireball in-game (it was blank before) but weirdly enough, it still doesn't do damage and has no knockback or burning effect...
The code looks like this:
case ABILITY_SPELL_FIREBALL:
{
float fDamage = (100.0f + GetCreatureSpellPower(stEvent.oCaster)) * FIREBALL_DAMAGE_FACTOR;
// damage over time
ApplyEffectDamageOverTime(oTarget, stEvent.oCaster, stEvent.nAbility, fDamage, FIREBALL_DURATION, DAMAGE_TYPE_FIRE);
// impact damage
eEffect = EffectDamage(fDamage, DAMAGE_TYPE_FIRE);
ApplyEffectOnObject(EFFECT_DURATION_TYPE_INSTANT, eEffect, oTarget, 0.0f, stEvent.oCaster, stEvent.nAbility);
// physical resistance
if (ResistanceCheck(stEvent.oCaster, oTarget, PROPERTY_ATTRIBUTE_SPELLPOWER, RESISTANCE_PHYSICAL) == FALSE)
{
// knockdown
eEffect = EffectKnockdown(oTarget, 0, stEvent.nAbility);
eEffect = SetEffectEngineInteger(eEffect, EFFECT_INTEGER_USE_INTERPOLATION_ANGLE, 2);
eEffect = SetEffectEngineVector(eEffect, EFFECT_VECTOR_ORIGIN, GetPositionFromLocation(stEvent.lTarget));
ApplyEffectOnObject(EFFECT_DURATION_TYPE_TEMPORARY, eEffect, oTarget, RandomFloat(), stEvent.oCaster, stEvent.nAbility);
}
break;
}
case 250000:
{
float fDamage = (100.0f + GetCreatureSpellPower(stEvent.oCaster)) * FIREBALL_DAMAGE_FACTOR;
// damage over time
ApplyEffectDamageOverTime(oTarget, stEvent.oCaster, stEvent.nAbility, fDamage, FIREBALL_DURATION, DAMAGE_TYPE_FIRE);
// impact damage
eEffect = EffectDamage(fDamage, DAMAGE_TYPE_FIRE);
ApplyEffectOnObject(EFFECT_DURATION_TYPE_INSTANT, eEffect, oTarget, 0.0f, stEvent.oCaster, stEvent.nAbility);
// physical resistance
if (ResistanceCheck(stEvent.oCaster, oTarget, PROPERTY_ATTRIBUTE_SPELLPOWER, RESISTANCE_PHYSICAL) == FALSE)
{
// knockdown
eEffect = EffectKnockdown(oTarget, 0, stEvent.nAbility);
eEffect = SetEffectEngineInteger(eEffect, EFFECT_INTEGER_USE_INTERPOLATION_ANGLE, 2);
eEffect = SetEffectEngineVector(eEffect, EFFECT_VECTOR_ORIGIN, GetPositionFromLocation(stEvent.lTarget));
ApplyEffectOnObject(EFFECT_DURATION_TYPE_TEMPORARY, eEffect, oTarget, RandomFloat(), stEvent.oCaster, stEvent.nAbility);
}
break;
}
// spell-specific special events
switch (stEvent.nAbility)
{
case ABILITY_SPELL_FIREBALL:
{
// ignite
SendComboEventAoE(COMBO_EVENT_IGNITE, SHAPE_SPHERE, stEvent.lTarget, stEvent.oCaster, fRadius);
break;
}
case 250000:
{
// ignite
SendComboEventAoE(COMBO_EVENT_IGNITE, SHAPE_SPHERE, stEvent.lTarget, stEvent.oCaster, fRadius);
break;
}
Am I missing something?
My guess would be that I'm messing something with exporting? I used the option Tools / Export / Export with dependent resources...