No, and yes. No, because you do have to edit every spell script. Yes, because if you decide to, you should put a central spells include in, to make future mass edits to spells easier.
All of our spells start with this:
#include "hg_inc"
#include "ac_spell_inc"
void main() {
struct SpellInfo si = GetSpellInfo();
if (si.id < 0)
return;
GetSpellInfo collects all pertinent spell info like caster, whether an item was used, what metamagic was used, casterlevel, and so on, for easy, uniform access to it in the spellscripts (which all had to be rewritten to use it). Here's the function itself - you can see where the bug you're concerned with is dealt with a short ways in, where metamagic's value is set to 0 if cast from an item.
[code=auto:0]
struct SpellInfo GetSpellInfo (object oCaster=OBJECT_SELF, int nSpellId=-1, int nSpellclass=-1) {
struct SpellInfo si;
if (nSpellId >= 0) {
si.id = nSpellId;
si.item = OBJECT_INVALID;
} else {
si.id = GetSpellId();
si.item = GetSpellCastItem();
}
if (GetLocalInt(oCaster, "DebugSpells")) {
if (GetIsPC(oCaster))
SendMessageToPC(oCaster, C_WHITE + SFGetSpellInfo(si.id) + C_END);
else
WriteTimestampedLogEntry("SPELLDEBUG: " + SFGetSpellInfo(si.id));
}
string sScript = GetLocalString(oCaster, "SpellScript");
if (sScript != "") {
DeleteLocalString(oCaster, "SpellScript");
if (StringToInt(sScript) == si.id && !GetIsObjectValid(si.item)) {
int nPos = FindSubString(sScript, " ");
sScript = GetStringRight(sScript, GetStringLength(sScript) - (nPos + 1));
ExecuteScript(sScript, oCaster);
si.id = -1;
return si;
}
}
if (nSpellclass >= 0)
si.class = nSpellclass;
else if (GetIsObjectValid(si.item))
si.class = class_TYPE_INVALID;
else
si.class = GetLastSpellCastclass();
si.school = SFGetSpellSchool(si.id);
si.clevel = GetCasterLevel(oCaster);
si.meta = (si.class == class_TYPE_INVALID ? 0 : GetMetaMagicFeat());
si.sp = si.clevel;
si.caster = oCaster;
si.area = GetArea(oCaster);
si.target = GetSpellTargetObject();
si.loc = GetSpellTargetLocation();
if (!X2PreSpellCastCode()) {
si.id = -1;
return si;
}
if (GetIsObjectValid(si.item)) {
int nItemType = GetBaseItemType(si.item);
if (nItemType == BASE_ITEM_POTIONS ||
nItemType == BASE_ITEM_MAGICROD ||
nItemType == BASE_ITEM_MAGICWAND ||
nItemType == BASE_ITEM_ENCHANTED_POTION ||
nItemType == BASE_ITEM_ENCHANTED_WAND) {
if (!GetItemIsUsable(si.item, oCaster)) {
si.id = -1;
return si;
}
}
if (CheckRestWearRestricted(si.item, oCaster)) {
si.id = -1;
return si;
}
CheckMarketDupe(si.item, oCaster);
int nSpellSub = GetLocalInt(si.item, "CastSubstitute_" + IntToString(si.id));
if (nSpellSub > 0) {
si.id = nSpellSub;
si.school = SFGetSpellSchool(si.id);
}
}
string sPack = GetLocalString(oCaster, "SpellPack");
if (sPack != "") {
struct IntList il = GetIntList(sPack);
if (GetLocalInt(oCaster, "DebugSpells"))
SendMessageToPC(oCaster, C_WHITE + "Spell Pack: " + sPack + C_END);
if (il.i0 == si.id && si.class == class_TYPE_INVALID && !GetIsObjectValid(si.item)) {
si.id = il.i1;
si.meta = il.i2;
si.class = il.i3;
si.school = SFGetSpellSchool(si.id);
int nPseudo = FindSubString(sPack, "TAG");
object oPseudo = GetItemPossessedBy(si.caster, GetStringRight(sPack, GetStringLength(sPack) - (nPseudo + 4)));
if (GetIsObjectValid(oPseudo)) {
int nUses, nLimit = GetLocalInt(oPseudo, "RadialLimit");
int nReqLevel = (StringToInt(GetStringRight(GetTag(oPseudo), 1)) * 2) - 1;
if (nReqLevel < 1)
nReqLevel = 1;
if (GetLevelByclass(si.class, si.caster) < nReqLevel) {
nUses = -1;
} else if (nLimit > 0) {
int nUsed = GetLocalInt(oPseudo, "RadialUsed") + 1;
SetLocalInt(oPseudo, "RadialUsed", nUsed);
nUses = nLimit - nUsed;
if (nUses >= 50)
SetItemCharges(oPseudo, 50);
else if (nUses < 1)
SetItemSpellUses(oPseudo, 0);
else
SetItemCharges(oPseudo, nUses);
} else {
AddLocalInt(oPseudo, "Radial_" + IntToString(il.i4) + "_Used", 1);
SetItemSpellUses(oPseudo, -1);
SetItemCharges(oPseudo, 1);
nUses = GetLocalInt(oPseudo, "Radial_" + IntToString(il.i4) + "_Limit") -
GetLocalInt(oPseudo, "Radial_" + IntToString(il.i4) + "_Used");
}
if (nUses < 0) {
SendMessageToPCByStrRef(oCaster, 7956);
si.id = -1;
return si;
}
} else {
SendMessageToPCByStrRef(oCaster, 7956);
si.id = -1;
return si;
}
}
DeleteLocalString(oCaster, "SpellPack");
} else {
/* XXX: make sure this validation works for pseudospells */
/* ensure personal-range spells are always centered on the caster */
if (GetObjectType(si.target) == OBJECT_TYPE_CREATURE) {
if (SFGetSpellRange(si.id) == SPELLFUNC_RANGE_PERSONAL) {
if (GetLocalInt(oCaster, "DebugSpells"))
SendMessageToPC(oCaster, C_WHITE + "Spell target overridden due to personal range" + C_END);
si.target = oCaster;
si.loc = GetLocation(oCaster);
} else if (!(SFGetSpellTarget(si.id) & SPELLFUNC_TARGET_CREATURE)) {
if (GetLocalInt(oCaster, "DebugSpells"))
SendMessageToPC(oCaster, C_WHITE + "Spell target overridden due to not allowing creature targets" + C_END);
si.target = oCaster;
si.loc = GetLocation(oCaster);
}
}
}
/* validate metamagic */
if (si.meta == METAMAGIC_ANY || si.meta < 0)
si.meta = METAMAGIC_NONE;
/* allow creatures to cast spells as a specific class by local */
if (!GetIsPC(oCaster) && si.class == class_TYPE_INVALID) {
int nCastAs = GetLocalInt(oCaster, "CastAsclass_" + IntToString(si.id));
if (nCastAs == 0)
nCastAs = GetLocalInt(oCaster, "CastAsclass_*");
if (nCastAs > 0)
si.class = nCastAs;
}
/* adjust for shifter-cast spells */
if (GetIsPC(oCaster) &&
!GetIsObjectValid(si.item) &&
GetHasEffectOfType(EFFECT_TYPE_POLYMORPH, oCaster)) {
if (GetLevelByclass(class_TYPE_SHIFTER, oCaster) > 0 &&
(si.id == SPELL_BESTOW_CURSE ||
si.id == SPELL_BURNING_HANDS ||
si.id == SPELL_DARKNESS ||
si.id == SPELL_DISPEL_MAGIC ||
si.id == SPELL_DOMINATE_MONSTER ||
si.id == SPELL_FINGER_OF_DEATH ||
si.id == SPELL_GREATER_SPELL_BREACH ||
si.id == SPELL_GREAT_THUNDERCLAP ||
si.id == SPELL_ICE_STORM ||
si.id == SPELL_ISAACS_GREATER_MISSILE_STORM ||
si.id == SPELL_ISAACS_LESSER_MISSILE_STORM ||
si.id == SPELL_MAGIC_MISSILE ||
si.id == SPELL_MESTILS_ACID_BREATH ||
si.id == SPELL_PRISMATIC_SPRAY ||
si.id == SPELL_SUNBURST ||
si.id == SPELL_VAMPIRIC_TOUCH ||
si.id == SPELL_WEB ||
si.id == SPELL_WALL_OF_FIRE)) {
int nEssence = 1;
if (si.id == SPELL_FINGER_OF_DEATH ||
si.id == SPELL_GREATER_SPELL_BREACH ||
si.id == SPELL_PRISMATIC_SPRAY ||
si.id == SPELL_WEB)
nEssence = 2;
else if (si.id == SPELL_GREAT_THUNDERCLAP ||
si.id == SPELL_MAGIC_MISSILE ||
si.id == SPELL_ISAACS_LESSER_MISSILE_STORM ||
si.id == SPELL_ISAACS_GREATER_MISSILE_STORM ||
si.id == SPELL_SUNBURST)
nEssence = 3;
else if (si.id == SPELL_BESTOW_CURSE ||
si.id == SPELL_WALL_OF_FIRE)
nEssence = 4;
else if (si.id == SPELL_DOMINATE_MONSTER)
nEssence = 40;
if (!ConsumeShifterEssence(oCaster, nEssence)) {
si.id = -1;
return si;
}
si.class = class_TYPE_SHIFTER;
si.slevel = SFGetInnateSpellLevel(si.id);
si.meta = METAMAGIC_NONE;
} else {
FloatingTextStringOnCreature("You cannot cast spells while polymorphed!", oCaster, FALSE);
si.id = -1;
return si;
}
}
/* if the spell was cast as a particular class, get the DC for it */
if (si.class != class_TYPE_INVALID) {
if ((si.slevel = SFGetInnateSpellLevelByclass(si.class, si.id)) < 0)
si.slevel = SFGetInnateSpellLevel(si.id);
if (si.class == class_TYPE_CLERIC ||
si.class == class_TYPE_DRUID ||
si.class == class_TYPE_PALADIN ||
si.class == class_TYPE_RANGER) {
if (GetLevelByclass(si.class, oCaster) < (si.slevel * 2) - 1) {
FloatingTextStringOnCreature("You do not have enough class levels to cast that spell!", oCaster, FALSE);
si.id = -1;
return si;
}
}
if (GetLevelByclass(class_TYPE_PALEMASTER, oCaster) > 10 &&
si.id == SPELL_BESTOW_CURSE)
si.school = SPELL_SCHOOL_NECROMANCY;
if (GetIsQuasiclass(QUASIclass_BLOODFIRE_MAGE, oCaster) &&
SFGetIsQuasiSpell(QUASIclass_BLOODFIRE_MAGE, si.id))
si.school = SPELL_SCHOOL_EVOCATION;
if (GetIsQuasiclass(QUASIclass_DRAGONSTORM_MAGE, oCaster) &&
SFGetIsQuasiSpell(QUASIclass_DRAGONSTORM_MAGE, si.id))
si.school = SPELL_SCHOOL_EVOCATION;
if (GetIsQuasiclass(QUASIclass_DWARVEN_WARCHANTER, oCaster) &&
si.id == SPELL_HOLD_MONSTER)
si.school = SPELL_SCHOOL_TRANSMUTATION;
if (GetIsQuasiclass(QUASIclass_HERALD_OF_STORMS, oCaster)) {
if (si.id == SPELL_HORRID_WILTING)
si.school = SPELL_SCHOOL_CONJURATION;
else if (si.id == HGSPELL_FREEZING_FOG)
si.school = SPELL_SCHOOL_CONJURATION;
else if (si.id == HGSPELL_STATIC_FIELD)
si.school = SPELL_SCHOOL_CONJURATION;
}
si.dc = GetCasterSpellSaveDC(si.id, si.class, si.slevel, si.school, oCaster);
} else {
/* Base DC is 10 + spell level + minimum ability modifier to cast */
si.slevel = SFGetInnateSpellLevel(si.id);
si.dc = 10 + si.slevel + (si.slevel / 2);
}
/* If the spell was item-cast, check for item spell modifiers */
if (GetIsObjectValid(si.item)) {
int nItemCL = GetLocalInt(si.item, "CasterLevel_" + IntToString(si.id));
if (nItemCL == 0)
nItemCL = GetLocalInt(si.item, "CasterLevel_*");
if (nItemCL > 0) {
si.clevel = nItemCL;
si.dc += nItemCL / 2;
si.meta = GetLocalInt(si.item, "Metamagic_" + IntToString(si.id));
if ((si.sp == GetLocalInt(si.item, "SpellPenetration_" + IntToString(si.id))) == 0)
si.sp = si.clevel + (si.clevel / 10);
int nItemclass = GetLocalInt(si.item, "CastAsclass_" + IntToString(si.id));
if (nItemclass == 0)
nItemclass = GetLocalInt(si.item, "CastAsclass_*");
if (nItemclass > 0)
si.class = nItemclass;
SendSpellDebug(oCaster, si);
return si;
} else if (nItemCL < 0) {
int nItemclass = GetLocalInt(si.item, "CastAsclass_" + IntToString(si.id));
if (nItemclass == 0)
nItemclass = GetLocalInt(si.item, "CastAsclass_*");
if (GetLevelIncludingLLs(nItemclass, oCaster) < 1) {
si.clevel = 1;
si.sp = 1;
SendSpellDebug(oCaster, si);
return si;
}
si.class = nItemclass;
if ((si.slevel = SFGetInnateSpellLevelByclass(si.class, si.id)) < 0)
si.slevel = SFGetInnateSpellLevel(si.id);
si.dc = GetCasterSpellSaveDC(si.id, si.class, si.slevel, si.school, oCaster);
if (si.class == class_TYPE_BARD || si.class == class_TYPE_SORCERER) {
int nItemSlots = GetLocalInt(si.item, "CastWithSlots_" + IntToString(si.id));
if (nItemSlots == 0)
nItemSlots = GetLocalInt(si.item, "CastWithSlots_*");
int nRemaining = GetRemainingSpellSlots(si.caster, si.class, si.slevel);
if (nItemSlots > nRemaining) {
FloatingTextStringOnCreature("You do not have enough spell slots remaining to use that item!", si.caster, FALSE);
si.id = -1;
return si;
}
SetRemainingSpellSlots(si.caster, si.class, si.slevel, nRemaining - nItemSlots);
}
} else {
SendSpellDebug(oCaster, si);
return si;
}
}
si.dc -= GetSpellAreaDCPenalty(si.caster, si.area);
string sOnce = GetLocalString(oCaster, "CasterLevelOnce");
if (sOnce != "") {
DeleteLocalString(oCaster, "CasterLevelOnce");
int nOnceID = StringToInt(GetStringSubString(sOnce, 0));
int nOnceLevel = StringToInt(GetStringSubString(sOnce, 1));
if (nOnceID == si.id && nOnceLevel > 0)
si.clevel = nOnceLevel;
else
si.clevel = 0;
} else
si.clevel = GetLocalInt(oCaster, "CasterLevelOnce_" + IntToString(si.id));
if (si.clevel > 0)
DeleteLocalInt(oCaster, "CasterLevelOnce_" + IntToString(si.id));
if (!GetIsPC(oCaster)) {
if (si.clevel == 0)
si.clevel = GetLocalInt(oCaster, "CasterLevel_" + IntToString(si.class) + "_" + IntToString(si.id));
if (si.clevel == 0)
si.clevel = GetLocalInt(oCaster, "CasterLevel_" + IntToString(si.id));
if (si.clevel == 0)
si.clevel = GetLocalInt(oCaster, "CasterLevel_" + IntToString(si.class) + "_*");
if (si.clevel == 0)
si.clevel = GetLocalInt(oCaster, "CasterLevel_*");
}
if (si.clevel == 0) {
if (GetIsDM(oCaster)) {
si.clevel = GetLocalInt(oCaster, "CasterLevel_*");
if (si.clevel == 0)
si.clevel = 60;
} else if (si.class == class_TYPE_INVALID)
si.clevel = GetCasterLevel(oCaster);
else if (si.class == class_TYPE_SHIFTER)
si.clevel = GetLevelIncludingLLs(class_TYPE_DRUID, oCaster) +
GetLevelIncludingLLs(class_TYPE_SHIFTER, oCaster);
else
si.clevel = GetLevelIncludingLLs(si.class, oCaster);
}
int nLevel;
/* Blackguards and Paladins must be aligned to cast spells */
if (si.class == class_TYPE_BLACKGUARD) {
if (GetAlignmentGoodEvil(si.caster) != ALIGNMENT_EVIL) {
FloatingTextStringOnCreature("You must be evil to cast Blackguard spells!", si.caster, FALSE);
si.id = -1;
return si;
}
/* Non-quasi blackguards add their paladin level to caster level for their spells. */
if (!GetQuasiclass(si.caster))
si.clevel += GetLevelByclass(class_TYPE_PALADIN, si.caster);
} else if (si.class == class_TYPE_PALADIN && GetAlignmentGoodEvil(si.caster) != ALIGNMENT_GOOD) {
FloatingTextStringOnCreature("You must be good to cast Paladin spells!", si.caster, FALSE);
si.id = -1;
return si;
} else if (si.class == class_TYPE_ASSASSIN && GetAlignmentGoodEvil(si.caster) == ALIGNMENT_GOOD) {
FloatingTextStringOnCreature("You must not be good to cast Assassin spells!", si.caster, FALSE);
si.id = -1;
return si;
}
/* Pale Masters add 7/6 their PM level to caster level for illusion/necro
* spells and PWK. They don't get the bonus for EV. They also get +1 DC
* for every 15 levels of PM. */
if ((nLevel = GetLevelIncludingLLs(class_TYPE_PALEMASTER, oCaster)) > 0) {
if (si.class == class_TYPE_SORCERER && GetLocalInt(oCaster, "ForsakenSorcerer")) {
si.clevel -= 10;
nLevel += 10;
}
if (si.id != SPELL_ETHEREAL_VISAGE &&
si.id != SPELL_SHADES_STONESKIN &&
(si.school == SPELL_SCHOOL_ILLUSION ||
si.school == SPELL_SCHOOL_NECROMANCY ||
si.id == SPELL_BESTOW_CURSE ||
si.id == SPELL_POWER_WORD_KILL))
si.clevel += (nLevel * 7) / 6;
else
si.clevel += (nLevel / 3);
}
if (si.class != class_TYPE_INVALID && GetIsPC(oCaster)) {
/* Many quasiclasses add multiple class levels for spell caster levels */
switch (GetQuasiclass(oCaster)) {
case QUASIclass_BANE_KNIGHT:
if (si.class == class_TYPE_SORCERER &&
SFGetIsQuasiSpell(QUASIclass_BANE_KNIGHT, si.id))
si.clevel += GetLevelIncludingLLs(class_TYPE_BLACKGUARD, oCaster);
break;
case QUASIclass_STAFFMASTER:
if (si.class == class_TYPE_WIZARD &&
(nLevel = GetLevelIncludingLLs(class_TYPE_WEAPON_MASTER, oCaster)) > 0) {
if (SFGetIsQuasiSpell(QUASIclass_STAFFMASTER, si.id))
si.clevel += nLevel;
else
si.clevel += (nLevel / 3);
}
break;
case QUASIclass_DIVINE_SLINGER:
if (si.class == class_TYPE_CLERIC &&
si.clevel > 16 &&
!SFGetIsQuasiSpell(QUASIclass_DIVINE_SLINGER, si.id))
si.clevel = 16 + (((si.clevel - 16) * 3) / 5);
break;
case QUASIclass_THEURGE:
if (si.class == class_TYPE_CLERIC)
si.clevel += (GetLevelIncludingLLs(class_TYPE_WIZARD, oCaster) - 1);
else if (si.class == class_TYPE_WIZARD)
si.clevel += (GetLevelIncludingLLs(class_TYPE_CLERIC, oCaster) - 1);
break;
case QUASIclass_LASH_OF_HATRED:
if (si.class == class_TYPE_RANGER &&
SFGetIsQuasiSpell(QUASIclass_LASH_OF_HATRED, si.id))
si.clevel += GetLevelIncludingLLs(class_TYPE_BLACKGUARD, oCaster);
break;
case QUASIclass_BLOODFIRE_MAGE:
if (si.class == class_TYPE_SORCERER &&
SFGetIsQuasiSpell(QUASIclass_BLOODFIRE_MAGE, si.id))
si.clevel += GetLevelIncludingLLs(class_TYPE_DRAGON_DISCIPLE, oCaster);
break;
case QUASIclass_DRAGONSTORM_MAGE:
if (si.class == class_TYPE_SORCERER &&
SFGetIsQuasiSpell(QUASIclass_DRAGONSTORM_MAGE, si.id))
si.clevel += GetLevelIncludingLLs(class_TYPE_DRAGON_DISCIPLE, oCaster);
break;
case QUASIclass_ACCURSED_PARIAH:
if (si.class == class_TYPE_SORCERER)
si.clevel += GetLevelIncludingLLs(class_TYPE_BARBARIAN, oCaster);
if (si.clevel > (nLevel = GetAbilityS