/* 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), "", 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), "", 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 (){}