Updated Release Archive. Fixed Mage-killer prereqs. Removed old LETO & ConvoCC related files. Added organized spell scroll store. Fixed Gloura spellbook. Various TLK fixes. Reorganized Repo. Removed invalid user folders. Added DocGen back in.
1363 lines
57 KiB
Plaintext
1363 lines
57 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_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;
|
|
|
|
//:: 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;
|
|
|
|
//Redspawn cast as sorcs
|
|
if(nClass == CLASS_TYPE_MONSTROUS
|
|
&& !GetLevelByClass(CLASS_TYPE_SORCERER, oPC)
|
|
&& GetRacialType(oPC) == RACIAL_TYPE_REDSPAWN_ARCANISS)
|
|
nClass = CLASS_TYPE_SORCERER;
|
|
|
|
//Marrutact cast as sorcs
|
|
if(nClass == CLASS_TYPE_MONSTROUS
|
|
&& !GetLevelByClass(CLASS_TYPE_SORCERER, oPC)
|
|
&& GetRacialType(oPC) == RACIAL_TYPE_MARRUTACT)
|
|
nClass = CLASS_TYPE_SORCERER;
|
|
|
|
//Driders cast as sorcs
|
|
if(nClass == CLASS_TYPE_ABERRATION
|
|
&& !GetLevelByClass(CLASS_TYPE_SORCERER, oPC)
|
|
&& GetRacialType(oPC) == RACIAL_TYPE_DRIDER)
|
|
nClass = CLASS_TYPE_SORCERER;
|
|
|
|
//Gloura cast as bards
|
|
if(nClass == CLASS_TYPE_FEY
|
|
&& !GetLevelByClass(CLASS_TYPE_BARD, oPC)
|
|
&& GetRacialType(oPC) == RACIAL_TYPE_GLOURA)
|
|
nClass = CLASS_TYPE_BARD;
|
|
//remove persistant locals used to track when all spells cast
|
|
string sArrayName = "NewSpellbookMem_"+IntToString(nClass);
|
|
if(persistant_array_exists(oPC, sArrayName))
|
|
{
|
|
if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_PREPARED)
|
|
{
|
|
int nSpellLevel, i, Max;
|
|
string sIDX, sSpellbookID, sClass = IntToString(nClass);
|
|
for(nSpellLevel = 0; nSpellLevel <= 9; nSpellLevel++)
|
|
{
|
|
sIDX = "SpellbookIDX" + IntToString(nSpellLevel) + "_" + sClass;
|
|
Max = persistant_array_get_size(oPC, sIDX);
|
|
for(i = 0; i < Max; i++)
|
|
{
|
|
sSpellbookID = persistant_array_get_string(oPC, sIDX, i);
|
|
if(sSpellbookID != "")
|
|
{
|
|
DeletePersistantLocalString(oPC, sArrayName+"_"+sSpellbookID);
|
|
}
|
|
}
|
|
persistant_array_delete(oPC, sIDX);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
persistant_array_delete(oPC, sArrayName);
|
|
persistant_array_create(oPC, sArrayName);
|
|
}
|
|
}
|
|
//delay it so wipespellbookhidefeats has time to start to run
|
|
//but before the deletes actually happen
|
|
DelayCommand(0.1, SetupSpells(oPC, nClass));
|
|
}
|
|
}
|
|
}
|
|
|
|
//NewSpellbookSpell() helper functions
|
|
int bTargetingAllowed(int nSpellID);
|
|
void CheckPrepSlots(int nClass, int nSpellID, int nSpellbookID, int bIsAction = FALSE);
|
|
void CheckSpontSlots(int nClass, int nSpellID, int nSpellSlotLevel, int bIsAction = FALSE);
|
|
void DoCleanUp(int nMetamagic);
|
|
|
|
void CastSpontaneousSpell(int nClass, int bInstantSpell = FALSE)
|
|
{
|
|
//get the spellbook ID
|
|
int nFakeSpellID = GetSpellId();
|
|
int nSpellID = GetPowerFromSpellID(nFakeSpellID);
|
|
if(nSpellID == -1) nSpellID = 0;
|
|
|
|
//Check the target first
|
|
if(!bTargetingAllowed(nSpellID))
|
|
return;
|
|
|
|
// if OBJECT_SELF is fighting - stop fighting and cast spell
|
|
if(GetCurrentAction() == ACTION_ATTACKOBJECT)
|
|
ClearAllActions();
|
|
|
|
//if its a subradial spell, get the master
|
|
int nMasterFakeSpellID = StringToInt(Get2DACache("spells", "Master", nFakeSpellID));
|
|
if(!nMasterFakeSpellID)
|
|
nMasterFakeSpellID = nFakeSpellID;
|
|
|
|
int nSpellbookID = SpellToSpellbookID(nMasterFakeSpellID);
|
|
|
|
// Paranoia - It should not be possible to get here without having the spells available array existing
|
|
if(!persistant_array_exists(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass)))
|
|
{
|
|
if(DEBUG) DoDebug("ERROR: NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + " array does not exist");
|
|
persistant_array_create(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass));
|
|
}
|
|
|
|
int nSpellLevel = StringToInt(Get2DACache(GetFileForClass(nClass), "Level", nSpellbookID));
|
|
|
|
// Make sure the caster has uses of this spell remaining
|
|
// 2009-9-20: Add metamagic feat abilities. -N-S
|
|
int nMetamagic = GetLocalInt(OBJECT_SELF, "MetamagicFeatAdjust");
|
|
if(nMetamagic)
|
|
{
|
|
//Need to check if metamagic can be applied to a spell
|
|
int nMetaTest;
|
|
int nMetaType = HexToInt(Get2DACache("spells", "MetaMagic", nSpellID));
|
|
|
|
int nSpellSlotLevel = nSpellLevel;
|
|
switch(nMetamagic)
|
|
{
|
|
case METAMAGIC_NONE: nMetaTest = 1; break; //no need to change anything
|
|
case METAMAGIC_EMPOWER: nMetaTest = nMetaType & 1; nSpellLevel += 2; break;
|
|
case METAMAGIC_EXTEND: nMetaTest = nMetaType & 2; nSpellLevel += 1; break;
|
|
case METAMAGIC_MAXIMIZE: nMetaTest = nMetaType & 4; nSpellLevel += 3; break;
|
|
case METAMAGIC_QUICKEN: nMetaTest = nMetaType & 8; nSpellLevel += 4; break;
|
|
case METAMAGIC_SILENT: nMetaTest = nMetaType & 16; nSpellLevel += 1; break;
|
|
case METAMAGIC_STILL: nMetaTest = nMetaType & 32; nSpellLevel += 1; break;
|
|
}
|
|
|
|
if(!nMetaTest)//can't use selected metamagic with this spell
|
|
{
|
|
nMetamagic = METAMAGIC_NONE;
|
|
ActionDoCommand(SendMessageToPC(OBJECT_SELF, "You can't use "+GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", nSpellID)))+"with selected metamagic."));
|
|
nSpellLevel = nSpellSlotLevel;
|
|
}
|
|
else if(nSpellLevel > 9)//now test the spell level
|
|
{
|
|
nMetamagic = METAMAGIC_NONE;
|
|
ActionDoCommand(SendMessageToPC(OBJECT_SELF, "Modified spell level is to high! Casting spell without metamagic"));
|
|
nSpellLevel = nSpellSlotLevel;
|
|
}
|
|
else if(GetLocalInt(OBJECT_SELF, "PRC_metamagic_state") == 1)
|
|
SetLocalInt(OBJECT_SELF, "MetamagicFeatAdjust", 0);
|
|
}
|
|
|
|
CheckSpontSlots(nClass, nSpellID, nSpellLevel);
|
|
if(GetLocalInt(OBJECT_SELF, "NSB_Cast"))
|
|
ActionDoCommand(CheckSpontSlots(nClass, nSpellID, nSpellLevel, TRUE));
|
|
else
|
|
return;
|
|
|
|
// Calculate DC. 10 + spell level on the casting class's list + DC increasing ability mod
|
|
//int nDC = 10 + nSpellLevel + GetDCAbilityModForClass(nClass, OBJECT_SELF);
|
|
// This is wrong and is breaking things, and is already calculated in the function it calls anyway - Strat
|
|
|
|
//remove any old effects
|
|
//seems cheat-casting breaks hardcoded removal
|
|
//and cant remove effects because I dont know all the targets!
|
|
if(!bInstantSpell)
|
|
{
|
|
//Handle quicken metamagic and Duskblade's Quick Cast
|
|
if((nMetamagic & METAMAGIC_QUICKEN) || GetLocalInt(OBJECT_SELF, "QuickCast"))
|
|
{
|
|
//Adding Auto-Quicken III - deleted after casting has finished.
|
|
object oSkin = GetPCSkin(OBJECT_SELF);
|
|
int nCastDur = StringToInt(Get2DACache("spells", "ConjTime", nSpellID)) + StringToInt(Get2DACache("spells", "CastTime", nSpellID));
|
|
itemproperty ipAutoQuicken = ItemPropertyBonusFeat(IP_CONST_NSB_AUTO_QUICKEN);
|
|
ActionDoCommand(AddItemProperty(DURATION_TYPE_TEMPORARY, ipAutoQuicken, oSkin, nCastDur/1000.0f));
|
|
DeleteLocalInt(OBJECT_SELF, "QuickCast");
|
|
}
|
|
}
|
|
|
|
//cast the spell
|
|
//dont need to override level, the spellscript will calculate it
|
|
//class is read from "NSB_Class"
|
|
ActionCastSpell(nSpellID, 0, -1, 0, nMetamagic, CLASS_TYPE_INVALID, 0, 0, OBJECT_INVALID, bInstantSpell);
|
|
|
|
//Clean up
|
|
ActionDoCommand(DoCleanUp(nMetamagic));
|
|
}
|
|
|
|
void CastPreparedSpell(int nClass, int nMetamagic = METAMAGIC_NONE, int bInstantSpell = FALSE)
|
|
{
|
|
object oPC = OBJECT_SELF;
|
|
|
|
//get the spellbook ID
|
|
int nFakeSpellID = GetSpellId();
|
|
int nSpellID = GetPowerFromSpellID(nFakeSpellID);
|
|
if(nSpellID == -1) nSpellID = 0;
|
|
|
|
//Check the target first
|
|
if(!bTargetingAllowed(nSpellID))
|
|
return;
|
|
|
|
// if OBJECT_SELF is fighting - stop fighting and cast spell
|
|
if(GetCurrentAction() == ACTION_ATTACKOBJECT)
|
|
ClearAllActions();
|
|
|
|
//if its a subradial spell, get the master
|
|
int nMasterFakeSpellID = StringToInt(Get2DACache("spells", "Master", nFakeSpellID));
|
|
if(!nMasterFakeSpellID)
|
|
nMasterFakeSpellID = nFakeSpellID;
|
|
|
|
int nSpellbookID = SpellToSpellbookID(nMasterFakeSpellID);
|
|
|
|
// Paranoia - It should not be possible to get here without having the spells available array existing
|
|
if(!persistant_array_exists(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass)))
|
|
{
|
|
if(DEBUG) DoDebug("ERROR: NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + " array does not exist");
|
|
persistant_array_create(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass));
|
|
}
|
|
|
|
int nSpellLevel = StringToInt(Get2DACache(GetFileForClass(nClass), "Level", nSpellbookID));
|
|
|
|
// Make sure the caster has uses of this spell remaining
|
|
CheckPrepSlots(nClass, nSpellID, nSpellbookID);
|
|
if(GetLocalInt(OBJECT_SELF, "NSB_Cast"))
|
|
ActionDoCommand(CheckPrepSlots(nClass, nSpellID, nSpellbookID, TRUE));
|
|
else
|
|
return;
|
|
|
|
// Calculate DC. 10 + spell level on the casting class's list + DC increasing ability mod
|
|
//int nDC = 10 + nSpellLevel + GetDCAbilityModForClass(nClass, OBJECT_SELF);
|
|
// This is wrong and is breaking things, and is already calculated in the function it calls anyway - Strat
|
|
|
|
//remove any old effects
|
|
//seems cheat-casting breaks hardcoded removal
|
|
//and cant remove effects because I dont know all the targets!
|
|
if(!bInstantSpell)
|
|
{
|
|
//Handle quicken metamagic and Duskblade's Quick Cast
|
|
if((nMetamagic & METAMAGIC_QUICKEN) || GetLocalInt(OBJECT_SELF, "QuickCast"))
|
|
{
|
|
//Adding Auto-Quicken III - deleted after casting has finished.
|
|
object oSkin = GetPCSkin(OBJECT_SELF);
|
|
int nCastDur = StringToInt(Get2DACache("spells", "ConjTime", nSpellID)) + StringToInt(Get2DACache("spells", "CastTime", nSpellID));
|
|
itemproperty ipAutoQuicken = ItemPropertyBonusFeat(IP_CONST_NSB_AUTO_QUICKEN);
|
|
ActionDoCommand(AddItemProperty(DURATION_TYPE_TEMPORARY, ipAutoQuicken, oSkin, nCastDur/1000.0f));
|
|
DeleteLocalInt(OBJECT_SELF, "QuickCast");
|
|
}
|
|
else if(nClass == CLASS_TYPE_HEALER)
|
|
{
|
|
if(GetHasFeat(FEAT_EFFORTLESS_HEALING)
|
|
&& GetIsOfSubschool(nSpellID, SUBSCHOOL_HEALING))
|
|
{
|
|
object oSkin = GetPCSkin(OBJECT_SELF);
|
|
//all spells from healing subschool except Close Wounds have casting time of 2.5s
|
|
float fCastDur = nSpellID == SPELL_CLOSE_WOUNDS ? 1.0f : 2.5f;
|
|
itemproperty ipImpCombatCast = ItemPropertyBonusFeat(IP_CONST_NSB_IMP_COMBAT_CAST);
|
|
ActionDoCommand(AddItemProperty(DURATION_TYPE_TEMPORARY, ipImpCombatCast, oSkin, fCastDur));
|
|
}
|
|
}
|
|
}
|
|
|
|
//cast the spell
|
|
//dont need to override level, the spellscript will calculate it
|
|
//class is read from "NSB_Class"
|
|
ActionCastSpell(nSpellID, 0, -1, 0, nMetamagic, CLASS_TYPE_INVALID, 0, 0, OBJECT_INVALID, bInstantSpell);
|
|
|
|
//Clean up
|
|
ActionDoCommand(DoCleanUp(nMetamagic));
|
|
}
|
|
|
|
void NewSpellbookSpell(int nClass, int nSpellbookType, int nMetamagic = METAMAGIC_NONE, int bInstantSpell = FALSE)
|
|
{
|
|
object oPC = OBJECT_SELF;
|
|
|
|
// if oPC is fighting - stop fighting and cast spell
|
|
if(GetCurrentAction(oPC) == ACTION_ATTACKOBJECT)
|
|
ClearAllActions();
|
|
|
|
//get the spellbook ID
|
|
int nFakeSpellID = GetSpellId();
|
|
int nSpellID = GetPowerFromSpellID(nFakeSpellID);
|
|
if(nSpellID == -1) nSpellID = 0;
|
|
|
|
//Check the target first
|
|
if(!bTargetingAllowed(nSpellID))
|
|
return;
|
|
|
|
//if its a subradial spell, get the master
|
|
int nMasterFakeSpellID = StringToInt(Get2DACache("spells", "Master", nFakeSpellID));
|
|
if(!nMasterFakeSpellID)
|
|
nMasterFakeSpellID = nFakeSpellID;
|
|
|
|
int nSpellbookID = SpellToSpellbookID(nMasterFakeSpellID);
|
|
|
|
// Paranoia - It should not be possible to get here without having the spells available array existing
|
|
if(!persistant_array_exists(oPC, "NewSpellbookMem_" + IntToString(nClass)))
|
|
{
|
|
if(DEBUG) DoDebug("ERROR: NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + " array does not exist");
|
|
persistant_array_create(oPC, "NewSpellbookMem_" + IntToString(nClass));
|
|
}
|
|
|
|
string sFile = GetFileForClass(nClass);
|
|
int nSpellLevel = StringToInt(Get2DACache(sFile, "Level", nSpellbookID));
|
|
|
|
// Make sure the caster has uses of this spell remaining
|
|
// 2009-9-20: Add metamagic feat abilities. -N-S
|
|
if(nSpellbookType == SPELLBOOK_TYPE_PREPARED)
|
|
{
|
|
CheckPrepSlots(nClass, nSpellID, nSpellbookID);
|
|
if(GetLocalInt(oPC, "NSB_Cast"))
|
|
ActionDoCommand(CheckPrepSlots(nClass, nSpellID, nSpellbookID, TRUE));
|
|
else
|
|
return;
|
|
}
|
|
else if(nSpellbookType == SPELLBOOK_TYPE_SPONTANEOUS)
|
|
{
|
|
nMetamagic = GetLocalInt(oPC, "MetamagicFeatAdjust");
|
|
if(nMetamagic)
|
|
{
|
|
//Need to check if metamagic can be applied to a spell
|
|
int nMetaTest;
|
|
int nMetaType = HexToInt(Get2DACache("spells", "MetaMagic", nSpellID));
|
|
|
|
int nSpellSlotLevel = nSpellLevel;
|
|
switch(nMetamagic)
|
|
{
|
|
case METAMAGIC_NONE: nMetaTest = 1; break; //no need to change anything
|
|
case METAMAGIC_EMPOWER: nMetaTest = nMetaType & 1; nSpellLevel += 2; break;
|
|
case METAMAGIC_EXTEND: nMetaTest = nMetaType & 2; nSpellLevel += 1; break;
|
|
case METAMAGIC_MAXIMIZE: nMetaTest = nMetaType & 4; nSpellLevel += 3; break;
|
|
case METAMAGIC_QUICKEN: nMetaTest = nMetaType & 8; nSpellLevel += 4; break;
|
|
case METAMAGIC_SILENT: nMetaTest = nMetaType & 16; nSpellLevel += 1; break;
|
|
case METAMAGIC_STILL: nMetaTest = nMetaType & 32; nSpellLevel += 1; break;
|
|
}
|
|
|
|
if(!nMetaTest)//can't use selected metamagic with this spell
|
|
{
|
|
nMetamagic = METAMAGIC_NONE;
|
|
ActionDoCommand(SendMessageToPC(oPC, "You can't use "+GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", nSpellID)))+"with selected metamagic."));
|
|
nSpellLevel = nSpellSlotLevel;
|
|
}
|
|
else if(nSpellLevel > 9)//now test the spell level
|
|
{
|
|
nMetamagic = METAMAGIC_NONE;
|
|
ActionDoCommand(SendMessageToPC(oPC, "Modified spell level is to high! Casting spell without metamagic"));
|
|
nSpellLevel = nSpellSlotLevel;
|
|
}
|
|
else if(GetLocalInt(oPC, "PRC_metamagic_state") == 1)
|
|
SetLocalInt(oPC, "MetamagicFeatAdjust", 0);
|
|
}
|
|
|
|
CheckSpontSlots(nClass, nSpellID, nSpellLevel);
|
|
if(GetLocalInt(oPC, "NSB_Cast"))
|
|
ActionDoCommand(CheckSpontSlots(nClass, nSpellID, nSpellLevel, TRUE));
|
|
else
|
|
return;
|
|
}
|
|
|
|
// Calculate DC. 10 + spell level on the casting class's list + DC increasing ability mod
|
|
//int nDC = 10 + nSpellLevel + GetDCAbilityModForClass(nClass, OBJECT_SELF);
|
|
// This is wrong and is breaking things, and is already calculated in the function it calls anyway - Strat
|
|
|
|
//remove any old effects
|
|
//seems cheat-casting breaks hardcoded removal
|
|
//and cant remove effects because I dont know all the targets!
|
|
if(!bInstantSpell)
|
|
{
|
|
//Handle quicken metamagic and Duskblade's Quick Cast
|
|
if((nMetamagic & METAMAGIC_QUICKEN) || GetLocalInt(oPC, "QuickCast"))
|
|
{
|
|
//Adding Auto-Quicken III - deleted after casting has finished.
|
|
object oSkin = GetPCSkin(oPC);
|
|
int nCastDur = StringToInt(Get2DACache("spells", "ConjTime", nSpellID)) + StringToInt(Get2DACache("spells", "CastTime", nSpellID));
|
|
itemproperty ipAutoQuicken = ItemPropertyBonusFeat(IP_CONST_NSB_AUTO_QUICKEN);
|
|
ActionDoCommand(AddItemProperty(DURATION_TYPE_TEMPORARY, ipAutoQuicken, oSkin, nCastDur/1000.0f));
|
|
DeleteLocalInt(oPC, "QuickCast");
|
|
}
|
|
else if(nClass == CLASS_TYPE_HEALER)
|
|
{
|
|
if(GetHasFeat(FEAT_EFFORTLESS_HEALING)
|
|
&& GetIsOfSubschool(nSpellID, SUBSCHOOL_HEALING))
|
|
{
|
|
object oSkin = GetPCSkin(oPC);
|
|
//all spells from healing subschool except Close Wounds have casting time of 2.5s
|
|
float fCastDur = nSpellID == SPELL_CLOSE_WOUNDS ? 1.0f : 2.5f;
|
|
itemproperty ipImpCombatCast = ItemPropertyBonusFeat(IP_CONST_NSB_IMP_COMBAT_CAST);
|
|
ActionDoCommand(AddItemProperty(DURATION_TYPE_TEMPORARY, ipImpCombatCast, oSkin, fCastDur));
|
|
}
|
|
}
|
|
}
|
|
|
|
//cast the spell
|
|
//dont need to override level, the spellscript will calculate it
|
|
//class is read from "NSB_Class"
|
|
ActionCastSpell(nSpellID, 0, -1, 0, nMetamagic, CLASS_TYPE_INVALID, 0, 0, OBJECT_INVALID, bInstantSpell);
|
|
|
|
//Clean up
|
|
ActionDoCommand(DoCleanUp(nMetamagic));
|
|
}
|
|
|
|
int bTargetingAllowed(int nSpellID)
|
|
{
|
|
object oTarget = GetSpellTargetObject();
|
|
if(GetIsObjectValid(oTarget))
|
|
{
|
|
int nTargetType = ~(HexToInt(Get2DACache("spells", "TargetType", nSpellID)));
|
|
|
|
//test targetting self
|
|
if(oTarget == OBJECT_SELF)
|
|
{
|
|
if(nTargetType & 1)
|
|
{
|
|
if(DEBUG) DoDebug("bTargetingAllowed: You cannot target yourself.");
|
|
return FALSE;
|
|
}
|
|
}
|
|
//test targetting others
|
|
else if(GetObjectType(oTarget) == OBJECT_TYPE_CREATURE)
|
|
{
|
|
if(nTargetType & 2)
|
|
{
|
|
if(DEBUG) DoDebug("bTargetingAllowed: You cannot target creatures.");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void CheckPrepSlots(int nClass, int nSpellID, int nSpellbookID, int bIsAction = FALSE)
|
|
{
|
|
DeleteLocalInt(OBJECT_SELF, "NSB_Cast");
|
|
int nCount = persistant_array_get_int(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass), nSpellbookID);
|
|
if(DEBUG) DoDebug("NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + "[" + IntToString(nSpellbookID) + "] = " + IntToString(nCount));
|
|
if(nCount < 1)
|
|
{
|
|
string sSpellName = GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", nSpellID)));
|
|
// "You have no castings of " + sSpellName + " remaining"
|
|
string sMessage = ReplaceChars(GetStringByStrRef(16828411), "<spellname>", sSpellName);
|
|
|
|
FloatingTextStringOnCreature(sMessage, OBJECT_SELF, FALSE);
|
|
if(bIsAction)
|
|
ClearAllActions();
|
|
}
|
|
else
|
|
{
|
|
SetLocalInt(OBJECT_SELF, "NSB_Cast", 1);
|
|
if(bIsAction)
|
|
{
|
|
SetLocalInt(OBJECT_SELF, "NSB_Class", nClass);
|
|
SetLocalInt(OBJECT_SELF, "NSB_SpellbookID", nSpellbookID);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckSpontSlots(int nClass, int nSpellID, int nSpellSlotLevel, int bIsAction = FALSE)
|
|
{
|
|
DeleteLocalInt(OBJECT_SELF, "NSB_Cast");
|
|
int nCount = persistant_array_get_int(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass), nSpellSlotLevel);
|
|
if(DEBUG) DoDebug("NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + "[" + IntToString(nSpellSlotLevel) + "] = " + IntToString(nCount));
|
|
if(nCount < 1)
|
|
{
|
|
// "You have no castings of spells of level " + IntToString(nSpellLevel) + " remaining"
|
|
string sMessage = ReplaceChars(GetStringByStrRef(16828409), "<spelllevel>", IntToString(nSpellSlotLevel));
|
|
FloatingTextStringOnCreature(sMessage, OBJECT_SELF, FALSE);
|
|
if(bIsAction)
|
|
ClearAllActions();
|
|
}
|
|
else
|
|
{
|
|
SetLocalInt(OBJECT_SELF, "NSB_Cast", 1);
|
|
if(bIsAction)
|
|
{
|
|
SetLocalInt(OBJECT_SELF, "NSB_Class", nClass);
|
|
SetLocalInt(OBJECT_SELF, "NSB_SpellLevel", nSpellSlotLevel);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoCleanUp(int nMetamagic)
|
|
{
|
|
if(nMetamagic & METAMAGIC_QUICKEN)
|
|
{
|
|
object oSkin = GetPCSkin(OBJECT_SELF);
|
|
RemoveItemProperty(oSkin, ItemPropertyBonusFeat(IP_CONST_NSB_AUTO_QUICKEN));
|
|
}
|
|
DeleteLocalInt(OBJECT_SELF, "NSB_Class");
|
|
DeleteLocalInt(OBJECT_SELF, "NSB_SpellLevel");
|
|
DeleteLocalInt(OBJECT_SELF, "NSB_SpellbookID");
|
|
}
|
|
|
|
//:: Test Void
|
|
//:: void main (){} |