PRC8/nwn/nwnprc/trunk/include/inc_newspellbook.nss
Jaysyn904 e81e395031 Added Vow of Poverty
Added Vow of Poverty, Jaebrin, Hobgoblin Warsoul & Forsaker fixes (thanks PRC5 & @Fencas).  Added iprp_matcost.2da for new materials.  Updated PRC8 Tester module.  Cohorts updated to support 8 classes. Fixed ranged disarm w/ Fighter. Updated release archive.
2024-12-26 17:37:36 -05:00

1375 lines
58 KiB
Plaintext

/* Steps for adding a new spellbook
Prepared:
Make cls_spgn_*.2da
Make cls_spcr_*.2da
Make blank cls_spell_*.2da
Add cls_spgn_*.2da to classes.2da
Add class entry in prc_classes.2da
Add the spellbook feat (#1999) to cls_feat_*.2da at the appropriate level
Add class to PRCGetSpellSaveDC() in prc_add_spell_dc
Add class to GetSpellbookTypeForClass() below
Add class to GetAbilityScoreForClass() below
Add class to bKnowsAllClassSpells() below if necessary
Add class to GetIsArcaneClass() or GetIsDivineClass() in prc_inc_castlvl as appropriate
Add class to GetCasterLevelModifier() in prc_inc_castlvl if necessary
Add class to SetupLookupStage() in inc_lookups
Add class to GetCasterLvl() in prc_inc_spells
Add Practiced Spellcaster feat to feat.2da and to PracticedSpellcasting() in prc_inc_castlvl
Run the assemble_spellbooks.bat file
Make the prc_* scripts in newspellbook. The filenames can be found under the spell entries for the class in spells.2da.
Spont:
Make cls_spgn_*.2da
Make cls_spkn_*.2da
Make cls_spcr_*.2da
Make blank cls_spell_*.2da
Add cls_spkn_*.2da and cls_spgn_*.2da to classes.2da
Add class entry in prc_classes.2da
Add class to PRCGetSpellSaveDC() in prc_add_spell_dc
Add class to GetSpellbookTypeForClass() below
Add class to GetAbilityScoreForClass() below
Add class to bKnowsAllClassSpells() below if necessary
Add class to GetIsArcaneClass() or GetIsDivineClass() in prc_inc_castlvl as appropriate
Add class to GetCasterLevelModifier() in prc_inc_castlvl if necessary
Add class to SetupLookupStage() in inc_lookups
Add class to GetCasterLvl() in prc_inc_spells
Add Practiced Spellcaster feat to feat.2da and to PracticedSpellcasting() in prc_inc_castlvl
Add class to prc_amagsys_gain if(CheckMissingSpells(oPC, CLASS_TYPE_SORCERER, MinimumSpellLevel, MaximumSpellLevel))
Add class to ExecuteScript("prc_amagsys_gain", oPC) list in EvalPRCFeats in prc_inc_function
Run the assemble_spellbooks.bat file
Make the prc_* scripts in newspellbook
prc_classes.2da entry:
Label - name for the class
Name - tlk file strref
SpellCaster - does the class cast spells? 0 = No, 1 = Yes (used for bonus spellslot item properties)
SBType - S = spontaneous, P = prepared
AL - does the class use Advanced Learning of any type? 0 = No, 1 = Yes
*/
//////////////////////////////////////////////////
/* Function prototypes */
//////////////////////////////////////////////////
int GetSpellbookTypeForClass(int nClass);
int GetAbilityScoreForClass(int nClass, object oPC);
/**
* Determines the given character's DC-modifying ability modifier for
* the given class' spells. Handles split-score casters.
*
* @param nClass The spellcasting class for whose spells to determine ability mod to DC for
* @param oPC The character whose abilities to examine
* @return The DC-modifying ability score's ability modifier value
*/
int GetDCAbilityModForClass(int nClass, object oPC);
string GetFileForClass(int nClass);
int GetSpellslotLevel(int nClass, object oPC);
int GetItemBonusSlotCount(object oPC, int nClass, int nSpellLevel);
int GetSlotCount(int nLevel, int nSpellLevel, int nAbilityScore, int nClass, object oItemPosessor = OBJECT_INVALID);
int bKnowsAllClassSpells(int nClass);
int GetSpellKnownMaxCount(int nLevel, int nSpellLevel, int nClass, object oPC);
int GetSpellKnownCurrentCount(object oPC, int nSpellLevel, int nClass);
int GetSpellUnknownCurrentCount(object oPC, int nSpellLevel, int nClass);
void AddSpellUse(object oPC, int nSpellbookID, int nClass, string sFile, string sArrayName, int nSpellbookType, object oSkin, int nFeatID, int nIPFeatID, string sIDX = "");
void RemoveSpellUse(object oPC, int nSpellID, int nClass);
// int GetSpellUses(object oPC, int nSpellID, int nClass);
int GetSpellLevel(int nSpellID, int nClass);
void SetupSpells(object oPC, int nClass);
void CheckAndRemoveFeat(object oHide, itemproperty ipFeat);
void WipeSpellbookHideFeats(object oPC);
void CheckNewSpellbooks(object oPC);
void NewSpellbookSpell(int nClass, int nSpellbookType, int nMetamagic = METAMAGIC_NONE, int bInstantSpell = FALSE);
void CastSpontaneousSpell(int nClass, int bInstantSpell = FALSE);
void CastPreparedSpell(int nClass, int nMetamagic = METAMAGIC_NONE, int bInstantSpell = FALSE);
//////////////////////////////////////////////////
/* Constants */
//////////////////////////////////////////////////
/* stored in "prc_inc_sb_const"
Accessed via "prc_inc_core" */
//////////////////////////////////////////////////
/* Includes */
//////////////////////////////////////////////////
// ** THIS ORDER IS IMPORTANT **
//#include "prc_effect_inc" //access via prc_inc_core
//#include "inc_lookups" //access via prc_inc_core
#include "prc_inc_core"
#include "inc_sp_gain_mem" //providing child access to prc_inc_core
//Must load in this order.
//#include "prc_inc_castlvl" //access via prc_inc_core
//#include "prc_inc_descrptr" //access via prc_inc_core
//////////////////////////////////////////////////
/* Function definitions */
//////////////////////////////////////////////////
int GetSpellbookTypeForClass(int nClass)
{
switch(nClass)
{
case CLASS_TYPE_ARCHIVIST:
case CLASS_TYPE_BLACKGUARD:
case CLASS_TYPE_BLIGHTER:
case CLASS_TYPE_CLERIC:
case CLASS_TYPE_CULTIST_SHATTERED_PEAK:
case CLASS_TYPE_DRUID:
case CLASS_TYPE_HEALER:
case CLASS_TYPE_KNIGHT_CHALICE:
case CLASS_TYPE_KNIGHT_MIDDLECIRCLE:
case CLASS_TYPE_NENTYAR_HUNTER:
case CLASS_TYPE_OCULAR:
case CLASS_TYPE_PALADIN:
case CLASS_TYPE_RANGER:
case CLASS_TYPE_SHADOWLORD:
case CLASS_TYPE_SHAMAN:
case CLASS_TYPE_SLAYER_OF_DOMIEL:
case CLASS_TYPE_SOHEI:
case CLASS_TYPE_SOLDIER_OF_LIGHT:
case CLASS_TYPE_UR_PRIEST:
case CLASS_TYPE_VASSAL:
case CLASS_TYPE_VIGILANT:
case CLASS_TYPE_WIZARD:
return SPELLBOOK_TYPE_PREPARED;
case CLASS_TYPE_ASSASSIN:
case CLASS_TYPE_BARD:
case CLASS_TYPE_BEGUILER:
case CLASS_TYPE_CELEBRANT_SHARESS:
case CLASS_TYPE_DREAD_NECROMANCER:
case CLASS_TYPE_DUSKBLADE:
case CLASS_TYPE_FAVOURED_SOUL:
case CLASS_TYPE_HARPER:
case CLASS_TYPE_HEXBLADE:
case CLASS_TYPE_JUSTICEWW:
case CLASS_TYPE_KNIGHT_WEAVE:
case CLASS_TYPE_SORCERER:
case CLASS_TYPE_SUBLIME_CHORD:
case CLASS_TYPE_SUEL_ARCHANAMACH:
case CLASS_TYPE_WARMAGE:
return SPELLBOOK_TYPE_SPONTANEOUS;
// shapechanger HD count as sorcerer for aranea.
case CLASS_TYPE_SHAPECHANGER:
return SPELLBOOK_TYPE_SPONTANEOUS;
// Multiple races
case CLASS_TYPE_MONSTROUS:
return SPELLBOOK_TYPE_SPONTANEOUS;
// Gloura as Bard
case CLASS_TYPE_FEY:
return SPELLBOOK_TYPE_SPONTANEOUS;
// Drider as Sorc
case CLASS_TYPE_ABERRATION:
return SPELLBOOK_TYPE_SPONTANEOUS;
//outsider HD count as sorc for raks
case CLASS_TYPE_OUTSIDER: {
/// @todo Will eventually need to add a check here to differentiate between races. Not all are sorcerers, just most
return SPELLBOOK_TYPE_SPONTANEOUS;
}
}
return SPELLBOOK_TYPE_INVALID;
}
int GetAbilityScoreForClass(int nClass, object oPC)
{
switch(nClass)
{
case CLASS_TYPE_BLACKGUARD:
case CLASS_TYPE_BLIGHTER:
case CLASS_TYPE_CLERIC:
case CLASS_TYPE_DRUID:
case CLASS_TYPE_FIST_OF_ZUOKEN:
case CLASS_TYPE_HEALER:
case CLASS_TYPE_JUSTICEWW:
case CLASS_TYPE_KNIGHT_CHALICE:
case CLASS_TYPE_KNIGHT_MIDDLECIRCLE:
case CLASS_TYPE_NENTYAR_HUNTER:
case CLASS_TYPE_OCULAR:
case CLASS_TYPE_PALADIN:
case CLASS_TYPE_PSYWAR:
case CLASS_TYPE_RANGER:
case CLASS_TYPE_SHAMAN:
case CLASS_TYPE_SLAYER_OF_DOMIEL:
case CLASS_TYPE_SOHEI:
case CLASS_TYPE_SOLDIER_OF_LIGHT:
case CLASS_TYPE_UR_PRIEST:
case CLASS_TYPE_VASSAL:
case CLASS_TYPE_VIGILANT:
case CLASS_TYPE_WARMIND:
return GetAbilityScore(oPC, ABILITY_WISDOM);
case CLASS_TYPE_ARCHIVIST:
case CLASS_TYPE_ASSASSIN:
case CLASS_TYPE_BEGUILER:
case CLASS_TYPE_CULTIST_SHATTERED_PEAK:
case CLASS_TYPE_DUSKBLADE:
case CLASS_TYPE_PSION:
case CLASS_TYPE_PSYCHIC_ROGUE:
case CLASS_TYPE_SHADOWCASTER:
case CLASS_TYPE_SHADOWLORD:
case CLASS_TYPE_WIZARD:
return GetAbilityScore(oPC, ABILITY_INTELLIGENCE);
case CLASS_TYPE_BARD:
case CLASS_TYPE_CELEBRANT_SHARESS:
case CLASS_TYPE_DREAD_NECROMANCER:
case CLASS_TYPE_FAVOURED_SOUL:
case CLASS_TYPE_HARPER:
case CLASS_TYPE_HEXBLADE:
case CLASS_TYPE_KNIGHT_WEAVE:
case CLASS_TYPE_SORCERER:
case CLASS_TYPE_SUBLIME_CHORD:
case CLASS_TYPE_SUEL_ARCHANAMACH:
case CLASS_TYPE_WARMAGE:
case CLASS_TYPE_WILDER:
return GetAbilityScore(oPC, ABILITY_CHARISMA);
//shapeshifter HD count as sorc for aranea
case CLASS_TYPE_SHAPECHANGER:
return GetAbilityScore(oPC, ABILITY_CHARISMA);
// Multiple races
case CLASS_TYPE_MONSTROUS:
return GetAbilityScore(oPC, ABILITY_CHARISMA);
// Gloura as Bard
case CLASS_TYPE_FEY:
return GetAbilityScore(oPC, ABILITY_CHARISMA);
// Drider as Sorc
case CLASS_TYPE_ABERRATION:
return GetAbilityScore(oPC, ABILITY_CHARISMA);
//outsider HD count as sorc for raks
case CLASS_TYPE_OUTSIDER: {
/// @todo Will eventually need to add a check here to differentiate between races. Not all are sorcerers, just most
return GetAbilityScore(oPC, ABILITY_CHARISMA);
}
}
return GetAbilityScore(oPC, ABILITY_CHARISMA); //default for SLAs?
}
int GetDCAbilityModForClass(int nClass, object oPC)
{
switch(nClass)
{
case CLASS_TYPE_BLACKGUARD:
case CLASS_TYPE_BLIGHTER:
case CLASS_TYPE_CLERIC:
case CLASS_TYPE_DRUID:
case CLASS_TYPE_FAVOURED_SOUL:
case CLASS_TYPE_FIST_OF_ZUOKEN:
case CLASS_TYPE_JUSTICEWW:
case CLASS_TYPE_KNIGHT_CHALICE:
case CLASS_TYPE_KNIGHT_MIDDLECIRCLE:
case CLASS_TYPE_OCULAR:
case CLASS_TYPE_NENTYAR_HUNTER:
case CLASS_TYPE_PALADIN:
case CLASS_TYPE_PSYWAR:
case CLASS_TYPE_RANGER:
case CLASS_TYPE_SHAMAN:
case CLASS_TYPE_SLAYER_OF_DOMIEL:
case CLASS_TYPE_SOHEI:
case CLASS_TYPE_SOLDIER_OF_LIGHT:
case CLASS_TYPE_UR_PRIEST:
case CLASS_TYPE_VASSAL:
case CLASS_TYPE_VIGILANT:
case CLASS_TYPE_WARMIND:
return GetAbilityModifier(ABILITY_WISDOM, oPC);
case CLASS_TYPE_ARCHIVIST:
case CLASS_TYPE_ASSASSIN:
case CLASS_TYPE_BEGUILER:
case CLASS_TYPE_CULTIST_SHATTERED_PEAK:
case CLASS_TYPE_DUSKBLADE:
case CLASS_TYPE_PSION:
case CLASS_TYPE_PSYCHIC_ROGUE:
case CLASS_TYPE_SHADOWLORD:
case CLASS_TYPE_WIZARD:
return GetAbilityModifier(ABILITY_INTELLIGENCE, oPC);
case CLASS_TYPE_BARD:
case CLASS_TYPE_CELEBRANT_SHARESS:
case CLASS_TYPE_DREAD_NECROMANCER:
case CLASS_TYPE_HARPER:
case CLASS_TYPE_HEALER:
case CLASS_TYPE_HEXBLADE:
case CLASS_TYPE_SHADOWCASTER:
case CLASS_TYPE_SORCERER:
case CLASS_TYPE_SUBLIME_CHORD:
case CLASS_TYPE_SUEL_ARCHANAMACH:
case CLASS_TYPE_WARMAGE:
case CLASS_TYPE_WILDER:
return GetAbilityModifier(ABILITY_CHARISMA, oPC);
//shapechanger HD count as sorc for aranea
case CLASS_TYPE_SHAPECHANGER:
return GetAbilityScore(oPC, ABILITY_CHARISMA);
// Multiple races
case CLASS_TYPE_MONSTROUS:
return GetAbilityScore(oPC, ABILITY_CHARISMA);
// Gloura as Bard
case CLASS_TYPE_FEY:
return GetAbilityScore(oPC, ABILITY_CHARISMA);
// Drider as Sorc
case CLASS_TYPE_ABERRATION:
return GetAbilityScore(oPC, ABILITY_CHARISMA);
//outsider HD count as sorc for raks
case CLASS_TYPE_OUTSIDER: {
// @todo Will eventually need to add a check here to differentiate between races. Not all are sorcerers, just most
return GetAbilityModifier(ABILITY_CHARISMA, oPC);
}
}
return GetAbilityModifier(ABILITY_CHARISMA, oPC); //default for SLAs?
}
string GetFileForClass(int nClass)
{
string sFile = Get2DACache("classes", "FeatsTable", nClass);
sFile = "cls_spell" + GetStringRight(sFile, GetStringLength(sFile) - 8); // Hardcoded the cls_ part. It's not as if any class uses some other prefix - Ornedan, 20061231
//if(DEBUG) DoDebug("GetFileForClass(" + IntToString(nClass) + ") = " + sFile);
return sFile;
}
int GetSpellslotLevel(int nClass, object oPC)
{
int nLevel = GetLevelByClass(nClass, oPC);
//:: Rakshasa cast as sorcerers
if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_RAKSHASA)
nLevel = GetLevelByClass(CLASS_TYPE_OUTSIDER, oPC);
//:: Arkamoi cast as sorcerers
else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_ARKAMOI)
nLevel = GetLevelByClass(CLASS_TYPE_MONSTROUS, oPC);
//:: Driders cast as sorcerers
else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_DRIDER)
nLevel = GetLevelByClass(CLASS_TYPE_ABERRATION, oPC);
//:: Redspawn Arcaniss cast as 3/4 sorcerers
else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_REDSPAWN_ARCANISS)
nLevel = GetLevelByClass(CLASS_TYPE_MONSTROUS, oPC)*3/4;
//:: Marrutact cast as 6/7 sorcerers
else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_MARRUTACT)
nLevel = GetLevelByClass(CLASS_TYPE_MONSTROUS, oPC)*6/7;
//:: Hobgoblin Warsouls cast as sorcerers
else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_HOBGOBLIN_WARSOUL)
nLevel = GetLevelByClass(CLASS_TYPE_MONSTROUS, oPC);
//:: Gloura cast as bards
else if(nClass == CLASS_TYPE_BARD && !GetLevelByClass(CLASS_TYPE_BARD, oPC) && GetRacialType(oPC) == RACIAL_TYPE_GLOURA)
nLevel = GetLevelByClass(CLASS_TYPE_FEY, oPC);
//:: Aranea cast as sorcerers
else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_ARANEA)
nLevel = GetLevelByClass(CLASS_TYPE_SHAPECHANGER, oPC);
int nArcSpellslotLevel;
int nDivSpellslotLevel;
int i;
for(i = 1; i <= 8; i++)
{
int nTempClass = GetClassByPosition(i, oPC);
//spellcasting prc
int nArcSpellMod = StringToInt(Get2DACache("classes", "ArcSpellLvlMod", nTempClass));
int nDivSpellMod = StringToInt(Get2DACache("classes", "DivSpellLvlMod", nTempClass));
/*//special case for combat medic class
if(nTempClass == CLASS_TYPE_COMBAT_MEDIC && (nClass == CLASS_TYPE_BARD || nClass == CLASS_TYPE_WITCH))
nArcSpellMod = 1;*/
if(nArcSpellMod == 1)
nArcSpellslotLevel += GetLevelByClass(nTempClass, oPC);
else if(nArcSpellMod > 1)
nArcSpellslotLevel += (GetLevelByClass(nTempClass, oPC) + 1) / nArcSpellMod;
if(nDivSpellMod == 1)
nDivSpellslotLevel += GetLevelByClass(nTempClass, oPC);
else if(nDivSpellMod > 1)
nDivSpellslotLevel += (GetLevelByClass(nTempClass, oPC) + 1) / nDivSpellMod;
}
if(GetPrimaryArcaneClass(oPC) == nClass)
nLevel += nArcSpellslotLevel;
if(GetPrimaryDivineClass(oPC) == nClass)
nLevel += nDivSpellslotLevel;
// For this special instance, we know that this is the only prestige class
if (nClass == CLASS_TYPE_SORCERER && GetLevelByClass(CLASS_TYPE_ULTIMATE_MAGUS, oPC))
nLevel = GetLevelByClass(CLASS_TYPE_ULTIMATE_MAGUS, oPC) + GetLevelByClass(CLASS_TYPE_SORCERER, oPC);
if(DEBUG) DoDebug("GetSpellslotLevel(" + IntToString(nClass) + ", " + GetName(oPC) + ") = " + IntToString(nLevel));
return nLevel;
}
int GetItemBonusSlotCount(object oPC, int nClass, int nSpellLevel)
{
// Value maintained by CheckPRCLimitations()
return GetLocalInt(oPC, "PRC_IPRPBonSpellSlots_" + IntToString(nClass) + "_" + IntToString(nSpellLevel));
}
int GetSlotCount(int nLevel, int nSpellLevel, int nAbilityScore, int nClass, object oItemPosessor = OBJECT_INVALID)
{
// Ability score limit rule: Must have casting ability score of at least 10 + spel level to be able to cast spells of that level at all
if(nAbilityScore < nSpellLevel + 10)
return 0;
int nSlots;
string sFile;
/*// Bioware casters use their classes.2da-specified tables
if( nClass == CLASS_TYPE_WIZARD
|| nClass == CLASS_TYPE_SORCERER
|| nClass == CLASS_TYPE_BARD
|| nClass == CLASS_TYPE_CLERIC
|| nClass == CLASS_TYPE_DRUID
|| nClass == CLASS_TYPE_PALADIN
|| nClass == CLASS_TYPE_RANGER)
{*/
sFile = Get2DACache("classes", "SpellGainTable", nClass);
/*}
// New spellbook casters use the cls_spbk_* tables
else
{
sFile = Get2DACache("classes", "FeatsTable", nClass);
sFile = "cls_spbk" + GetStringRight(sFile, GetStringLength(sFile) - 8); // Hardcoded the cls_ part. It's not as if any class uses some other prefix - Ornedan, 20061231
}*/
string sSlots = Get2DACache(sFile, "SpellLevel" + IntToString(nSpellLevel), nLevel - 1);
if(sSlots == "")
{
nSlots = -1;
//if(DEBUG) DoDebug("GetSlotCount: Problem getting slot numbers for " + IntToString(nSpellLevel) + " " + IntToString(nLevel) + " " + sFile);
}
else
nSlots = StringToInt(sSlots);
if(nSlots == -1)
return 0;
// Add spell slots from items
if(GetIsObjectValid(oItemPosessor))
nSlots += GetItemBonusSlotCount(oItemPosessor, nClass, nSpellLevel);
// Add spell slots from high ability score. Level 0 spells are exempt
if(nSpellLevel == 0)
return nSlots;
else
{
int nAbilityMod = nClass == CLASS_TYPE_ARCHIVIST ? GetAbilityModifier(ABILITY_WISDOM, oItemPosessor) : (nAbilityScore - 10) / 2;
if(nAbilityMod >= nSpellLevel) // Need an ability modifier at least equal to the spell level to gain bonus slots
nSlots += ((nAbilityMod - nSpellLevel) / 4) + 1;
return nSlots;
}
}
//if the class doesn't learn all available spells on level-up add it here
int bKnowsAllClassSpells(int nClass)
{
switch(nClass)
{
//case CLASS_TYPE_WIZARD:
case CLASS_TYPE_ARCHIVIST:
case CLASS_TYPE_ASSASSIN:
case CLASS_TYPE_BARD:
case CLASS_TYPE_CELEBRANT_SHARESS:
case CLASS_TYPE_CULTIST_SHATTERED_PEAK:
case CLASS_TYPE_DUSKBLADE:
case CLASS_TYPE_FAVOURED_SOUL:
case CLASS_TYPE_HEXBLADE:
case CLASS_TYPE_JUSTICEWW:
case CLASS_TYPE_KNIGHT_WEAVE:
case CLASS_TYPE_SORCERER:
case CLASS_TYPE_SUBLIME_CHORD:
case CLASS_TYPE_SUEL_ARCHANAMACH:
return FALSE;
// Everyone else
default:
return TRUE;
}
return TRUE;
}
int GetSpellKnownMaxCount(int nLevel, int nSpellLevel, int nClass, object oPC)
{
// If the character doesn't have any spell slots available on for this level, it can't know any spells of that level either
// @todo Check rules. There might be cases where this doesn't hold
if(!GetSlotCount(nLevel, nSpellLevel, GetAbilityScoreForClass(nClass, oPC), nClass))
return 0;
int nKnown;
string sFile;
// Bioware casters use their classes.2da-specified tables
/*if( nClass == CLASS_TYPE_WIZARD
|| nClass == CLASS_TYPE_SORCERER
|| nClass == CLASS_TYPE_BARD
|| nClass == CLASS_TYPE_CLERIC
|| nClass == CLASS_TYPE_DRUID
|| nClass == CLASS_TYPE_PALADIN
|| nClass == CLASS_TYPE_RANGER)
{*/
sFile = Get2DACache("classes", "SpellKnownTable", nClass);
/*}
else
{
sFile = Get2DACache("classes", "FeatsTable", nClass);
sFile = "cls_spkn" + GetStringRight(sFile, GetStringLength(sFile) - 8); // Hardcoded the cls_ part. It's not as if any class uses some other prefix - Ornedan, 20061231
}*/
string sKnown = Get2DACache(sFile, "SpellLevel" + IntToString(nSpellLevel), nLevel - 1);
if(DEBUG) DoDebug("GetSpellKnownMaxCount(" + IntToString(nLevel) + ", " + IntToString(nSpellLevel) + ", " + IntToString(nClass) + ", " + GetName(oPC) + ") = " + sKnown);
if(sKnown == "")
{
nKnown = -1;
//if(DEBUG) DoDebug("GetSpellKnownMaxCount: Problem getting known numbers for " + IntToString(nSpellLevel) + " " + IntToString(nLevel) + " " + sFile);
}
else
nKnown = StringToInt(sKnown);
if(nKnown == -1)
return 0;
// Bard and Sorcerer only have new spellbook spells known if they have taken prestige classes that increase spellcasting
if(nClass == CLASS_TYPE_SORCERER || nClass == CLASS_TYPE_BARD)
{
if((GetLevelByClass(nClass) == nLevel) //no PrC
&& !(GetHasFeat(FEAT_DRACONIC_GRACE, oPC) || GetHasFeat(FEAT_DRACONIC_BREATH, oPC))) //no Draconic feats that apply
return 0;
}
return nKnown;
}
int GetSpellKnownCurrentCount(object oPC, int nSpellLevel, int nClass)
{
// Check short-term cache
string sClassNum = IntToString(nClass);
if(GetLocalInt(oPC, "GetSKCCCache_" + IntToString(nSpellLevel) + "_" + sClassNum))
return GetLocalInt(oPC, "GetSKCCCache_" + IntToString(nSpellLevel) + "_" + sClassNum) - 1;
// Loop over all spells known and count the number of spells of each level known
int i;
int nKnown;
int nKnown0, nKnown1, nKnown2, nKnown3, nKnown4;
int nKnown5, nKnown6, nKnown7, nKnown8, nKnown9;
string sFile = GetFileForClass(nClass);
for(i = 0; i < persistant_array_get_size(oPC, "Spellbook" + sClassNum); i++)
{
int nNewSpellbookID = persistant_array_get_int(oPC, "Spellbook" + sClassNum, i);
int nLevel = StringToInt(Get2DACache(sFile, "Level", nNewSpellbookID));
switch(nLevel)
{
case 0: nKnown0++; break; case 1: nKnown1++; break;
case 2: nKnown2++; break; case 3: nKnown3++; break;
case 4: nKnown4++; break; case 5: nKnown5++; break;
case 6: nKnown6++; break; case 7: nKnown7++; break;
case 8: nKnown8++; break; case 9: nKnown9++; break;
}
}
// Pick the level requested for returning
switch(nSpellLevel)
{
case 0: nKnown = nKnown0; break; case 1: nKnown = nKnown1; break;
case 2: nKnown = nKnown2; break; case 3: nKnown = nKnown3; break;
case 4: nKnown = nKnown4; break; case 5: nKnown = nKnown5; break;
case 6: nKnown = nKnown6; break; case 7: nKnown = nKnown7; break;
case 8: nKnown = nKnown8; break; case 9: nKnown = nKnown9; break;
}
if(DEBUG) DoDebug("GetSpellKnownCurrentCount(" + GetName(oPC) + ", " + IntToString(nSpellLevel) + ", " + sClassNum + ") = " + IntToString(nKnown));
if(DEBUG) DoDebug("GetSpellKnownCurrentCount(i " + IntToString(i) + ", nKnown0 " + IntToString(nKnown0) + ", nKnown1 " + IntToString(nKnown1) + ", nKnown2 " + IntToString(nKnown2) + ", nKnown3 " + IntToString(nKnown3) + ", nKnown4 " + IntToString(nKnown4) + ", nKnown5 " + IntToString(nKnown5) + ", nKnown6 " + IntToString(nKnown6) + ", nKnown7 " + IntToString(nKnown7) + ", nKnown8 " + IntToString(nKnown8) + ", nKnown9 " + IntToString(nKnown9));
if(DEBUG) DoDebug("GetSpellKnownCurrentCount(persistant_array_get_size "+IntToString(persistant_array_get_size(oPC, "Spellbook" + sClassNum)));
// Cache the values for 1 second
SetLocalInt(oPC, "GetSKCCCache_0_" + sClassNum, nKnown0 + 1);
SetLocalInt(oPC, "GetSKCCCache_1_" + sClassNum, nKnown1 + 1);
SetLocalInt(oPC, "GetSKCCCache_2_" + sClassNum, nKnown2 + 1);
SetLocalInt(oPC, "GetSKCCCache_3_" + sClassNum, nKnown3 + 1);
SetLocalInt(oPC, "GetSKCCCache_4_" + sClassNum, nKnown4 + 1);
SetLocalInt(oPC, "GetSKCCCache_5_" + sClassNum, nKnown5 + 1);
SetLocalInt(oPC, "GetSKCCCache_6_" + sClassNum, nKnown6 + 1);
SetLocalInt(oPC, "GetSKCCCache_7_" + sClassNum, nKnown7 + 1);
SetLocalInt(oPC, "GetSKCCCache_8_" + sClassNum, nKnown8 + 1);
SetLocalInt(oPC, "GetSKCCCache_9_" + sClassNum, nKnown9 + 1);
DelayCommand(1.0, DeleteLocalInt(oPC, "GetSKCCCache_0_" + sClassNum));
DelayCommand(1.0, DeleteLocalInt(oPC, "GetSKCCCache_1_" + sClassNum));
DelayCommand(1.0, DeleteLocalInt(oPC, "GetSKCCCache_2_" + sClassNum));
DelayCommand(1.0, DeleteLocalInt(oPC, "GetSKCCCache_3_" + sClassNum));
DelayCommand(1.0, DeleteLocalInt(oPC, "GetSKCCCache_4_" + sClassNum));
DelayCommand(1.0, DeleteLocalInt(oPC, "GetSKCCCache_5_" + sClassNum));
DelayCommand(1.0, DeleteLocalInt(oPC, "GetSKCCCache_6_" + sClassNum));
DelayCommand(1.0, DeleteLocalInt(oPC, "GetSKCCCache_7_" + sClassNum));
DelayCommand(1.0, DeleteLocalInt(oPC, "GetSKCCCache_8_" + sClassNum));
DelayCommand(1.0, DeleteLocalInt(oPC, "GetSKCCCache_9_" + sClassNum));
return nKnown;
}
int GetSpellUnknownCurrentCount(object oPC, int nSpellLevel, int nClass)
{
// Get the lookup token created by MakeSpellbookLevelLoop()
string sTag = "SpellLvl_" + IntToString(nClass) + "_Level_" + IntToString(nSpellLevel);
object oCache = GetObjectByTag(sTag);
if(!GetIsObjectValid(oCache))
{
if(DEBUG) DoDebug("GetSpellUnknownCurrentCount: " + sTag + " is not valid");
return 0;
}
// Read the total number of spells on the given level and determine how many are already known
int nTotal = array_get_size(oCache, "Lkup");
int nKnown = GetSpellKnownCurrentCount(oPC, nSpellLevel, nClass);
int nUnknown = nTotal - nKnown;
if(DEBUG) DoDebug("GetSpellUnknownCurrentCount(" + GetName(oPC) + ", " + IntToString(nSpellLevel) + ", " + IntToString(nClass) + ") = " + IntToString(nUnknown));
return nUnknown;
}
void AddSpellUse(object oPC, int nSpellbookID, int nClass, string sFile, string sArrayName, int nSpellbookType, object oSkin, int nFeatID, int nIPFeatID, string sIDX = "")
{
/*
string sFile = GetFileForClass(nClass);
string sArrayName = "NewSpellbookMem_"+IntToString(nClass);
int nSpellbookType = GetSpellbookTypeForClass(nClass);
object oSkin = GetPCSkin(oPC);
int nFeatID = StringToInt(Get2DACache(sFile, "FeatID", nSpellbookID));
//add the feat only if they dont already have it
int nIPFeatID = StringToInt(Get2DACache(sFile, "IPFeatID", nSpellbookID));
*/
object oToken = GetHideToken(oPC);
// Add the spell use feats and set a marker local that tells for CheckAndRemoveFeat() to skip removing this feat
string sIPFeatID = IntToString(nIPFeatID);
SetLocalInt(oSkin, "NewSpellbookTemp_" + sIPFeatID, TRUE);
AddSkinFeat(nFeatID, nIPFeatID, oSkin, oPC);
// Increase the current number of uses
if(nSpellbookType == SPELLBOOK_TYPE_PREPARED)
{
//sanity test
if(!persistant_array_exists(oPC, sArrayName))
{
if(DEBUG) DoDebug("ERROR: AddSpellUse: " + sArrayName + " array does not exist, creating");
persistant_array_create(oPC, sArrayName);
}
int nUses = persistant_array_get_int(oPC, sArrayName, nSpellbookID);
nUses++;
persistant_array_set_int(oPC, sArrayName, nSpellbookID, nUses);
if(DEBUG) DoDebug("AddSpellUse: " + sArrayName + "[" + IntToString(nSpellbookID) + "] = " + IntToString(array_get_int(oPC, sArrayName, nSpellbookID)));
//Create index array - to avoid duplicates mark only 1st use of nSpellbookID
if(nUses == 1)
{
if(!persistant_array_exists(oPC, sIDX))
persistant_array_create(oPC, sIDX);
persistant_array_set_int(oPC, sIDX, array_get_size(oPC, sIDX), nSpellbookID);
}
}
else if(nSpellbookType == SPELLBOOK_TYPE_SPONTANEOUS)
{
//sanity test
if(!persistant_array_exists(oPC, sArrayName))
{
if(DEBUG) DoDebug("ERROR: AddSpellUse: " + sArrayName + " array does not exist, creating");
persistant_array_create(oPC, sArrayName);
}
/*int nSpellLevel = StringToInt(Get2DACache(sFile, "Level", nSpellbookID));
int nCount = persistant_array_get_int(oPC, sArrayName, nSpellLevel);
if(nCount < 1)
{
int nLevel = GetSpellslotLevel(nClass, oPC);
int nAbility = GetAbilityScoreForClass(nClass, oPC);
nCount = GetSlotCount(nLevel, nSpellLevel, nAbility, nClass, oPC);
array_set_int(oPC, sArrayName, nSpellLevel, nCount);
}*/
if(DEBUG) DoDebug("AddSpellUse() called on spontaneous spellbook. nIPFeatID = " + sIPFeatID);
}
}
void RemoveSpellUse(object oPC, int nSpellID, int nClass)
{
string sFile = GetFileForClass(nClass);
int nSpellbookID = SpellToSpellbookID(nSpellID);
if(nSpellbookID == -1)
{
if(DEBUG) DoDebug("ERROR: RemoveSpellUse: Unable to resolve spell to spellbookID: " + IntToString(nSpellID) + " in file " + sFile);
return;
}
if(!persistant_array_exists(oPC, "NewSpellbookMem_"+IntToString(nClass)))
{
if(DEBUG) DoDebug("RemoveSpellUse: NewSpellbookMem_" + IntToString(nClass) + " does not exist, creating.");
persistant_array_create(oPC, "NewSpellbookMem_"+IntToString(nClass));
}
// Reduce the remaining uses of the given spell by 1 (except never reduce uses below 0).
// Spontaneous spellbooks reduce the number of spells of the spell's level remaining
int nSpellbookType = GetSpellbookTypeForClass(nClass);
if(nSpellbookType == SPELLBOOK_TYPE_PREPARED)
{
int nCount = persistant_array_get_int(oPC, "NewSpellbookMem_" + IntToString(nClass), nSpellbookID);
if(nCount > 0)
persistant_array_set_int(oPC, "NewSpellbookMem_" + IntToString(nClass), nSpellbookID, nCount - 1);
}
else if(nSpellbookType == SPELLBOOK_TYPE_SPONTANEOUS)
{
int nSpellLevel = StringToInt(Get2DACache(sFile, "Level", nSpellbookID));
int nCount = persistant_array_get_int(oPC, "NewSpellbookMem_" + IntToString(nClass), nSpellLevel);
if(nCount > 0)
persistant_array_set_int(oPC, "NewSpellbookMem_" + IntToString(nClass), nSpellLevel, nCount - 1);
}
}
int GetSpellLevel(int nSpellID, int nClass)
{
string sFile = GetFileForClass(nClass);
int nSpellbookID = SpellToSpellbookID(nSpellID);
if(nSpellbookID == -1)
{
if(DEBUG) DoDebug("ERROR: GetSpellLevel: Unable to resolve spell to spellbookID: "+IntToString(nSpellID)+" "+sFile);
return -1;
}
// get spell level
int nSpellLevel = -1;
string sSpellLevel = Get2DACache(sFile, "Level", nSpellbookID);
if (sSpellLevel != "")
nSpellLevel = StringToInt(sSpellLevel);
return nSpellLevel;
}
//called inside for loop in SetupSpells(), delayed to prevent TMI
void SpontaneousSpellSetupLoop(object oPC, int nClass, string sFile, object oSkin, int i)
{
int nSpellbookID = persistant_array_get_int(oPC, "Spellbook" + IntToString(nClass), i);
string sIPFeatID = Get2DACache(sFile, "IPFeatID", nSpellbookID);
int nIPFeatID = StringToInt(sIPFeatID);
int nFeatID = StringToInt(Get2DACache(sFile, "FeatID", nSpellbookID));
//int nRealSpellID = StringToInt(Get2DACache(sFile, "RealSpellID", nSpellbookID));
SetLocalInt(oSkin, "NewSpellbookTemp_" + sIPFeatID, TRUE);
AddSkinFeat(nFeatID, nIPFeatID, oSkin, oPC);
}
void SetupSpells(object oPC, int nClass)
{
string sFile = GetFileForClass(nClass);
string sClass = IntToString(nClass);
string sArrayName = "NewSpellbookMem_" + sClass;
object oSkin = GetPCSkin(oPC);
int nLevel = GetSpellslotLevel(nClass, oPC);
int nAbility = GetAbilityScoreForClass(nClass, oPC);
int nSpellbookType = GetSpellbookTypeForClass(nClass);
if(DEBUG) DoDebug("SetupSpells\n"
+ "nClass = " + IntToString(nClass) + "\n"
+ "nSpellslotLevel = " + IntToString(nLevel) + "\n"
+ "nAbility = " + IntToString(nAbility) + "\n"
+ "nSpellbookType = " + IntToString(nSpellbookType) + "\n"
+ "sFile = " + sFile + "\n"
);
// For spontaneous spellbooks, set up an array that tells how many spells of each level they can cast
// And add casting feats for each spell known to the caster's hide
if(nSpellbookType == SPELLBOOK_TYPE_SPONTANEOUS)
{
// Spell slots
int nSpellLevel, nSlots;
for(nSpellLevel = 0; nSpellLevel <= 9; nSpellLevel++)
{
nSlots = GetSlotCount(nLevel, nSpellLevel, nAbility, nClass, oPC);
persistant_array_set_int(oPC, sArrayName, nSpellLevel, nSlots);
}
int i;
for(i = 0; i < persistant_array_get_size(oPC, "Spellbook" + sClass); i++)
{ //adding feats
SpontaneousSpellSetupLoop(oPC, nClass, sFile, oSkin, i);
}
}// end if - Spontaneous spellbook
// For prepared spellbooks, add spell uses and use feats according to spells memorised list
else if(nSpellbookType == SPELLBOOK_TYPE_PREPARED && !GetIsBioSpellCastClass(nClass))
{
int nSpellLevel, nSlot, nSlots, nSpellbookID;
string sArrayName2, sIDX;
// clearing existing spells
int i;
for(i = 0; i < persistant_array_get_size(oPC, sArrayName); i++)
{
persistant_array_set_int(oPC, sArrayName, i, 0);
}
for(nSpellLevel = 0; nSpellLevel <= 9; nSpellLevel++)
{
sArrayName2 = "Spellbook" + IntToString(nSpellLevel) + "_" + sClass; // Minor optimisation: cache the array name string for multiple uses
sIDX = "SpellbookIDX" + IntToString(nSpellLevel) + "_" + sClass;
nSlots = GetSlotCount(nLevel, nSpellLevel, nAbility, nClass, oPC);
nSlot;
for(nSlot = 0; nSlot < nSlots; nSlot++)
{
//done when spells are added to it
nSpellbookID = persistant_array_get_int(oPC, sArrayName2, nSlot);
if(nSpellbookID != 0)
{
AddSpellUse(oPC, nSpellbookID, nClass, sFile, sArrayName, nSpellbookType, oSkin,
StringToInt(Get2DACache(sFile, "FeatID", nSpellbookID)),
StringToInt(Get2DACache(sFile, "IPFeatID", nSpellbookID)),
sIDX);
}
}
}
}
}
void CheckAndRemoveFeat(object oHide, itemproperty ipFeat)
{
int nSubType = GetItemPropertySubType(ipFeat);
if(!GetLocalInt(oHide, "NewSpellbookTemp_" + IntToString(nSubType)))
{
RemoveItemProperty(oHide, ipFeat);
DeleteLocalInt(oHide, "NewSpellbookTemp_" + IntToString(nSubType));
if(DEBUG) DoDebug("CheckAndRemoveFeat: DeleteLocalInt(oHide, NewSpellbookTemp_" + IntToString(nSubType) + ");");
if(DEBUG) DoDebug("CheckAndRemoveFeat: Removing item property");
}
else
{
DeleteLocalInt(oHide, "NewSpellbookTemp_" + IntToString(nSubType));
if(DEBUG) DoDebug("CheckAndRemoveFeat: DeleteLocalInt(oHide, NewSpellbookTemp_" + IntToString(nSubType) + ");");
}
}
void WipeSpellbookHideFeats(object oPC)
{
object oHide = GetPCSkin(oPC);
itemproperty ipTest = GetFirstItemProperty(oHide);
while(GetIsItemPropertyValid(ipTest))
{
int nSubType = GetItemPropertySubType(ipTest);
if(GetItemPropertyType(ipTest) == ITEM_PROPERTY_BONUS_FEAT &&
((nSubType > SPELLBOOK_IPRP_FEATS_START && nSubType < SPELLBOOK_IPRP_FEATS_END) ||
(nSubType > SPELLBOOK_IPRP_FEATS_START2 && nSubType < SPELLBOOK_IPRP_FEATS_END2))
)
{
DelayCommand(1.0f, CheckAndRemoveFeat(oHide, ipTest));
}
ipTest = GetNextItemProperty(oHide);
}
}
void CheckNewSpellbooks(object oPC)
{
WipeSpellbookHideFeats(oPC);
int i;
for(i = 1; i <= 8; i++)
{
int nClass = GetClassByPosition(i, oPC);
int nLevel = GetLevelByClass(nClass, oPC);
if(DEBUG) DoDebug("CheckNewSpellbooks\n"
+ "nClass = " + IntToString(nClass) + "\n"
+ "nLevel = " + IntToString(nLevel) + "\n"
);
//if bard/sorc newspellbook is disabled after selecting
//remove those from radial
if( (GetPRCSwitch(PRC_BARD_DISALLOW_NEWSPELLBOOK) && nClass == CLASS_TYPE_BARD)
||(GetPRCSwitch(PRC_SORC_DISALLOW_NEWSPELLBOOK) && nClass == CLASS_TYPE_SORCERER))
{
//do nothing
}
else if(nLevel)
{
//Aranea cast as sorcs
if(nClass == CLASS_TYPE_SHAPECHANGER
&& !GetLevelByClass(CLASS_TYPE_SORCERER, oPC)
&& GetRacialType(oPC) == RACIAL_TYPE_ARANEA)
nClass = CLASS_TYPE_SORCERER;
//raks cast as sorcs
if(nClass == CLASS_TYPE_OUTSIDER
&& !GetLevelByClass(CLASS_TYPE_SORCERER, oPC)
&& GetRacialType(oPC) == RACIAL_TYPE_RAKSHASA)
nClass = CLASS_TYPE_SORCERER;
//Arkamoi cast as sorcs
if(nClass == CLASS_TYPE_MONSTROUS
&& !GetLevelByClass(CLASS_TYPE_SORCERER, oPC)
&& GetRacialType(oPC) == RACIAL_TYPE_ARKAMOI)
nClass = CLASS_TYPE_SORCERER;
//Hobgoblin Warsouls cast as sorcs
if(nClass == CLASS_TYPE_MONSTROUS
&& !GetLevelByClass(CLASS_TYPE_SORCERER, oPC)
&& GetRacialType(oPC) == RACIAL_TYPE_HOBGOBLIN_WARSOUL)
nClass = CLASS_TYPE_SORCERER;
//Redspawn cast as sorcs
if(nClass == CLASS_TYPE_MONSTROUS
&& !GetLevelByClass(CLASS_TYPE_SORCERER, oPC)
&& GetRacialType(oPC) == RACIAL_TYPE_REDSPAWN_ARCANISS)
nClass = CLASS_TYPE_SORCERER;
//Marrutact cast as sorcs
if(nClass == CLASS_TYPE_MONSTROUS
&& !GetLevelByClass(CLASS_TYPE_SORCERER, oPC)
&& GetRacialType(oPC) == RACIAL_TYPE_MARRUTACT)
nClass = CLASS_TYPE_SORCERER;
//Driders cast as sorcs
if(nClass == CLASS_TYPE_ABERRATION
&& !GetLevelByClass(CLASS_TYPE_SORCERER, oPC)
&& GetRacialType(oPC) == RACIAL_TYPE_DRIDER)
nClass = CLASS_TYPE_SORCERER;
//Gloura cast as bards
if(nClass == CLASS_TYPE_FEY
&& !GetLevelByClass(CLASS_TYPE_BARD, oPC)
&& GetRacialType(oPC) == RACIAL_TYPE_GLOURA)
nClass = CLASS_TYPE_BARD;
//remove persistant locals used to track when all spells cast
string sArrayName = "NewSpellbookMem_"+IntToString(nClass);
if(persistant_array_exists(oPC, sArrayName))
{
if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_PREPARED)
{
int nSpellLevel, i, Max;
string sIDX, sSpellbookID, sClass = IntToString(nClass);
for(nSpellLevel = 0; nSpellLevel <= 9; nSpellLevel++)
{
sIDX = "SpellbookIDX" + IntToString(nSpellLevel) + "_" + sClass;
Max = persistant_array_get_size(oPC, sIDX);
for(i = 0; i < Max; i++)
{
sSpellbookID = persistant_array_get_string(oPC, sIDX, i);
if(sSpellbookID != "")
{
DeletePersistantLocalString(oPC, sArrayName+"_"+sSpellbookID);
}
}
persistant_array_delete(oPC, sIDX);
}
}
else
{
persistant_array_delete(oPC, sArrayName);
persistant_array_create(oPC, sArrayName);
}
}
//delay it so wipespellbookhidefeats has time to start to run
//but before the deletes actually happen
DelayCommand(0.1, SetupSpells(oPC, nClass));
}
}
}
//NewSpellbookSpell() helper functions
int bTargetingAllowed(int nSpellID);
void CheckPrepSlots(int nClass, int nSpellID, int nSpellbookID, int bIsAction = FALSE);
void CheckSpontSlots(int nClass, int nSpellID, int nSpellSlotLevel, int bIsAction = FALSE);
void DoCleanUp(int nMetamagic);
void CastSpontaneousSpell(int nClass, int bInstantSpell = FALSE)
{
//get the spellbook ID
int nFakeSpellID = GetSpellId();
int nSpellID = GetPowerFromSpellID(nFakeSpellID);
if(nSpellID == -1) nSpellID = 0;
//Check the target first
if(!bTargetingAllowed(nSpellID))
return;
// if OBJECT_SELF is fighting - stop fighting and cast spell
if(GetCurrentAction() == ACTION_ATTACKOBJECT)
ClearAllActions();
//if its a subradial spell, get the master
int nMasterFakeSpellID = StringToInt(Get2DACache("spells", "Master", nFakeSpellID));
if(!nMasterFakeSpellID)
nMasterFakeSpellID = nFakeSpellID;
int nSpellbookID = SpellToSpellbookID(nMasterFakeSpellID);
// Paranoia - It should not be possible to get here without having the spells available array existing
if(!persistant_array_exists(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass)))
{
if(DEBUG) DoDebug("ERROR: NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + " array does not exist");
persistant_array_create(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass));
}
int nSpellLevel = StringToInt(Get2DACache(GetFileForClass(nClass), "Level", nSpellbookID));
// Make sure the caster has uses of this spell remaining
// 2009-9-20: Add metamagic feat abilities. -N-S
int nMetamagic = GetLocalInt(OBJECT_SELF, "MetamagicFeatAdjust");
if(nMetamagic)
{
//Need to check if metamagic can be applied to a spell
int nMetaTest;
int nMetaType = HexToInt(Get2DACache("spells", "MetaMagic", nSpellID));
int nSpellSlotLevel = nSpellLevel;
switch(nMetamagic)
{
case METAMAGIC_NONE: nMetaTest = 1; break; //no need to change anything
case METAMAGIC_EMPOWER: nMetaTest = nMetaType & 1; nSpellLevel += 2; break;
case METAMAGIC_EXTEND: nMetaTest = nMetaType & 2; nSpellLevel += 1; break;
case METAMAGIC_MAXIMIZE: nMetaTest = nMetaType & 4; nSpellLevel += 3; break;
case METAMAGIC_QUICKEN: nMetaTest = nMetaType & 8; nSpellLevel += 4; break;
case METAMAGIC_SILENT: nMetaTest = nMetaType & 16; nSpellLevel += 1; break;
case METAMAGIC_STILL: nMetaTest = nMetaType & 32; nSpellLevel += 1; break;
}
if(!nMetaTest)//can't use selected metamagic with this spell
{
nMetamagic = METAMAGIC_NONE;
ActionDoCommand(SendMessageToPC(OBJECT_SELF, "You can't use "+GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", nSpellID)))+"with selected metamagic."));
nSpellLevel = nSpellSlotLevel;
}
else if(nSpellLevel > 9)//now test the spell level
{
nMetamagic = METAMAGIC_NONE;
ActionDoCommand(SendMessageToPC(OBJECT_SELF, "Modified spell level is to high! Casting spell without metamagic"));
nSpellLevel = nSpellSlotLevel;
}
else if(GetLocalInt(OBJECT_SELF, "PRC_metamagic_state") == 1)
SetLocalInt(OBJECT_SELF, "MetamagicFeatAdjust", 0);
}
CheckSpontSlots(nClass, nSpellID, nSpellLevel);
if(GetLocalInt(OBJECT_SELF, "NSB_Cast"))
ActionDoCommand(CheckSpontSlots(nClass, nSpellID, nSpellLevel, TRUE));
else
return;
// Calculate DC. 10 + spell level on the casting class's list + DC increasing ability mod
//int nDC = 10 + nSpellLevel + GetDCAbilityModForClass(nClass, OBJECT_SELF);
// This is wrong and is breaking things, and is already calculated in the function it calls anyway - Strat
//remove any old effects
//seems cheat-casting breaks hardcoded removal
//and cant remove effects because I dont know all the targets!
if(!bInstantSpell)
{
//Handle quicken metamagic and Duskblade's Quick Cast
if((nMetamagic & METAMAGIC_QUICKEN) || GetLocalInt(OBJECT_SELF, "QuickCast"))
{
//Adding Auto-Quicken III - deleted after casting has finished.
object oSkin = GetPCSkin(OBJECT_SELF);
int nCastDur = StringToInt(Get2DACache("spells", "ConjTime", nSpellID)) + StringToInt(Get2DACache("spells", "CastTime", nSpellID));
itemproperty ipAutoQuicken = ItemPropertyBonusFeat(IP_CONST_NSB_AUTO_QUICKEN);
ActionDoCommand(AddItemProperty(DURATION_TYPE_TEMPORARY, ipAutoQuicken, oSkin, nCastDur/1000.0f));
DeleteLocalInt(OBJECT_SELF, "QuickCast");
}
}
//cast the spell
//dont need to override level, the spellscript will calculate it
//class is read from "NSB_Class"
ActionCastSpell(nSpellID, 0, -1, 0, nMetamagic, CLASS_TYPE_INVALID, 0, 0, OBJECT_INVALID, bInstantSpell);
//Clean up
ActionDoCommand(DoCleanUp(nMetamagic));
}
void CastPreparedSpell(int nClass, int nMetamagic = METAMAGIC_NONE, int bInstantSpell = FALSE)
{
object oPC = OBJECT_SELF;
//get the spellbook ID
int nFakeSpellID = GetSpellId();
int nSpellID = GetPowerFromSpellID(nFakeSpellID);
if(nSpellID == -1) nSpellID = 0;
//Check the target first
if(!bTargetingAllowed(nSpellID))
return;
// if OBJECT_SELF is fighting - stop fighting and cast spell
if(GetCurrentAction() == ACTION_ATTACKOBJECT)
ClearAllActions();
//if its a subradial spell, get the master
int nMasterFakeSpellID = StringToInt(Get2DACache("spells", "Master", nFakeSpellID));
if(!nMasterFakeSpellID)
nMasterFakeSpellID = nFakeSpellID;
int nSpellbookID = SpellToSpellbookID(nMasterFakeSpellID);
// Paranoia - It should not be possible to get here without having the spells available array existing
if(!persistant_array_exists(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass)))
{
if(DEBUG) DoDebug("ERROR: NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + " array does not exist");
persistant_array_create(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass));
}
int nSpellLevel = StringToInt(Get2DACache(GetFileForClass(nClass), "Level", nSpellbookID));
// Make sure the caster has uses of this spell remaining
CheckPrepSlots(nClass, nSpellID, nSpellbookID);
if(GetLocalInt(OBJECT_SELF, "NSB_Cast"))
ActionDoCommand(CheckPrepSlots(nClass, nSpellID, nSpellbookID, TRUE));
else
return;
// Calculate DC. 10 + spell level on the casting class's list + DC increasing ability mod
//int nDC = 10 + nSpellLevel + GetDCAbilityModForClass(nClass, OBJECT_SELF);
// This is wrong and is breaking things, and is already calculated in the function it calls anyway - Strat
//remove any old effects
//seems cheat-casting breaks hardcoded removal
//and cant remove effects because I dont know all the targets!
if(!bInstantSpell)
{
//Handle quicken metamagic and Duskblade's Quick Cast
if((nMetamagic & METAMAGIC_QUICKEN) || GetLocalInt(OBJECT_SELF, "QuickCast"))
{
//Adding Auto-Quicken III - deleted after casting has finished.
object oSkin = GetPCSkin(OBJECT_SELF);
int nCastDur = StringToInt(Get2DACache("spells", "ConjTime", nSpellID)) + StringToInt(Get2DACache("spells", "CastTime", nSpellID));
itemproperty ipAutoQuicken = ItemPropertyBonusFeat(IP_CONST_NSB_AUTO_QUICKEN);
ActionDoCommand(AddItemProperty(DURATION_TYPE_TEMPORARY, ipAutoQuicken, oSkin, nCastDur/1000.0f));
DeleteLocalInt(OBJECT_SELF, "QuickCast");
}
else if(nClass == CLASS_TYPE_HEALER)
{
if(GetHasFeat(FEAT_EFFORTLESS_HEALING)
&& GetIsOfSubschool(nSpellID, SUBSCHOOL_HEALING))
{
object oSkin = GetPCSkin(OBJECT_SELF);
//all spells from healing subschool except Close Wounds have casting time of 2.5s
float fCastDur = nSpellID == SPELL_CLOSE_WOUNDS ? 1.0f : 2.5f;
itemproperty ipImpCombatCast = ItemPropertyBonusFeat(IP_CONST_NSB_IMP_COMBAT_CAST);
ActionDoCommand(AddItemProperty(DURATION_TYPE_TEMPORARY, ipImpCombatCast, oSkin, fCastDur));
}
}
}
//cast the spell
//dont need to override level, the spellscript will calculate it
//class is read from "NSB_Class"
ActionCastSpell(nSpellID, 0, -1, 0, nMetamagic, CLASS_TYPE_INVALID, 0, 0, OBJECT_INVALID, bInstantSpell);
//Clean up
ActionDoCommand(DoCleanUp(nMetamagic));
}
void NewSpellbookSpell(int nClass, int nSpellbookType, int nMetamagic = METAMAGIC_NONE, int bInstantSpell = FALSE)
{
object oPC = OBJECT_SELF;
// if oPC is fighting - stop fighting and cast spell
if(GetCurrentAction(oPC) == ACTION_ATTACKOBJECT)
ClearAllActions();
//get the spellbook ID
int nFakeSpellID = GetSpellId();
int nSpellID = GetPowerFromSpellID(nFakeSpellID);
if(nSpellID == -1) nSpellID = 0;
//Check the target first
if(!bTargetingAllowed(nSpellID))
return;
//if its a subradial spell, get the master
int nMasterFakeSpellID = StringToInt(Get2DACache("spells", "Master", nFakeSpellID));
if(!nMasterFakeSpellID)
nMasterFakeSpellID = nFakeSpellID;
int nSpellbookID = SpellToSpellbookID(nMasterFakeSpellID);
// Paranoia - It should not be possible to get here without having the spells available array existing
if(!persistant_array_exists(oPC, "NewSpellbookMem_" + IntToString(nClass)))
{
if(DEBUG) DoDebug("ERROR: NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + " array does not exist");
persistant_array_create(oPC, "NewSpellbookMem_" + IntToString(nClass));
}
string sFile = GetFileForClass(nClass);
int nSpellLevel = StringToInt(Get2DACache(sFile, "Level", nSpellbookID));
// Make sure the caster has uses of this spell remaining
// 2009-9-20: Add metamagic feat abilities. -N-S
if(nSpellbookType == SPELLBOOK_TYPE_PREPARED)
{
CheckPrepSlots(nClass, nSpellID, nSpellbookID);
if(GetLocalInt(oPC, "NSB_Cast"))
ActionDoCommand(CheckPrepSlots(nClass, nSpellID, nSpellbookID, TRUE));
else
return;
}
else if(nSpellbookType == SPELLBOOK_TYPE_SPONTANEOUS)
{
nMetamagic = GetLocalInt(oPC, "MetamagicFeatAdjust");
if(nMetamagic)
{
//Need to check if metamagic can be applied to a spell
int nMetaTest;
int nMetaType = HexToInt(Get2DACache("spells", "MetaMagic", nSpellID));
int nSpellSlotLevel = nSpellLevel;
switch(nMetamagic)
{
case METAMAGIC_NONE: nMetaTest = 1; break; //no need to change anything
case METAMAGIC_EMPOWER: nMetaTest = nMetaType & 1; nSpellLevel += 2; break;
case METAMAGIC_EXTEND: nMetaTest = nMetaType & 2; nSpellLevel += 1; break;
case METAMAGIC_MAXIMIZE: nMetaTest = nMetaType & 4; nSpellLevel += 3; break;
case METAMAGIC_QUICKEN: nMetaTest = nMetaType & 8; nSpellLevel += 4; break;
case METAMAGIC_SILENT: nMetaTest = nMetaType & 16; nSpellLevel += 1; break;
case METAMAGIC_STILL: nMetaTest = nMetaType & 32; nSpellLevel += 1; break;
}
if(!nMetaTest)//can't use selected metamagic with this spell
{
nMetamagic = METAMAGIC_NONE;
ActionDoCommand(SendMessageToPC(oPC, "You can't use "+GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", nSpellID)))+"with selected metamagic."));
nSpellLevel = nSpellSlotLevel;
}
else if(nSpellLevel > 9)//now test the spell level
{
nMetamagic = METAMAGIC_NONE;
ActionDoCommand(SendMessageToPC(oPC, "Modified spell level is to high! Casting spell without metamagic"));
nSpellLevel = nSpellSlotLevel;
}
else if(GetLocalInt(oPC, "PRC_metamagic_state") == 1)
SetLocalInt(oPC, "MetamagicFeatAdjust", 0);
}
CheckSpontSlots(nClass, nSpellID, nSpellLevel);
if(GetLocalInt(oPC, "NSB_Cast"))
ActionDoCommand(CheckSpontSlots(nClass, nSpellID, nSpellLevel, TRUE));
else
return;
}
// Calculate DC. 10 + spell level on the casting class's list + DC increasing ability mod
//int nDC = 10 + nSpellLevel + GetDCAbilityModForClass(nClass, OBJECT_SELF);
// This is wrong and is breaking things, and is already calculated in the function it calls anyway - Strat
//remove any old effects
//seems cheat-casting breaks hardcoded removal
//and cant remove effects because I dont know all the targets!
if(!bInstantSpell)
{
//Handle quicken metamagic and Duskblade's Quick Cast
if((nMetamagic & METAMAGIC_QUICKEN) || GetLocalInt(oPC, "QuickCast"))
{
//Adding Auto-Quicken III - deleted after casting has finished.
object oSkin = GetPCSkin(oPC);
int nCastDur = StringToInt(Get2DACache("spells", "ConjTime", nSpellID)) + StringToInt(Get2DACache("spells", "CastTime", nSpellID));
itemproperty ipAutoQuicken = ItemPropertyBonusFeat(IP_CONST_NSB_AUTO_QUICKEN);
ActionDoCommand(AddItemProperty(DURATION_TYPE_TEMPORARY, ipAutoQuicken, oSkin, nCastDur/1000.0f));
DeleteLocalInt(oPC, "QuickCast");
}
else if(nClass == CLASS_TYPE_HEALER)
{
if(GetHasFeat(FEAT_EFFORTLESS_HEALING)
&& GetIsOfSubschool(nSpellID, SUBSCHOOL_HEALING))
{
object oSkin = GetPCSkin(oPC);
//all spells from healing subschool except Close Wounds have casting time of 2.5s
float fCastDur = nSpellID == SPELL_CLOSE_WOUNDS ? 1.0f : 2.5f;
itemproperty ipImpCombatCast = ItemPropertyBonusFeat(IP_CONST_NSB_IMP_COMBAT_CAST);
ActionDoCommand(AddItemProperty(DURATION_TYPE_TEMPORARY, ipImpCombatCast, oSkin, fCastDur));
}
}
}
//cast the spell
//dont need to override level, the spellscript will calculate it
//class is read from "NSB_Class"
ActionCastSpell(nSpellID, 0, -1, 0, nMetamagic, CLASS_TYPE_INVALID, 0, 0, OBJECT_INVALID, bInstantSpell);
//Clean up
ActionDoCommand(DoCleanUp(nMetamagic));
}
int bTargetingAllowed(int nSpellID)
{
object oTarget = GetSpellTargetObject();
if(GetIsObjectValid(oTarget))
{
int nTargetType = ~(HexToInt(Get2DACache("spells", "TargetType", nSpellID)));
//test targetting self
if(oTarget == OBJECT_SELF)
{
if(nTargetType & 1)
{
if(DEBUG) DoDebug("bTargetingAllowed: You cannot target yourself.");
return FALSE;
}
}
//test targetting others
else if(GetObjectType(oTarget) == OBJECT_TYPE_CREATURE)
{
if(nTargetType & 2)
{
if(DEBUG) DoDebug("bTargetingAllowed: You cannot target creatures.");
return FALSE;
}
}
}
return TRUE;
}
void CheckPrepSlots(int nClass, int nSpellID, int nSpellbookID, int bIsAction = FALSE)
{
DeleteLocalInt(OBJECT_SELF, "NSB_Cast");
int nCount = persistant_array_get_int(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass), nSpellbookID);
if(DEBUG) DoDebug("NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + "[" + IntToString(nSpellbookID) + "] = " + IntToString(nCount));
if(nCount < 1)
{
string sSpellName = GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", nSpellID)));
// "You have no castings of " + sSpellName + " remaining"
string sMessage = ReplaceChars(GetStringByStrRef(16828411), "<spellname>", sSpellName);
FloatingTextStringOnCreature(sMessage, OBJECT_SELF, FALSE);
if(bIsAction)
ClearAllActions();
}
else
{
SetLocalInt(OBJECT_SELF, "NSB_Cast", 1);
if(bIsAction)
{
SetLocalInt(OBJECT_SELF, "NSB_Class", nClass);
SetLocalInt(OBJECT_SELF, "NSB_SpellbookID", nSpellbookID);
}
}
}
void CheckSpontSlots(int nClass, int nSpellID, int nSpellSlotLevel, int bIsAction = FALSE)
{
DeleteLocalInt(OBJECT_SELF, "NSB_Cast");
int nCount = persistant_array_get_int(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass), nSpellSlotLevel);
if(DEBUG) DoDebug("NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + "[" + IntToString(nSpellSlotLevel) + "] = " + IntToString(nCount));
if(nCount < 1)
{
// "You have no castings of spells of level " + IntToString(nSpellLevel) + " remaining"
string sMessage = ReplaceChars(GetStringByStrRef(16828409), "<spelllevel>", IntToString(nSpellSlotLevel));
FloatingTextStringOnCreature(sMessage, OBJECT_SELF, FALSE);
if(bIsAction)
ClearAllActions();
}
else
{
SetLocalInt(OBJECT_SELF, "NSB_Cast", 1);
if(bIsAction)
{
SetLocalInt(OBJECT_SELF, "NSB_Class", nClass);
SetLocalInt(OBJECT_SELF, "NSB_SpellLevel", nSpellSlotLevel);
}
}
}
void DoCleanUp(int nMetamagic)
{
if(nMetamagic & METAMAGIC_QUICKEN)
{
object oSkin = GetPCSkin(OBJECT_SELF);
RemoveItemProperty(oSkin, ItemPropertyBonusFeat(IP_CONST_NSB_AUTO_QUICKEN));
}
DeleteLocalInt(OBJECT_SELF, "NSB_Class");
DeleteLocalInt(OBJECT_SELF, "NSB_SpellLevel");
DeleteLocalInt(OBJECT_SELF, "NSB_SpellbookID");
}
//:: Test Void
//:: void main (){}