Updated AMS marker feats. Removed arcane & divine marker feats. Updated Dread Necromancer for epic progression. Updated weapon baseitem models. Updated new weapons for crafting & npc equip. Updated prefix. Updated release archive.
		
			
				
	
	
		
			1255 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			1255 lines
		
	
	
		
			53 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 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 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_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;
 | |
|         //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_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);
 | |
|         //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_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_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);
 | |
|         //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);
 | |
|     
 | |
|     //Raks cast as sorcs
 | |
|     if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_RAKSHASA)
 | |
|         nLevel = GetLevelByClass(CLASS_TYPE_OUTSIDER, oPC);
 | |
|     else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_ARKAMOI) //Bozaks cast as sorcs
 | |
|         nLevel = GetLevelByClass(CLASS_TYPE_MONSTROUS, oPC);
 | |
|     else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_DRIDER) //Driders cast as sorcs
 | |
|         nLevel = GetLevelByClass(CLASS_TYPE_ABERRATION, oPC);    
 | |
|     
 | |
|     int nArcSpellslotLevel;
 | |
|     int nDivSpellslotLevel;
 | |
|     int i;
 | |
|     for(i = 1; i <= 3; 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;
 | |
|     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(persistant_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, persistant_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);
 | |
| 
 | |
|     // 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;
 | |
|         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 <= 3; 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)
 | |
|         {
 | |
|             //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;
 | |
|                 
 | |
|             //Driders cast as sorcs
 | |
|             if(nClass == CLASS_TYPE_ABERRATION
 | |
|                 && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC)
 | |
|                 && GetRacialType(oPC) == RACIAL_TYPE_DRIDER)                
 | |
|                 nClass = CLASS_TYPE_SORCERER;                
 | |
|             //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");
 | |
| } |