PRC8/nwn/nwnprc/trunk/include/prc_nui_lv_inc.nss
Jaysyn904 a882749366 2025/06/22 Update
Fixed Vow of Poverty.
Fixed Vow of Poverty bonus feats not being restored onClientEnter.
Added PRC option for spontaneous casters unlearning spells (@Rakiov)
Gloura was double dipping caster level.
Added NUI levelup spellpicker for most AMS classes (@Rakiov)
Tweaked Tactical Insight.
Updated ToB maneuver TLK entries to display correct prerequisites.
2025-06-22 19:40:24 -04:00

3337 lines
130 KiB
Plaintext

//::///////////////////////////////////////////////
//:: PRC Level Up NUI
//:: prc_nui_lv_inc
//:://////////////////////////////////////////////
/*
This is the logic for the Level Up NUI, holding all the functions needed for
the NUI to operate properly and allow leveling up in different classes.
*/
//:://////////////////////////////////////////////
//:: Created By: Rakiov
//:: Created On: 20.06.2005
//:://////////////////////////////////////////////
#include "prc_nui_com_inc"
#include "tob_inc_tobfunc"
#include "tob_inc_moveknwn"
#include "inv_inc_invfunc"
#include "shd_inc_mystknwn"
#include "shd_inc_shdfunc"
#include "true_inc_truknwn"
#include "true_inc_trufunc"
////////////////////////////////////////////////////////////////////////////
/// ///
/// Spont Casters / Base ///
/// ///
////////////////////////////////////////////////////////////////////////////
//
// GetSpellListObject
// Gets the JSON Object representation of a class's spellbook 2da. This function
// will cache it's result to the object given to it to avoid further calculations
// and will not clear itself since it does not change.
//
// Arguments:
// nClass:int the ClassID
// oPC:Object the player
//
// Returns:
// Json:Dictionary<String, List[]> a dictionary of each circle's spellbook Ids.
//
json GetSpellListObject(int nClass, object oPC=OBJECT_SELF);
//
// GetKnownSpellListObject
// Gets the JSON Object representation of a player's known spell list. This function
// will temporarily cache it's result to the object given to avoid further calculations.
// However this should be cleared after done using the level up screen or reset.
//
// Arguments:
// nClass:int the ClassID
// oPC:Object the player
//
// Returns:
// Json:Dictionary<String, List[]> a dictionary of each circle's known spellbook Ids.
//
json GetKnownSpellListObject(int nClass, object oPC=OBJECT_SELF);
//
// GetKnownSpellListObject
// Gets the JSON Object representation of a player's chosen spell list. This function
// will temporarily cache it's result to the object given to avoid further calculations.
// However this should be cleared after done using the level up screen or reset.
//
// Arguments:
// nClass:int the ClassID
// oPC:Object the player
//
// Returns:
// Json:Dictionary<String, List[]> a dictionary of each circle's chosen spellbook Ids.
//
json GetChosenSpellListObject(int nClass, object oPC=OBJECT_SELF);
//
// ShouldAddSpellToSpellButtons
// Given a classId and a spellbookId, if the player knows the spell already we
// should not add the spell, otherwise we should
//
// Arguments:
// nClass:int Class ID
// spellbookId:int the spell book ID
// oPC:object the player
//
// Returns:
// int:Boolean TRUE if spell should be added, FALSE otherwise
//
int ShouldAddSpellToSpellButtons(int nClass, int spellbookId, object oPC=OBJECT_SELF);
//
// OpenNUILevelUpWindow
// Opens the Level Up NUI window for the provided class
//
// Arguments:
// nClass:int the ClassID
//
void OpenNUILevelUpWindow(int nClass, object oPC=OBJECT_SELF);
//
// CloseNUILevelUpWindow
// Closes the NUI Level Up Window if its open
//
void CloseNUILevelUpWindow(object oPC=OBJECT_SELF);
//
// GetTrueClassType
// Gets the true class Id for a provided class Id, mostly for RHD and for
// ToB prestige classes
//
// Arguments:
// nClass:int classId
//
// Returns:
// int the true classId based off nClass
//
int GetTrueClassType(int nClass, object oPC=OBJECT_SELF);
//
// GetRemainingSpellChoices
// Gets the remaining spell choices for a class at the given circle by checking its
// chosen spells and comparing it against the total spells allowed. This value
// is cached on the player and cleared everytime the window is refreshed/closed
//
// Arguments:
// nClass:int the class id
// circleLevel:int the circle being checked
//
// Returns:
// int the amount of choices left at the circle
//
int GetRemainingSpellChoices(int nClass, int circleLevel, object oPC=OBJECT_SELF);
//
// ShouldSpellButtonBeEnabled
// Checks whether a spell button should be enabled either because all choices have
// been made, replacing spells isn't allowed, or for various other reasons
//
// Arguments:
// nClass:int class id
// circleLevel:int the chosen circle
// spellbookId:int the chosen spell
//
// Returns:
// int:Boolean TRUE if spell button should be enabled, FALSE otherwise
//
int ShouldSpellButtonBeEnabled(int nClass, int circleLevel, int spellbookId, object oPC=OBJECT_SELF);
//
// AddSpellToChosenList
// Adds spell to the chosen spells list
//
// Arguments:
// nClass:int the classId
// spellbookId:int the spellbook Id
// spellCircle:int the current circle of the spell
//
void AddSpellToChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF);
//
// RemoveSpellFromChosenList
// Removes a spell from the chosen spell list
//
// Arguments:
// nClass:int the class id
// spellbookId:int the spellbook Id
// spellCircle:int the circle of the spell
//
void RemoveSpellFromChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF);
//
// LearnSpells
// gives the player the spells they want to learn based off of the chosen spell
// list in a stored variable
//
// Arguments:
// nClass:int the classId
//
void LearnSpells(int nClass, object oPC=OBJECT_SELF);
//
// RemoveSpells
// removes spells from the player that they may know currently but aren't selected
// based off lists in stored variables
//
// Arguments:
// nClass:int the classId
//
void RemoveSpells(int nClass, object oPC=OBJECT_SELF);
//
// FinishLevelUp
// Finishes level up NUI by removing spells, learning spells, clearing cache, then closing the NUI
//
// Arguments:
// nClass:int the class id
//
void FinishLevelUp(int nClass, object oPC=OBJECT_SELF);
//
// ClearLevelUpNUICaches
// Clears the cache (stored local variables) for the level up NUI so it is
// ready to be used for a new level up
//
// Arguments:
// nClass:int class id
// oPC:object the player object this is stored under
//
void ClearLevelUpNUICaches(int nClass, object oPC=OBJECT_SELF);
//
// SpellIsWithinObject
// checks whether a spell is within a JSON Object structure used by the remaining
// spells object and known spells object, following this structure
// {
// "circleLevel:int": [ 1,2,3...,spellId],
// ...
// }
//
// Arguments
// nClass:int classId
// spellbookId:int the spellbook Id
// circleLevel:int the chosen circle of the spell
// spellList;JsonObject the spell list object being checked
//
// Returns:
// int:Boolean TRUE if it is in the object, FALSE otherwise
//
int SpellIsWithinObject(int nClass, int spellbookId, int circleLevel, json spellList, object oPC=OBJECT_SELF);
//
// IsLevelUpNUIOpen
// Checks if the Level Up NUI is open for the player or not
//
// Arguments:
// oPC:object the player object
//
// Returns:
// int:Boolean TRUE if it is, FALSE otherwise
//
int IsLevelUpNUIOpen(object oPC=OBJECT_SELF);
//
// IsClassAllowedToUseLevelUpNUI
// Is the provided class allowed to use the level up NUI
//
// Arguments:
// nClass:int class id
//
// Returns:
// int:Boolean TRUE if it can, FALSE otherwise
//
int IsClassAllowedToUseLevelUpNUI(int nClass);
//
// EnabledChosenButton
// determines if a chosen spell button should be enabled or not. It may not due to
// class restrictions, replacing is not enabled, or other reason
//
// Arguments:
// nClass:int the class id
// spellbookId: the spellbook Id
// circleLevel: the spell's circle
//
// Returns:
// int:Boolean TRUE if it should be enabled, FALSE otherwise
//
int EnableChosenButton(int nClass, int spellbookId, int circleLevel, object oPC=OBJECT_SELF);
//
// ResetChoices
// Action for the Level Up NUI's 'Reset' button, resets choices by clearing the cache of
// the user so their choices are forgotten and they can start over.
//
// Arguments:
// oPC:object the player object
//
void ResetChoices(object oPC=OBJECT_SELF);
//
// RemoveSpellKnown
// Removes a spell from a player based off class id. This is for classes that
// aren't spont casters where we have to go in and adjust persistant arrays
// to say if a spell is known or not.
//
// Arguments:
// nClass:int class id
// spellbookId:int the spellbook Id
// oPC:object the player object
// nList:int the list we are removing the spell from (extra invocations or expanded knowledge)
//
void RemoveSpellKnown(int nClass, int spellbookId, object oPC=OBJECT_SELF, int nList=0);
//
// GetSpellIDsKnown
// Gets the SpellIDs list of the given class and list and returns it as a JsonObject following this structure
// {
// "spellId:int": TRUE,
// ...
// }
//
// This is to keep lookups at O(1) processing time. This value is cached and is
// cleared when the player finishes level up
//
// Arguments:
// nClass:int class id
// oPC:object the player object
// nList:int the list we are checking if provided (extra invocations or expanded knowledge)
//
// Returns:
// JsonObject the list of spell ids the class knows in JsonObject format
//
json GetSpellIDsKnown(int nClass, object oPC=OBJECT_SELF, int nList=0);
//
// ReasonForDisabledSpell
// Provides the reason for why a spell choice is disabled
//
// Arguments:
// nClass:int the class id
// spellbookId:int the spellbook Id
//
// Returns:
// string the reason for the disabled button, empty string otherwise
//
string ReasonForDisabledSpell(int nClass, int spellbookId, object oPC=OBJECT_SELF);
//
// ReasonForDisabledChosen
// Provides the reason for why a chosen spell button is disabled
//
// Arguments:
// nClass:int the class id
// spellbookId:int the spellbook Id
//
// Returns:
// string the reason for the disabled button, empty string otherwise
//
string ReasonForDisabledChosen(int nClass, int spellbookId, object oPC=OBJECT_SELF);
//
// GetExpandedChoicesList
// Gets the expanded choices list for a class (the list of expanded knowledge or
// extra invocations). It follows this structure
//
// {
// "spellId:int": TRUE,
// ...
// }
// This is cached to reduce process times and is cleared everytime the window is refreshed/closed
//
// Arguments:
// nClass:int the class id
//
// Returns:
// JsonObject the object representation of the expanded choices
//
json GetExpandedChoicesList(int nClass, object oPC=OBJECT_SELF);
//
// GetExpandedChoicesList
// Gets the epic expanded choices list for a class (the list of expanded knowledge or
// extra invocations). It follows this structure
//
// {
// "spellId:int": TRUE,
// ...
// }
// This is cached to reduce process times and is cleared everytime the window is refreshed/closed
//
// Arguments:
// nClass:int the class id
//
// Returns:
// JsonObject the object representation of the expanded choices
//
json GetEpicExpandedChoicesList(int nClass, object oPC=OBJECT_SELF);
//
// GetRemainingExpandedChoices
// Gets the remaining expanded choices for a class based off list, comparing the
// total number of choices allowed and the total number chosen
//
// Arguments:
// nClass: class id
// nList: the list we are checking (extra invocations/expanded knowledge)
//
// Returns:
// int the amount of choices left
//
int GetRemainingExpandedChoices(int nClass, int nList, object oPC=OBJECT_SELF);
//
// IsSpellInExpandedChoices
// tells if a spell is in the expanded choices list or not
//
// Arguments:
// nClass:int class id
// nList:int the list we are checking (extra invocations/expanded knowledge)
// spellId:int the spell id (not the spellbook id)
//
// Returns
// int:Boolean TRUE if it is a expanded choice, FALSE otherwise
//
int IsSpellInExpandedChoices(int nClass, int nList, int spellId, object oPC=OBJECT_SELF);
//
// GetChosenReplaceListObject
// The chosen list of spells we wish to replace for PnP replacing if Bioware replacing
// is disabled. This is cached and is cleared when the player is finished leveling
// or resets their choices
//
// Arguments:
// oPC:object the player
//
// Returns:
// json the list of spells chosen to replace
//
json GetChosenReplaceListObject(object oPC=OBJECT_SELF);
////////////////////////////////////////////////////////////////////////////
/// ///
/// Psionics ///
/// ///
////////////////////////////////////////////////////////////////////////////
//
// IsExpKnowledgePower
// checks if a spell is a expanded knowledge spell
//
// Arguments:
// nClass:int class id
// spellbookId:int the spellbook Id
//
// Returns:
// int:Boolean TRUE if the spell is a expanded knowledge spell, FALSE otherwise
//
int IsExpKnowledgePower(int nClass, int spellbookId);
//
// GetExpKnowledgePowerListRequired
// Tells what list the spell should be added to based on if it was added to the
// expanded choices or epic expanded choices list
//
// Arguments:
// nClass:int the class id
// spellbookId:int the spellbook Id
//
// Returns:
// int -1 for the expanded knowledge list, -2 for the epic expanded knowledge
// list, 0 if just add it to the normal class list
//
int GetExpKnowledgePowerListRequired(int nClass, int spellbookId, object oPC=OBJECT_SELF);
//
// GetCurrentPowerList
// Gets the current chosen powers list. This is cached and is cleared when the
// player either finishs leveling up or resets.
//
// Arguments:
// oPC:object the player object
//
// Returns:
// JsonArray the list of chosen powers wanting to learn
//
json GetCurrentPowerList(object oPC=OBJECT_SELF);
//
// ShouldAddPower
// Tells if the power should be added to the list of choices or not. It may not
// be added because its an expanded knowledge choice and you have no more expanded
// knowledge slots, or it may be a restricted spell like psions list
//
// Arguments:
// nClass:int the class id
// spellbookId:int the spellbook id
//
// Returns:
// int:Boolean TRUE if it should be added, FALSE otherwise
//
int ShouldAddPower(int nClass, int spellbookId, object oPC=OBJECT_SELF);
//
// LearnPowers
// learns the list of chosen powers for the player based off their chosen power list
//
// Arguments:
// nClass:int class id
// oPC:object the player object where stored variables are
//
void LearnPowers(int nClass, object oPC=OBJECT_SELF);
//
// GetMaxPowerLevelForClass
// gets the max power level for the player based off their level and the class's
// known 2da
//
// Arguments:
// nClass:int the class id
// oPC:object the player
//
// Returns:
// int the max power level (circle) the player can achieve on that class
//
int GetMaxPowerLevelForClass(int nClass, object oPC=OBJECT_SELF);
//
// GetRemainingPowerChoices
// Gets the remaining power choices the character has at the given chosen circle/power level
//
// Arguments:
// nClass:int class id
// chosenCircle:int the chosen circle/power level
// oPC:object the player
// extra:int should we add the expanded knowledge choices or not
//
// Returns:
// int the number of choices left at the given circle
//
int GetRemainingPowerChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE);
////////////////////////////////////////////////////////////////////////////
/// ///
/// Initiators ///
/// ///
////////////////////////////////////////////////////////////////////////////
//
// GetDisciplineInfoObject
// Gets the disciplien info for the given class, telling what the chosen spells
// disicpline is, what type of maneuever it is, the different totals, and prerequisites.
// This is cached and is cleared when the window is refreshed/closed
//
// Argument:
// nClass:int class id
//
// Returns:
// JsonObject the object representation of the chosen spells discipline info
//
json GetDisciplineInfoObject(int nClass, object oPC=OBJECT_SELF);
//
// HasPreRequisitesForManeuver
// Does the player have the prerequisites for the given spell based off their chosen
// spell list
//
// Arguments:
// nClass:int the class id
// spellbookId:int the spellbook id
// oPC:object the player object with stored variables
//
// Returns:
// int:Boolean, TRUE if you have the prerequisites, FALSE otherwise
//
int HasPreRequisitesForManeuver(int nClass, int spellbookId, object oPC=OBJECT_SELF);
//
// GetMaxInitiatorCircle
// gets the max circle/level a player can obtain with the given class
//
// Arguments:
// nClass:int the class id
// oPC:object the player
//
// Returns:
// int the highest circle the player can achieve with the class
//
int GetMaxInitiatorCircle(int nClass, object oPC=OBJECT_SELF);
//
// GetRemainingManeuverChoices
// Gets remaining maneuever choices for the player
//
// Arguments:
// nClass:int class id
// oPC:object the player
//
// Returns:
// int the remaining maneuevers choices
//
int GetRemainingManeuverChoices(int nClass, object oPC=OBJECT_SELF);
//
// GetRemainingStanceChoices
// Gets remaining stance choices for the player
//
// Arguments:
// nClass:int class id
// oPC:object the player
//
// Returns:
// int the remaining stance choices
//
int GetRemainingStanceChoices(int nClass, object oPC=OBJECT_SELF);
//
// IsRequiredForOtherManeuvers
// Checks the given prerequisite number and the chosen spells to see if removing it
// will cause it to fail the requirement for other maneuevers
//
// Arguments:
// nClass:int the class id
// prereq:int the chosen spells prerequisite number of maneuevers needed
// discipline:string the chosen spells discipline
//
// Returns:
// int:Boolean TRUE if it is required, FALSE otherwise
//
int IsRequiredForOtherManeuvers(int nClass, int prereq, string discipline, object oPC=OBJECT_SELF);
//
// IsAllowedDiscipline
// checks to see if the given spell is a allowed discipline for a class
//
// Arguments:
// nClass:int class id
// spellbookId:int the spellbook id
//
// Returns:
// int:boolean TRUE if it is allowed, FALSE otherwise
//
int IsAllowedDiscipline(int nClass, int spellbookId, object oPC=OBJECT_SELF);
//
// AddSpellDisciplineInfo
// Adds the maneuver's discipline info to the class's discpline object
//
// Arguments:
// sFile:string the class's spell 2da
// spellbookId:int the spellbook Id
// classDisc:JsonObject the class discipline object we are adding to
//
// Returns:
// json:Object the classDisc with the given spells information added
//
json AddSpellDisciplineInfo(string sFile, int spellbookId, json classDisc);
//
// IsRequiredForToBPRCClass
// tells if a given maneuver is needed to satisfy a PRC's prerequsitie
//
// Arguments:
// nClass:int class id
// spellbookId:int the spellbook id
//
// Returns:
// int:Boolean TRUE if the maneuver is required for a PRC, FALSE otherwise.
//
int IsRequiredForToBPRCClass(int nClass, int spellbookId, object oPC=OBJECT_SELF);
////////////////////////////////////////////////////////////////////////////
/// ///
/// Invokers ///
/// ///
////////////////////////////////////////////////////////////////////////////
//
// GetInvokerKnownListObject
// gets the invokers known invocations list in object format, needed to tell how many
// of each invocation level does a person know at a given level. This is cached on the
// player and not cleared since it never changes.
//
// Arguments:
// nClass:int class id
//
// Returns:
// json:Object the list of invocations known in json format
//
json GetInvokerKnownListObject(int nClass, object oPC=OBJECT_SELF);
//
// GetRemainingInvocationChoices
// Gets the remaining invocation choices left
//
// Arguments:
// nClass:int class id
// chosenCircle:int the chosen circle we are checking
// oPC:Object the player
// extra:int should we count the number of extra invocations we have left
//
// Returns:
// int the amount of choices left at the given circle
//
int GetRemainingInvocationChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE);
//
// IsExtraChoiceInvocation
// tells if a given spell is a extra invocation choice
//
// Arguments:
// nClass:int class id
// spellbookId:int the spellbook id
//
// Returns:
// int;Boolean TRUE if it is a extra choice, FALSE otherwise
//
int IsExtraChoiceInvocation(int nClass, int spellbookId, object oPC=OBJECT_SELF);
////////////////////////////////////////////////////////////////////////////
/// ///
/// Truenamer ///
/// ///
////////////////////////////////////////////////////////////////////////////
//
// GetRemainingTruenameChoices
// gets the remaining truename choices left at the given lexicon type
//
// Arguments:
// nClass:int class id
// nType:int the lexicon
//
// Returns:
// int the amount of truename choices left for the given lexicon
//
int GetRemainingTruenameChoices(int nClass, int nType, object oPC=OBJECT_SELF);
//
// GetLexiconCircleKnownAtLevel
// gets the known circle level for a given lexicon
//
// Arguments:
// nLevel:int the level to check
// nType:int the lexicon we are checking
//
// Returns:
// int the highest circle we can achieve
//
int GetLexiconCircleKnownAtLevel(int nLevel, int nType);
////////////////////////////////////////////////////////////////////////////
/// ///
/// Archivist ///
/// ///
////////////////////////////////////////////////////////////////////////////
json GetArchivistNewSpellsList(object oPC=OBJECT_SELF);
////////////////////////////////////////////////////////////////////////////
/// ///
/// Spont Casters / Base ///
/// ///
////////////////////////////////////////////////////////////////////////////
int IsLevelUpNUIOpen(object oPC=OBJECT_SELF)
{
int nPreviousToken = NuiFindWindow(oPC, NUI_LEVEL_UP_WINDOW_ID);
if (nPreviousToken != 0)
{
return TRUE;
}
return FALSE;
}
int IsClassAllowedToUseLevelUpNUI(int nClass)
{
if (GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS)
return TRUE;
if (nClass == CLASS_TYPE_PSYWAR
|| nClass == CLASS_TYPE_PSYCHIC_ROGUE
|| nClass == CLASS_TYPE_PSION
|| nClass == CLASS_TYPE_FIST_OF_ZUOKEN
|| nClass == CLASS_TYPE_WILDER
|| nClass == CLASS_TYPE_WARMIND)
return TRUE;
if (nClass == CLASS_TYPE_WARBLADE
|| nClass == CLASS_TYPE_SWORDSAGE
|| nClass == CLASS_TYPE_CRUSADER)
return TRUE;
if (nClass == CLASS_TYPE_WARLOCK
|| nClass == CLASS_TYPE_DRAGONFIRE_ADEPT
|| nClass == CLASS_TYPE_DRAGON_SHAMAN)
return TRUE;
if (nClass == CLASS_TYPE_SHADOWCASTER
|| nClass == CLASS_TYPE_SHADOWSMITH)
return TRUE;
if (nClass == CLASS_TYPE_TRUENAMER)
return TRUE;
if (nClass == CLASS_TYPE_ARCHIVIST)
return TRUE;
return FALSE;
}
int SpellIsWithinObject(int nClass, int spellbookId, int circleLevel, json spellList, object oPC=OBJECT_SELF)
{
// check to see if the spell circle isn't empty
json currentList = JsonObjectGet(spellList, IntToString(circleLevel));
if (currentList == JsonNull())
return FALSE;
int totalSpells = JsonGetLength(currentList);
// then loop through the spell list and find the spell.
int i;
for (i = 0; i < totalSpells; i++)
{
int currentSpell = JsonGetInt(JsonArrayGet(currentList, i));
if (currentSpell == spellbookId)
return TRUE;
}
return FALSE;
}
int AllSpellsAreChosen(int nClass, object oPC=OBJECT_SELF)
{
// we need the max number of circles a class has.
json spellList = GetSpellListObject(nClass, oPC);
json spellCircles = JsonObjectKeys(spellList);
int totalCircles = JsonGetLength(spellCircles);
int i;
for (i = 0; i < totalCircles; i++)
{
// loop through each circle and check if you have any remaining choices left
// if you do or you have a deficit then you need to remove or add something
// until you get 0
int spellCircle = StringToInt(JsonGetString(JsonArrayGet(spellCircles, i)));
int remainingChoices = GetRemainingSpellChoices(nClass, spellCircle, oPC);
if (remainingChoices < 0 || remainingChoices > 0)
return FALSE;
}
return TRUE;
}
void AddSpellToChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF)
{
if (GetIsInvocationClass(nClass))
{
// get the remaining invocation choices left without extra feats
// if it is 0 then we are adding the chosen invocation to the extra lists
int totalInvocations = GetRemainingInvocationChoices(nClass, spellCircle, oPC, FALSE);
if (totalInvocations == 0)
{
string sFile = GetClassSpellbookFile(nClass);
if (GetRemainingExpandedChoices(nClass, INVOCATION_LIST_EXTRA, oPC))
{
json expList = GetExpandedChoicesList(nClass, oPC);
string spellId = Get2DACache(sFile, "SpellID", spellbookId);
expList = JsonObjectSet(expList, spellId, JsonBool(TRUE));
SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList);
}
else if (GetRemainingExpandedChoices(nClass, INVOCATION_LIST_EXTRA_EPIC, oPC))
{
json expList = GetEpicExpandedChoicesList(nClass, oPC);
string spellId = Get2DACache(sFile, "SpellID", spellbookId);
expList = JsonObjectSet(expList, spellId, JsonBool(TRUE));
SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList);
}
}
}
if (GetIsPsionicClass(nClass))
{
// if the power is a expanded knowledge than we immediatly add it to the
// extra list, otherwise check to make sure we have made all choices in our
// base list first before adding it to the extra list.
if (IsExpKnowledgePower(nClass, spellbookId)
|| GetRemainingPowerChoices(nClass, spellCircle, oPC, FALSE) == 0)
{
string sFile = GetClassSpellbookFile(nClass);
if (GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC))
{
json expList = GetExpandedChoicesList(nClass, oPC);
string spellId = Get2DACache(sFile, "SpellID", spellbookId);
expList = JsonObjectSet(expList, spellId, JsonBool(TRUE));
SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList);
}
else if (GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC))
{
json expList = GetEpicExpandedChoicesList(nClass, oPC);
string spellId = Get2DACache(sFile, "SpellID", spellbookId);
expList = JsonObjectSet(expList, spellId, JsonBool(TRUE));
SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList);
}
}
// add the power to the current power list.
json currPowerList = GetCurrentPowerList(oPC);
currPowerList = JsonArrayInsert(currPowerList, JsonInt(spellbookId));
SetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR, currPowerList);
}
if (nClass == CLASS_TYPE_ARCHIVIST)
{
json newSpells = GetArchivistNewSpellsList(oPC);
newSpells = JsonArrayInsert(newSpells, JsonInt(spellbookId));
SetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR, newSpells);
}
// base logic for spont casters, add the spell to the ChosenSpells JSON object
// by adding it to it's appropriate circle.
json chosenSpells = GetChosenSpellListObject(nClass, oPC);
json spellsAtCircle = JsonObjectGet(chosenSpells, IntToString(spellCircle));
if (spellsAtCircle == JsonNull())
spellsAtCircle = JsonArray();
spellsAtCircle = JsonArrayInsert(spellsAtCircle, JsonInt(spellbookId));
chosenSpells = JsonObjectSet(chosenSpells, IntToString(spellCircle), spellsAtCircle);
SetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR, chosenSpells);
// if we are not using bioware unlearning logic, then we need to limit the
// amount of spells we can replace.
if (!GetPRCSwitch(PRC_BIO_UNLEARN))
{
json unlearnList = GetChosenReplaceListObject(oPC);
// if the spell belongs to the unlearn list, then remove it to make room
// for a new spell.
if (JsonObjectGet(unlearnList, IntToString(spellbookId)) != JsonNull())
{
unlearnList = JsonObjectDel(unlearnList, IntToString(spellbookId));
SetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR, unlearnList);
}
}
}
void RemoveSpellFromChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF)
{
json chosenSpells = GetChosenSpellListObject(nClass, oPC);
json spellsAtCircle = JsonObjectGet(chosenSpells, IntToString(spellCircle));
if (spellsAtCircle == JsonNull())
spellsAtCircle = JsonArray();
int totalSpells = JsonGetLength(spellsAtCircle);
// find the spell at the circle in the chosen list and remove it.
int i;
for (i = 0; i < totalSpells; i++)
{
if (spellbookId == JsonGetInt(JsonArrayGet(spellsAtCircle, i)))
{
spellsAtCircle = JsonArrayDel(spellsAtCircle, i);
break;
}
}
chosenSpells = JsonObjectSet(chosenSpells, IntToString(spellCircle), spellsAtCircle);
SetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR, chosenSpells);
// if we re not using bioware unlearn logic we need to limit how many spells
// can be replaced
if (!GetPRCSwitch(PRC_BIO_UNLEARN))
{
json knownSpells = GetKnownSpellListObject(nClass, oPC);
json spellListAtCircle = JsonObjectGet(knownSpells, IntToString(spellCircle));
int totalSpells = JsonGetLength(spellListAtCircle);
// with the list of known spells, check the selected circle and see if the
// current spell belongs in the already known spell list.
for (i = 0; i < totalSpells; i++)
{
int chosenSpell = JsonGetInt(JsonArrayGet(spellListAtCircle, i));
if (chosenSpell == spellbookId)
{
// if it does we need to add the spell to the unlearn JSON object to track what spells
// are being replaced.
json unlearnList = GetChosenReplaceListObject(oPC);
unlearnList = JsonObjectSet(unlearnList, IntToString(spellbookId), JsonBool(TRUE));
SetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR, unlearnList);
break;
}
}
}
if (GetIsPsionicClass(nClass))
{
string sFile = GetClassSpellbookFile(nClass);
string spellId = Get2DACache(sFile, "SpellID", spellbookId);
// for psionics we need to check if the removed spell was a expanded knowledge choice
// or not. The id of the list is -1 or -2.
int i;
for (i == -1; i >= -2; i--)
{
json expList = (i == -1) ? GetExpandedChoicesList(nClass, oPC) :
GetEpicExpandedChoicesList(nClass, oPC);
//if the spell belongs in the expanded knowledge list, then we need
// to remove it.
if (JsonObjectGet(expList, spellId) != JsonNull())
{
expList = JsonObjectDel(expList, spellId);
if (i == POWER_LIST_EXP_KNOWLEDGE)
SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList);
else
SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList);
}
}
// then we need to remove the power from the selected powers list.
json currPowerChoices = GetCurrentPowerList(oPC);
int totalPowers = JsonGetLength(currPowerChoices);
for (i = 0; i < totalPowers; i++)
{
if (spellbookId == JsonGetInt(JsonArrayGet(currPowerChoices, i)))
{
currPowerChoices = JsonArrayDel(currPowerChoices, i);
break;
}
}
SetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR, currPowerChoices);
}
if (GetIsInvocationClass(nClass))
{
string sFile = GetClassSpellbookFile(nClass);
string spellId = Get2DACache(sFile, "SpellID", spellbookId);
// for invocations we need to check if the spell was added to the extra
// invocations list, the list ids are the invalid class id, and -2
int i;
for (i = 0; i <= 1; i++)
{
json expList = (i == 0) ? GetExpandedChoicesList(nClass, oPC) :
GetEpicExpandedChoicesList(nClass, oPC);
// if the spell was found, remove it.
if (JsonObjectGet(expList, spellId) != JsonNull())
{
expList = JsonObjectDel(expList, spellId);
if (i == 0)
SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList);
else
SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList);
}
}
}
if (nClass == CLASS_TYPE_ARCHIVIST)
{
json newSpells = GetArchivistNewSpellsList(oPC);
int totalNew = JsonGetLength(newSpells);
int i;
for (i = 0; i < totalNew; i++)
{
int newSpellbookId = JsonGetInt(JsonArrayGet(newSpells, i));
if (newSpellbookId == spellbookId)
{
newSpells = JsonArrayDel(newSpells, i);
SetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR, newSpells);
break;
}
}
}
}
void OpenNUILevelUpWindow(int nClass, object oPC=OBJECT_SELF)
{
CloseNUILevelUpWindow(oPC);
// set the NUI to the given classId
int currentClass = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR);
// we need to clear the cache if it was used before to avoid weird behaviors
ClearLevelUpNUICaches(currentClass, oPC);
// sometimes we are given a different classId instead of the base, we need to
// figure out what the true base class is (mostly true for RHD)
int chosenClass = GetTrueClassType(nClass, oPC);
SetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR, chosenClass);
ExecuteScript("prc_nui_lv_view", oPC);
}
int GetTrueClassType(int nClass, object oPC=OBJECT_SELF)
{
if (nClass == CLASS_TYPE_JADE_PHOENIX_MAGE
|| nClass == CLASS_TYPE_MASTER_OF_NINE
|| nClass == CLASS_TYPE_DEEPSTONE_SENTINEL
|| nClass == CLASS_TYPE_BLOODCLAW_MASTER
|| nClass == CLASS_TYPE_RUBY_VINDICATOR
|| nClass == CLASS_TYPE_ETERNAL_BLADE
|| nClass == CLASS_TYPE_SHADOW_SUN_NINJA)
{
int trueClass = GetPrimaryBladeMagicClass(oPC);
return trueClass;
}
if ((nClass == CLASS_TYPE_SHAPECHANGER
&& GetRacialType(oPC) == RACIAL_TYPE_ARANEA)
|| (nClass == CLASS_TYPE_OUTSIDER
&& GetRacialType(oPC) == RACIAL_TYPE_RAKSHASA)
|| (nClass == CLASS_TYPE_ABERRATION
&& GetRacialType(oPC) == RACIAL_TYPE_DRIDER)
|| (nClass == CLASS_TYPE_MONSTROUS
&& GetRacialType(oPC) == RACIAL_TYPE_ARKAMOI)
|| (nClass == CLASS_TYPE_MONSTROUS
&& GetRacialType(oPC) == RACIAL_TYPE_HOBGOBLIN_WARSOUL)
|| (nClass == CLASS_TYPE_MONSTROUS
&& GetRacialType(oPC) == RACIAL_TYPE_REDSPAWN_ARCANISS)
|| (nClass == CLASS_TYPE_MONSTROUS
&& GetRacialType(oPC) == RACIAL_TYPE_MARRUTACT))
return CLASS_TYPE_SORCERER;
if (nClass == CLASS_TYPE_FEY
&& GetRacialType(oPC) == RACIAL_TYPE_GLOURA)
return CLASS_TYPE_BARD;
return nClass;
}
void CloseNUILevelUpWindow(object oPC=OBJECT_SELF)
{
int currentClass = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR);
// if we are refreshing the NUI but not finished we need to clear some caching done
// to save computation time as they will need to be reprocessed.
DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(currentClass));
SetLocalInt(oPC, NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR, -20);
int nPreviousToken = NuiFindWindow(oPC, NUI_LEVEL_UP_WINDOW_ID);
if (nPreviousToken != 0)
{
NuiDestroy(oPC, nPreviousToken);
}
}
int ShouldSpellButtonBeEnabled(int nClass, int circleLevel, int spellbookId, object oPC=OBJECT_SELF)
{
// logic for psionics
if (GetIsPsionicClass(nClass))
{
int maxLevel = GetMaxPowerLevelForClass(nClass, oPC);
if (circleLevel > maxLevel)
return FALSE;
// if its an expanded knowledge choice and we have already made all our
// exp knowledge choices then it needs to be disabled.
if (IsExpKnowledgePower(nClass, spellbookId))
{
int remainingExp = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC)
+ GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC);
if (!remainingExp)
return FALSE;
}
}
if (GetIsShadowMagicClass(nClass))
{
// mysteries are weird, the circles are sectioned by 1-3, 4-6, 7-9
// if you do not have at least 2 choices from a circle you can't progress up
// so you can have access to circles 1,2,4,7,8
int nType = 1;
if (circleLevel >= 4 && circleLevel <= 6)
nType = 2;
if (circleLevel >= 7 && circleLevel <= 9)
nType = 3;
int maxPossibleCircle = GetMaxMysteryLevelLearnable(oPC, nClass, nType);
if (circleLevel > maxPossibleCircle)
return FALSE;
}
if (GetIsTruenamingClass(nClass))
{
string sFile = GetClassSpellbookFile(nClass);
int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", spellbookId));
// each lexicon learns at different rates
int maxCircle = GetLexiconCircleKnownAtLevel(GetLevelByClass(nClass, oPC), lexicon);
if (circleLevel > maxCircle)
return FALSE;
if (GetRemainingTruenameChoices(nClass, lexicon, oPC))
return TRUE;
return FALSE;
}
// logic for ToB
if (GetIsBladeMagicClass(nClass))
{
if (circleLevel > GetMaxInitiatorCircle(nClass, oPC))
return FALSE;
// if you do not have the prerequisite amount of maneuevers to learn
// the maneuever, then you can't learn it.
if (!HasPreRequisitesForManeuver(nClass, spellbookId, oPC))
return FALSE;
// maneuvers and stances have their own seperate limits
string sFile = GetClassSpellbookFile(nClass);
int type = StringToInt(Get2DACache(sFile, "Type", spellbookId));
if (type == MANEUVER_TYPE_BOOST
|| type == MANEUVER_TYPE_COUNTER
|| type == MANEUVER_TYPE_STRIKE
|| type == MANEUVER_TYPE_MANEUVER)
{
int remainingMan = GetRemainingManeuverChoices(nClass, oPC);
if (remainingMan)
return TRUE;
return FALSE;
}
if (type == MANEUVER_TYPE_STANCE)
{
int remainingStance = GetRemainingStanceChoices(nClass, oPC);
if (remainingStance)
return TRUE;
return FALSE;
}
}
if (nClass == CLASS_TYPE_ARCHIVIST)
{
int maxLevel = GetMaxSpellLevelForCasterLevel(nClass, GetCasterLevelByClass(nClass, oPC));
if (circleLevel > maxLevel)
return FALSE;
}
// default logic
// determine remaining Spell/Power choices left for player, if there is any
// remaining, enable the buttons.
if (GetRemainingSpellChoices(nClass, circleLevel, oPC))
return TRUE;
return FALSE;
}
int ShouldAddSpellToSpellButtons(int nClass, int spellbookId, object oPC=OBJECT_SELF)
{
json chosenSpells = GetChosenSpellListObject(nClass, oPC);
string sFile = GetClassSpellbookFile(nClass);
string spellLevel = Get2DACache(sFile, "Level", spellbookId);
json chosenSpellsAtCircle = JsonObjectGet(chosenSpells, spellLevel);
int chosenSpellCount = JsonGetLength(chosenSpellsAtCircle);
// if the spell is in the chosen list, then don't add it to the available list
int i;
for (i = 0; i < chosenSpellCount; i++)
{
int chosenSpellId = JsonGetInt(JsonArrayGet(chosenSpellsAtCircle, i));
if (chosenSpellId == spellbookId)
return FALSE;
}
if (GetIsBladeMagicClass(nClass))
return IsAllowedDiscipline(nClass, spellbookId, oPC);
// if a psionic class we need to see if the power is a expanded knowledge
// choice and if we should show it or not
if (GetIsPsionicClass(nClass))
return ShouldAddPower(nClass, spellbookId, oPC);
// for these set of classes we need to only allow 'advanced learning'
// spells to be added
if (nClass == CLASS_TYPE_BEGUILER
|| nClass == CLASS_TYPE_DREAD_NECROMANCER
|| nClass == CLASS_TYPE_WARMAGE)
{
int advancedLearning = StringToInt(Get2DACache(sFile, "AL", spellbookId));
if (advancedLearning)
return TRUE;
return FALSE;
}
if (nClass == CLASS_TYPE_ARCHIVIST)
{
int nLevel = GetLevelByClass(nClass, oPC);
if ((StringToInt(spellLevel) == 0) && (nLevel == 1))
return FALSE;
int advancedLearning = StringToInt(Get2DACache(sFile, "AL", spellbookId));
if (advancedLearning)
return FALSE;
}
return TRUE;
}
json GetChosenSpellListObject(int nClass, object oPC=OBJECT_SELF)
{
json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR);
// if this isn't set yet then we the chosen currently is the known spells
if (retValue == JsonNull())
{
retValue = GetKnownSpellListObject(nClass, oPC);
SetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR, retValue);
}
return retValue;
}
json GetKnownSpellListObject(int nClass, object oPC=OBJECT_SELF)
{
json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_SPELLS_VAR);
if (retValue == JsonNull())
retValue = JsonObject();
else
return retValue;
string sFile = GetClassSpellbookFile(nClass);
int totalSpells = Get2DARowCount(sFile);
if (nClass == CLASS_TYPE_ARCHIVIST)
{
int i;
for (i = 0; i < 10; i++)
{
string sSpellbook = GetSpellsKnown_Array(nClass, i);
int nSize = persistant_array_get_size(oPC, sSpellbook);
int j;
for (j = 0; j < nSize; j++)
{
int knownSpellbookID = persistant_array_get_int(oPC, sSpellbook, j);
// we store things in a JSON Object where the spell circle
// is the key to a JsonArray of spellbookIds.
json spellList = JsonObjectGet(retValue, IntToString(i));
if (spellList == JsonNull())
spellList = JsonArray();
spellList = JsonArrayInsert(spellList, JsonInt(knownSpellbookID));
retValue = JsonObjectSet(retValue, IntToString(i), spellList);
}
}
}
else
{
// loop through all the spells in the class's 2da
int i;
for (i = 0; i < totalSpells; i++)
{
int featId = StringToInt(Get2DACache(sFile, "FeatID", i));
// if you have the feat, you know the spell
if (featId && GetHasFeat(featId, oPC, TRUE))
{
string spellLevel = Get2DACache(sFile, "Level", i);
int nSpellLevel = StringToInt(spellLevel);
// some spells have **** as their level, so make sure we have
// parsed it correctly
if (IntToString(nSpellLevel) == spellLevel)
{
// we store things in a JSON Object where the spell circle
// is the key to a JsonArray of spellbookIds.
json spellList = JsonObjectGet(retValue, spellLevel);
if (spellList == JsonNull())
spellList = JsonArray();
spellList = JsonArrayInsert(spellList, JsonInt(i));
retValue = JsonObjectSet(retValue, spellLevel, spellList);
}
}
}
}
SetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_SPELLS_VAR, retValue);
return retValue;
}
json GetSpellListObject(int nClass, object oPC=OBJECT_SELF)
{
json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_SPELLBOOK_OBJECT_CACHE_VAR + IntToString(nClass));
if (retValue == JsonNull())
retValue = JsonObject();
else
return retValue;
string sFile = GetClassSpellbookFile(nClass);
int totalSpells = Get2DARowCount(sFile);
// loop through all the spells in the 2da and convert it to a JSON Object representation
int i;
for (i = 0; i < totalSpells; i++)
{
string spellLevel = Get2DACache(sFile, "Level", i);
int nSpellLevel = StringToInt(spellLevel);
// some spells in the list have **** as spell level. We need to ignore them
if (IntToString(nSpellLevel) == spellLevel)
{
if (nClass == CLASS_TYPE_ARCHIVIST)
{
int reqFeat = StringToInt(Get2DACache(sFile, "ReqFeat", i));
if (!reqFeat)
{
json spellList = JsonObjectGet(retValue, spellLevel);
if (spellList == JsonNull())
spellList = JsonArray();
spellList = JsonArrayInsert(spellList, JsonInt(i));
retValue = JsonObjectSet(retValue, spellLevel, spellList);
}
}
else
{
json spellList = JsonObjectGet(retValue, spellLevel);
if (spellList == JsonNull())
spellList = JsonArray();
spellList = JsonArrayInsert(spellList, JsonInt(i));
retValue = JsonObjectSet(retValue, spellLevel, spellList);
}
}
}
SetLocalJson(oPC, NUI_LEVEL_UP_SPELLBOOK_OBJECT_CACHE_VAR + IntToString(nClass), retValue);
return retValue;
}
int GetRemainingSpellChoices(int nClass, int circleLevel, object oPC=OBJECT_SELF)
{
int chosenCircle = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR);
int remainingChoices = 0;
// we only want to cache on the current circle.
if (chosenCircle == circleLevel)
{
remainingChoices = GetLocalInt(oPC, NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR);
// -20 is the chosen number to say there is no cache set since 0 is
// a valid option
if (remainingChoices != -20)
return remainingChoices;
}
// logic for psionics
if (GetIsPsionicClass(nClass))
remainingChoices = GetRemainingPowerChoices(nClass, circleLevel, oPC);
// logic for ToB
if (GetIsBladeMagicClass(nClass))
remainingChoices = (GetRemainingManeuverChoices(nClass, oPC) +
GetRemainingStanceChoices(nClass, oPC));
// logic for Invokers
if (GetIsInvocationClass(nClass))
remainingChoices = GetRemainingInvocationChoices(nClass, circleLevel, oPC);
// logic for mysteries
if (GetIsShadowMagicClass(nClass))
{
int totalChosen = 0;
json chosenSpells = GetChosenSpellListObject(nClass, oPC);
json circles = JsonObjectKeys(chosenSpells);
int totalCircles = JsonGetLength(circles);
int i;
for (i = 0; i < totalCircles; i++)
{
// loop through each circle and add its total spells together since
// we don't care about where you spend your spells, only the amount
string currentCircle = JsonGetString(JsonArrayGet(circles, i));
json spellList = JsonObjectGet(chosenSpells, currentCircle);
if (spellList != JsonNull())
totalChosen += JsonGetLength(spellList);
}
int maxKnown = GetMaxMysteryCount(oPC, nClass);
remainingChoices = (maxKnown - totalChosen);
}
if (GetIsTruenamingClass(nClass))
remainingChoices = GetRemainingTruenameChoices(nClass, -1, oPC);
if (nClass == CLASS_TYPE_ARCHIVIST)
{
int nLevel = GetLevelByClass(CLASS_TYPE_ARCHIVIST, oPC);
int spellsAvailable;
if (nLevel == 1)
spellsAvailable = (3 + GetAbilityModifier(ABILITY_INTELLIGENCE, oPC));
else
spellsAvailable = 2;
json newSpells = GetArchivistNewSpellsList(oPC);
int totalNewSpells = JsonGetLength(newSpells);
remainingChoices = (spellsAvailable - totalNewSpells);
}
if (GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS)
{
json chosenSpells = GetChosenSpellListObject(nClass, oPC);
int totalSpellsKnown = 0;
int casterLevel = GetCasterLevelByClass(nClass, oPC);
// these specific classes only learn at specific rates
int advancedLearning = 0;
// beguiler learns every 4th level starting on 3
if (nClass == CLASS_TYPE_BEGUILER)
advancedLearning = ((casterLevel+1)/4);
// dread learns every 4th level
if (nClass == CLASS_TYPE_DREAD_NECROMANCER)
advancedLearning = (casterLevel/4);
// warmage is a bastard child that choses when it learns a spell whenever
// it decides it feels like it wants to
if (nClass == CLASS_TYPE_WARMAGE)
{
if (casterLevel >= 3) // 1 choice
advancedLearning++;
if (casterLevel >= 6) // 2 choice
advancedLearning++;
if (casterLevel >= 11) // 3 choice
advancedLearning++;
if (casterLevel >= 16) // 4 choice
advancedLearning++;
if (casterLevel >= 24) // 5 choice
advancedLearning++;
if (casterLevel >= 28) // 6 choice
advancedLearning++;
if (casterLevel >= 32) // 7 choice
advancedLearning++;
if (casterLevel >= 36) // 8 choice
advancedLearning++;
if (casterLevel >= 40) // 9 choice
advancedLearning++;
}
if (advancedLearning)
{
int maxSpellLevel = GetMaxSpellLevelForCasterLevel(nClass, casterLevel);
// can't learn what you can't achieve
if (circleLevel > maxSpellLevel)
remainingChoices = 0;
else
{
int chosenSpellsAmount = 0;
json circles = JsonObjectKeys(chosenSpells);
int totalCircles = JsonGetLength(circles);
string sFile = GetClassSpellbookFile(nClass);
int i;
for (i = 0; i <= totalCircles; i++)
{
string currentCircle = JsonGetString(JsonArrayGet(circles, i));
json spellList = JsonObjectGet(chosenSpells, currentCircle);
if ((spellList != JsonNull()))
{
// loop through the spells of a given circle and count how
// many advanced learning spells you know
int numOfSpells = JsonGetLength(spellList);
int j;
for (j = 0; j < numOfSpells; j++)
{
int nSpellbookID = JsonGetInt(JsonArrayGet(spellList, j));
int isAL = StringToInt(Get2DACache(sFile, "AL", nSpellbookID));
if (isAL)
chosenSpellsAmount++;
}
}
}
remainingChoices = (advancedLearning - chosenSpellsAmount);
}
}
else
{
// default logic for spont casters
totalSpellsKnown = GetSpellKnownMaxCount(casterLevel, circleLevel, nClass, oPC);
// Favoured Soul has more 0 choices than there are spells for some reason
if (nClass == CLASS_TYPE_FAVOURED_SOUL && circleLevel == 0 && totalSpellsKnown > 6)
totalSpellsKnown = 6;
// logic for spont casters
json selectedCircle = JsonObjectGet(chosenSpells, IntToString(circleLevel));
if (selectedCircle == JsonNull())
return totalSpellsKnown;
int selectedSpellCount = JsonGetLength(selectedCircle);
remainingChoices = (totalSpellsKnown - selectedSpellCount);
}
}
if (chosenCircle == circleLevel)
SetLocalInt(oPC, NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR, remainingChoices);
return remainingChoices;
}
void FinishLevelUp(int nClass, object oPC=OBJECT_SELF)
{
RemoveSpells(nClass, oPC);
LearnSpells(nClass, oPC);
if (nClass == CLASS_TYPE_ARCHIVIST)
{
int nLevel = GetLevelByClass(nClass, oPC);
SetPersistantLocalInt(oPC, "LastSpellGainLevel", nLevel);
}
ClearLevelUpNUICaches(nClass, oPC);
}
void ClearLevelUpNUICaches(int nClass, object oPC=OBJECT_SELF)
{
// clear the chosen spells you made
DeleteLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR);
// clear the known spells you have
DeleteLocalJson(oPC, NUI_LEVEL_UP_KNOWN_SPELLS_VAR);
SetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR, -1);
DeleteLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR);
// clear the psionics selected choices
DeleteLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR);
// clear the expanded choices for psionics and invokers
DeleteLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR);
DeleteLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR);
// clear the PnP replace list
DeleteLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR);
DeleteLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR);
// for invocation and psionics we grab the list of known extra spells and cache it
// so we need to clear those caches
if (GetIsInvocationClass(nClass))
{
DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_0");
DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(INVOCATION_LIST_EXTRA));
DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(INVOCATION_LIST_EXTRA_EPIC));
}
if (GetIsPsionicClass(nClass))
{
DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_0");
DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(POWER_LIST_EXP_KNOWLEDGE));
DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(POWER_LIST_EPIC_EXP_KNOWLEDGE));
}
// for ToB we need to clear all the discipline info for determining PrC choice validity
if (GetIsBladeMagicClass(nClass))
{
DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(CLASS_TYPE_SWORDSAGE));
DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(CLASS_TYPE_WARBLADE));
DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(CLASS_TYPE_CRUSADER));
}
}
void RemoveSpells(int nClass, object oPC=OBJECT_SELF)
{
// we don't remove on psionic classes and archivist
if (GetIsPsionicClass(nClass) || nClass == CLASS_TYPE_ARCHIVIST)
return;
json knownSpells = GetKnownSpellListObject(nClass, oPC);
json chosenSpells = GetChosenSpellListObject(nClass, oPC);
json spellCircles = JsonObjectKeys(knownSpells);
int totalCircles = JsonGetLength(spellCircles);
// loop through all the known spells circles
int i;
for (i = 0; i < totalCircles; i++)
{
string sSpellLevel = JsonGetString(JsonArrayGet(spellCircles, i));
int nSpellLevel = StringToInt(sSpellLevel);
json chosenCircle = JsonObjectGet(knownSpells, sSpellLevel);
int totalSpells = JsonGetLength(chosenCircle);
// loop through the spell list at the given circle
int y;
for (y = 0; y < totalSpells; y++)
{
int nSpellbookID = JsonGetInt(JsonArrayGet(chosenCircle, y));
// if the spell is not a chosen spell, then it was removed
if (!SpellIsWithinObject(nClass, nSpellbookID, nSpellLevel, chosenSpells, oPC))
{
if (GetIsInvocationClass(nClass))
{
string sFile = GetClassSpellbookFile(nClass);
string spellId = Get2DACache(sFile, "SpellID", nSpellbookID);
int chosenList = 0;
// check to see if its a extra invocation choice and set it's chosen list
if (GetHasFeat(FEAT_EXTRA_INVOCATION_I, oPC))
{
json expList = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA);
if (JsonObjectGet(expList, spellId) != JsonNull())
chosenList = INVOCATION_LIST_EXTRA;
}
if (GetHasFeat(FEAT_EPIC_EXTRA_INVOCATION_I, oPC))
{
json expList = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA_EPIC);
if (JsonObjectGet(expList, spellId) != JsonNull())
chosenList = INVOCATION_LIST_EXTRA_EPIC;
}
RemoveSpellKnown(nClass, nSpellbookID, oPC, chosenList);
}
if (GetIsBladeMagicClass(nClass) || GetIsShadowMagicClass(nClass))
RemoveSpellKnown(nClass, nSpellbookID, oPC);
if (GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS)
{
string sFile = GetClassSpellbookFile(nClass);
string sSpellBook = GetSpellsKnown_Array(nClass);
// remove the spell from the spellbook
array_extract_int(oPC, sSpellBook, nSpellbookID);
// wipe the spell from the player
int ipFeatID = StringToInt(Get2DACache(sFile, "IPFeatID", nSpellbookID));
WipeSpellFromHide(ipFeatID, oPC);
}
}
}
}
}
void LearnSpells(int nClass, object oPC=OBJECT_SELF)
{
if (GetIsPsionicClass(nClass))
{
LearnPowers(nClass, oPC);
return;
}
json chosenSpells = GetChosenSpellListObject(nClass, oPC);
json knownSpells = GetKnownSpellListObject(nClass, oPC);
json spellCircles = JsonObjectKeys(chosenSpells);
int totalCircles = JsonGetLength(spellCircles);
// loop through chosen spells circles
int i;
for (i = 0; i < totalCircles; i++)
{
string sSpellLevel = JsonGetString(JsonArrayGet(spellCircles, i));
int nSpellLevel = StringToInt(sSpellLevel);
json chosenCircle = JsonObjectGet(chosenSpells, sSpellLevel);
int totalSpells = JsonGetLength(chosenCircle);
// loop through the spell list at the circle
int y;
for (y = 0; y < totalSpells; y++)
{
int nSpellbookID = JsonGetInt(JsonArrayGet(chosenCircle, y));
// if the spell is not in the known spell list then it was newly added
if (!SpellIsWithinObject(nClass, nSpellbookID, nSpellLevel, knownSpells, oPC))
{
if (GetIsTruenamingClass(nClass))
{
string sFile = GetClassSpellbookFile(nClass);
// find out what lexicon it belongs to and add it to that
int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", nSpellbookID));
AddUtteranceKnown(oPC, nClass, nSpellbookID, lexicon, TRUE, GetHitDice(oPC));
}
if (GetIsShadowMagicClass(nClass))
AddMysteryKnown(oPC, nClass, nSpellbookID, TRUE, GetHitDice(oPC));
if (GetIsInvocationClass(nClass))
{
string sFile = GetClassSpellbookFile(nClass);
string spellId = Get2DACache(sFile, "SpellID", nSpellbookID);
int chosenList = nClass;
json expList = GetExpandedChoicesList(nClass, oPC);
// if the invocation belongs to the extra or epic extra list
// then we need to provide those list ids instead.
if (JsonObjectGet(expList, spellId) != JsonNull())
chosenList = INVOCATION_LIST_EXTRA;
expList = GetEpicExpandedChoicesList(nClass, oPC);
if (JsonObjectGet(expList, spellId) != JsonNull())
chosenList = INVOCATION_LIST_EXTRA_EPIC;
AddInvocationKnown(oPC, chosenList, nSpellbookID, TRUE, GetHitDice(oPC));
}
if (GetIsBladeMagicClass(nClass))
{
string sFile = GetClassSpellbookFile(nClass);
int maneuverType = StringToInt(Get2DACache(sFile, "Type", nSpellbookID));
// we save our moves either to stance or maneuever
if (maneuverType != MANEUVER_TYPE_STANCE)
maneuverType = MANEUVER_TYPE_MANEUVER;
AddManeuverKnown(oPC, nClass, nSpellbookID, maneuverType, TRUE, GetHitDice(oPC));
}
int nSpellbookType = GetSpellbookTypeForClass(nClass);
if (nSpellbookType == SPELLBOOK_TYPE_SPONTANEOUS
|| nClass == CLASS_TYPE_ARCHIVIST)
{
// these classes have their own syste,
if (nClass == CLASS_TYPE_BEGUILER
|| nClass == CLASS_TYPE_DREAD_NECROMANCER
|| nClass == CLASS_TYPE_WARMAGE)
{
int casterLevel = GetCasterLevelByClass(nClass, oPC);
// this is taken from prc_s_spellgain as it is coupled with the
// dynamic dialogue system
int advancedLearning = 0;
// beguilers learn every 4th level starting on 3rd
if (nClass == CLASS_TYPE_BEGUILER)
advancedLearning = ((casterLevel+1)/4);
// dread learns every 4th level
if (nClass == CLASS_TYPE_DREAD_NECROMANCER)
advancedLearning = (casterLevel/4);
if (nClass == CLASS_TYPE_WARMAGE)
{
if (casterLevel >= 3)
advancedLearning++;
}
if (advancedLearning)
{
// incremenet the total advanced learning known
int nAdvLearn = GetPersistantLocalInt(oPC, "AdvancedLearning_"+IntToString(nClass));
nAdvLearn++;
SetPersistantLocalInt(oPC, "AdvancedLearning_"+IntToString(nClass), nAdvLearn);
}
}
// get location of persistant storage on the hide
string sSpellbook = GetSpellsKnown_Array(nClass, nSpellLevel);
//object oToken = GetHideToken(oPC);
// Create spells known persistant array if it is missing
int nSize = persistant_array_get_size(oPC, sSpellbook);
if (nSize < 0)
{
persistant_array_create(oPC, sSpellbook);
nSize = 0;
}
// Mark the spell as known (e.g. add it to the end of oPCs spellbook)
persistant_array_set_int(oPC, sSpellbook, nSize, nSpellbookID);
if (nSpellbookType == SPELLBOOK_TYPE_SPONTANEOUS)
{
// add spell
string sFile = GetClassSpellbookFile(nClass);
string sArrayName = "NewSpellbookMem_" + IntToString(nClass);
int featId = StringToInt(Get2DACache(sFile, "FeatID", nSpellbookID));
int ipFeatID = StringToInt(Get2DACache(sFile, "IPFeatID", nSpellbookID));
AddSpellUse(oPC, nSpellbookID, nClass, sFile, sArrayName, nSpellbookType, GetPCSkin(oPC), featId, ipFeatID);
}
}
}
}
}
}
int EnableChosenButton(int nClass, int spellbookId, int circleLevel, object oPC=OBJECT_SELF)
{
if (GetIsPsionicClass(nClass)
|| GetIsShadowMagicClass(nClass)
|| GetIsTruenamingClass(nClass)
|| nClass == CLASS_TYPE_DREAD_NECROMANCER
|| nClass == CLASS_TYPE_BEGUILER
|| nClass == CLASS_TYPE_WARMAGE
|| nClass == CLASS_TYPE_ARCHIVIST)
{
json knownSpells = GetKnownSpellListObject(nClass, oPC);
json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel));
int totalSpells = JsonGetLength(currentCircle);
int i;
for (i = 0; i < totalSpells; i++)
{
// if spell belongs to known spells, then disable, we don't allow
// replacing for these classes.
int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i));
if (currentSpellbookId == spellbookId)
return FALSE;
}
}
if (GetIsBladeMagicClass(nClass))
{
string sFile = GetClassSpellbookFile(nClass);
int prereqs = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId));
string discipline = Get2DACache(sFile, "Discipline", spellbookId);
// if the maneuver is required for others to exist, t hen disable it
if (IsRequiredForOtherManeuvers(nClass, prereqs, discipline, oPC))
return FALSE;
// if it is required for a PRC to exist, then disable it.
if (IsRequiredForToBPRCClass(nClass, spellbookId, oPC))
return FALSE;
}
if (GetIsInvocationClass(nClass))
{
// dragon Shamans can't replace
if (nClass == CLASS_TYPE_DRAGON_SHAMAN)
{
json invokKnown = GetSpellIDsKnown(nClass, oPC);
string sFile = GetClassSpellbookFile(nClass);
string spellId = Get2DACache(sFile, "SpellID", spellbookId);
json chosenSpell = JsonObjectGet(invokKnown, spellId);
if (chosenSpell != JsonNull())
return FALSE;
}
}
// If we do not use the bioware unlearn system, we follow PnP
if (!GetPRCSwitch(PRC_BIO_UNLEARN))
{
json knownSpells = GetKnownSpellListObject(nClass, oPC);
json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel));
int totalSpells = JsonGetLength(currentCircle);
int i;
for (i = 0; i < totalSpells; i++)
{
int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i));
// if the spell belongs to the known spell list, then we need to determine
if (currentSpellbookId == spellbookId)
{
json unlearnList = GetChosenReplaceListObject(oPC);
int totalUnlearned = JsonGetLength(unlearnList);
int totalAllowed = GetPRCSwitch(PRC_UNLEARN_SPELL_MAXNR);
// we default to 1 if no max number of unlearns is set
if (!totalAllowed)
totalAllowed = 1;
// we cannot replace a spell if we have more than or equal to the
// amount of relearns allowed, therefore disable.
return totalUnlearned < totalAllowed;
}
}
}
return TRUE;
}
void ResetChoices(object oPC=OBJECT_SELF)
{
// reset choices made so far
DeleteLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR);
DeleteLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR);
DeleteLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR);
DeleteLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR);
DeleteLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR);
DeleteLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR);
}
void RemoveSpellKnown(int nClass, int spellbookId, object oPC=OBJECT_SELF, int nList=0)
{
string sBase;
string levelArrayBaseId;
string generalArrayBaseId;
string totalCountId;
string sFile = GetClassSpellbookFile(nClass);
int chosenList = (nList != 0) ? nList : nClass;
int spellID = StringToInt(Get2DACache(sFile, "SpellID", spellbookId));
// if statements are to change the location of the spellbook we are grabbing
if (GetIsShadowMagicClass(nClass))
{
sBase = _MYSTERY_LIST_NAME_BASE + IntToString(chosenList);
levelArrayBaseId = _MYSTERY_LIST_LEVEL_ARRAY;
generalArrayBaseId = _MYSTERY_LIST_GENERAL_ARRAY;
totalCountId = _MYSTERY_LIST_TOTAL_KNOWN;
}
if (GetIsInvocationClass(nClass))
{
sBase = _INVOCATION_LIST_NAME_BASE + IntToString(chosenList);
levelArrayBaseId = _INVOCATION_LIST_LEVEL_ARRAY;
generalArrayBaseId = _INVOCATION_LIST_GENERAL_ARRAY;
totalCountId = _INVOCATION_LIST_TOTAL_KNOWN;
}
if (GetIsBladeMagicClass(nClass))
{
int maneuverType = StringToInt(Get2DACache(sFile, "Type", spellbookId));
if (maneuverType != MANEUVER_TYPE_STANCE) maneuverType = MANEUVER_TYPE_MANEUVER;
sBase = _MANEUVER_LIST_NAME_BASE + IntToString(chosenList) + IntToString(maneuverType);
levelArrayBaseId = _MANEUVER_LIST_LEVEL_ARRAY;
generalArrayBaseId = _MANEUVER_LIST_GENERAL_ARRAY;
totalCountId = _MANEUVER_LIST_TOTAL_KNOWN;
}
if (GetIsTruenamingClass(nClass))
{
string lexicon = Get2DACache(sFile, "Lexicon", spellbookId);
sBase = _UTTERANCE_LIST_NAME_BASE + IntToString(chosenList) + lexicon;
levelArrayBaseId = _UTTERANCE_LIST_LEVEL_ARRAY;
generalArrayBaseId = _UTTERANCE_LIST_GENERAL_ARRAY;
totalCountId = _UTTERANCE_LIST_TOTAL_KNOWN;
}
string sTestArray;
int found = FALSE;
int i;
for (i = 1; i <= GetHitDice(oPC); i++)
{
sTestArray = sBase + levelArrayBaseId + IntToString(i);
if (persistant_array_exists(oPC, sTestArray))
{
// if we found the spell, then we remove it.
if (persistant_array_extract_int(oPC, sTestArray, spellID) >= 0)
{
found = TRUE;
break;
}
}
}
if (!found)
{
// if not found we check the general list where spells aren't set to a level.
sTestArray = sBase + generalArrayBaseId;
if (persistant_array_exists(oPC, sTestArray))
{
//if we could not find the spell here, something went wrong
if (persistant_array_extract_int(oPC, sTestArray, spellID) < 0)
{
SendMessageToPC(oPC, "Could not find spellID " + IntToString(spellID) + " in the class's spellbook!");
return;
}
}
}
// decrement the amount of spells known.
SetPersistantLocalInt(oPC, sBase + totalCountId,
GetPersistantLocalInt(oPC, sBase + totalCountId) - 1
);
// if ToB we need to decrement the specific discipline as well.
if (GetIsBladeMagicClass(nClass))
{
int maneuverType = StringToInt(Get2DACache(sFile, "Type", spellbookId));
if (maneuverType == MANEUVER_TYPE_BOOST
|| maneuverType == MANEUVER_TYPE_COUNTER
|| maneuverType == MANEUVER_TYPE_STRIKE
|| maneuverType == MANEUVER_TYPE_MANEUVER)
maneuverType = MANEUVER_TYPE_MANEUVER;
string sDisciplineArray = _MANEUVER_LIST_DISCIPLINE + IntToString(maneuverType) + "_" + Get2DACache(sFile, "Discipline", spellbookId);
SetPersistantLocalInt(oPC, sDisciplineArray,
GetPersistantLocalInt(oPC, sDisciplineArray) - 1);
}
// remove spell from player
int ipFeatID = StringToInt(Get2DACache(sFile, "IPFeatID", spellbookId));
itemproperty ipFeat = PRCItemPropertyBonusFeat(ipFeatID);
object oSkin = GetPCSkin(oPC);
RemoveItemProperty(oSkin, ipFeat);
CheckAndRemoveFeat(oSkin, ipFeat);
}
json GetSpellIDsKnown(int nClass, object oPC=OBJECT_SELF, int nList=0)
{
json spellIds = GetLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(nList));
if (spellIds == JsonNull())
spellIds = JsonObject();
else
return spellIds;
string sBase;
string levelArrayBaseId;
string generalArrayBaseId;
// if we are given a listId then use that instead, used for extra choices and
// expanded knowledge
int chosenList = (nList != 0) ? nList : nClass;
// these if checks are for setting class specific ids
if (nClass == CLASS_TYPE_DRAGON_SHAMAN
|| nClass == CLASS_TYPE_DRAGONFIRE_ADEPT
|| nClass == CLASS_TYPE_WARLOCK)
{
sBase = _INVOCATION_LIST_NAME_BASE + IntToString(chosenList);
levelArrayBaseId = _INVOCATION_LIST_LEVEL_ARRAY;
generalArrayBaseId = _INVOCATION_LIST_GENERAL_ARRAY;
}
if (GetIsPsionicClass(nClass))
{
sBase = _POWER_LIST_NAME_BASE + IntToString(chosenList);
levelArrayBaseId = _POWER_LIST_LEVEL_ARRAY;
generalArrayBaseId = _POWER_LIST_GENERAL_ARRAY;
}
// go through the level list and translate the spellIds into a JSON Object
// structure for easier access.
int i;
for (i = 1; i <= GetHitDice(oPC); i++)
{
string sTestArray = sBase + levelArrayBaseId + IntToString(i);
if (persistant_array_exists(oPC, sTestArray))
{
int nSize = persistant_array_get_size(oPC, sTestArray);
int j;
for (j = 0; j < nSize; j++)
{
spellIds = JsonObjectSet(spellIds, IntToString(persistant_array_get_int(oPC, sTestArray, j)), JsonBool(TRUE));
}
}
}
// go through the general list and translate the spellIds into a JSON Object
// structure for easier access.
string sTestArray = sBase + generalArrayBaseId;
if (persistant_array_exists(oPC, sTestArray))
{
int nSize = persistant_array_get_size(oPC, sTestArray);
int j;
for (j = 0; j < nSize; j++)
{
spellIds = JsonObjectSet(spellIds, IntToString(persistant_array_get_int(oPC, sTestArray, j)), JsonBool(TRUE));
}
}
SetLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(nList), spellIds);
return spellIds;
}
string ReasonForDisabledSpell(int nClass, int spellbookId, object oPC=OBJECT_SELF)
{
string sFile = GetClassSpellbookFile(nClass);
int circleLevel = StringToInt(Get2DACache(sFile, "Level", spellbookId));
// logic for psionics
if (GetIsPsionicClass(nClass))
{
int maxLevel = GetMaxPowerLevelForClass(nClass, oPC);
if (circleLevel > maxLevel)
return "You are unable to learn at this level currently.";
// if its an expanded knowledge choice and we have already made all our
// exp knowledge choices then it needs to be disabled.
if (IsExpKnowledgePower(nClass, spellbookId))
{
int remainingExp = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC)
+ GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC);
if (!remainingExp)
return "You have no more expanded knowledge choices left.";
}
}
if (GetIsShadowMagicClass(nClass))
{
// mysteries are weird, the circles are sectioned by 1-3, 4-6, 7-9
// if you do not have at least 2 choices from a circle you can't progress up
// so you can have access to circles 1,2,4,7,8
int nType = 1;
if (circleLevel >= 4 && circleLevel <= 6)
nType = 2;
if (circleLevel >= 7 && circleLevel <= 9)
nType = 3;
int maxPossibleCircle = GetMaxMysteryLevelLearnable(oPC, nClass, nType);
if (circleLevel > maxPossibleCircle)
return "You are unable to learn at this level currently.";
}
if (GetIsTruenamingClass(nClass))
{
int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", spellbookId));
// each lexicon learns at different rates
int maxCircle = GetLexiconCircleKnownAtLevel(GetLevelByClass(nClass, oPC), lexicon);
if (circleLevel > maxCircle)
return "You are unable to learn at this level currently.";
if (GetRemainingTruenameChoices(nClass, lexicon, oPC))
return "";
return "You have made all your truenaming choices.";
}
// logic for ToB
if (GetIsBladeMagicClass(nClass))
{
if (circleLevel > GetMaxInitiatorCircle(nClass, oPC))
return "You are unable to learn at this level currently.";
// if you do not have the prerequisite amount of maneuevers to learn
// the maneuever, then you can't learn it.
if (!HasPreRequisitesForManeuver(nClass, spellbookId, oPC))
return "You do not have the prerequisites for this maneuver.";
// maneuvers and stances have their own seperate limits
int type = StringToInt(Get2DACache(sFile, "Type", spellbookId));
if (type == MANEUVER_TYPE_BOOST
|| type == MANEUVER_TYPE_COUNTER
|| type == MANEUVER_TYPE_STRIKE
|| type == MANEUVER_TYPE_MANEUVER)
{
int remainingMan = GetRemainingManeuverChoices(nClass, oPC);
if (remainingMan)
return "";
return "You have made all your maneuver choices.";
}
if (type == MANEUVER_TYPE_STANCE)
{
int remainingStance = GetRemainingStanceChoices(nClass, oPC);
if (remainingStance)
return "";
return "You have made all your stance choices.";
}
}
// default logic
// determine remaining Spell/Power choices left for player, if there is any
// remaining, enable the buttons.
if (GetRemainingSpellChoices(nClass, circleLevel, oPC))
return "";
return "You have made all your spell choices.";
}
string ReasonForDisabledChosen(int nClass, int spellbookId, object oPC=OBJECT_SELF)
{
string sFile = GetClassSpellbookFile(nClass);
int circleLevel = StringToInt(Get2DACache(sFile, "Level", spellbookId));
if (GetIsPsionicClass(nClass)
|| GetIsShadowMagicClass(nClass)
|| GetIsTruenamingClass(nClass)
|| nClass == CLASS_TYPE_DREAD_NECROMANCER
|| nClass == CLASS_TYPE_BEGUILER
|| nClass == CLASS_TYPE_WARMAGE)
{
json knownSpells = GetKnownSpellListObject(nClass, oPC);
json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel));
int totalSpells = JsonGetLength(currentCircle);
int i;
for (i = 0; i < totalSpells; i++)
{
// if spell belongs to known spells, then disable, we don't allow
// replacing for these classes.
int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i));
if (currentSpellbookId == spellbookId)
return "You cannot replace spells as this class.";
}
}
if (GetIsBladeMagicClass(nClass))
{
int prereqs = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId));
string discipline = Get2DACache(sFile, "Discipline", spellbookId);
// if the maneuver is required for others to exist, t hen disable it
if (IsRequiredForOtherManeuvers(nClass, prereqs, discipline, oPC))
return "This maneuver is required for another maneuver.";
// if it is required for a PRC to exist, then disable it.
if (IsRequiredForToBPRCClass(nClass, spellbookId, oPC))
return "This maneuver is reuquired for a PRC class.";
}
if (GetIsInvocationClass(nClass))
{
// dragon Shamans can't replace
if (nClass == CLASS_TYPE_DRAGON_SHAMAN)
{
json invokKnown = GetSpellIDsKnown(nClass, oPC);
string spellId = Get2DACache(sFile, "SpellID", spellbookId);
json chosenSpell = JsonObjectGet(invokKnown, spellId);
if (chosenSpell != JsonNull())
return "You cannot replace invocations as this class.";
}
}
// If we do not use the bioware unlearn system, we follow PnP
if (!GetPRCSwitch(PRC_BIO_UNLEARN))
{
json knownSpells = GetKnownSpellListObject(nClass, oPC);
json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel));
int totalSpells = JsonGetLength(currentCircle);
int i;
for (i = 0; i < totalSpells; i++)
{
int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i));
// if the spell belongs to the known spell list, then we need to determine
if (currentSpellbookId == spellbookId)
{
json unlearnList = GetChosenReplaceListObject(oPC);
int totalUnlearned = JsonGetLength(unlearnList);
int totalAllowed = GetPRCSwitch(PRC_UNLEARN_SPELL_MAXNR);
// we default to 1 if no max number of unlearns is set
if (!totalAllowed)
totalAllowed = 1;
// we cannot replace a spell if we have more than or equal to the
// amount of relearns allowed, therefore disable.
if (totalUnlearned < totalAllowed)
return "";
return "You can only replace " + IntToString(totalAllowed) + " spells during level up.";
}
}
}
return "";
}
json GetExpandedChoicesList(int nClass, object oPC=OBJECT_SELF)
{
json expandedChoices = GetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR);
if (expandedChoices != JsonNull())
return expandedChoices;
if (GetIsPsionicClass(nClass))
expandedChoices = GetSpellIDsKnown(nClass, oPC, POWER_LIST_EXP_KNOWLEDGE);
else
expandedChoices = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA);
SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expandedChoices);
return expandedChoices;
}
int GetRemainingExpandedChoices(int nClass, int nList, object oPC=OBJECT_SELF)
{
int remainingChoices = 0;
json expandedList = (nList == INVOCATION_LIST_EXTRA || nList == POWER_LIST_EXP_KNOWLEDGE ) ? GetExpandedChoicesList(nClass, oPC)
: GetEpicExpandedChoicesList(nClass, oPC);
int expChoicesCount = JsonGetLength(JsonObjectKeys(expandedList));
int maxExpChoices = (GetIsPsionicClass(nClass)) ? GetMaxPowerCount(oPC, nList)
: GetMaxInvocationCount(oPC, nList);
remainingChoices += (maxExpChoices - expChoicesCount);
return remainingChoices;
}
json GetEpicExpandedChoicesList(int nClass, object oPC=OBJECT_SELF)
{
json expandedChoices = GetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR);
if (expandedChoices != JsonNull())
return expandedChoices;
if (GetIsPsionicClass(nClass))
expandedChoices = GetSpellIDsKnown(nClass, oPC, POWER_LIST_EPIC_EXP_KNOWLEDGE);
else
expandedChoices = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA_EPIC);
SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expandedChoices);
return expandedChoices;
}
int IsSpellInExpandedChoices(int nClass, int nList, int spellId, object oPC=OBJECT_SELF)
{
json expList = (nList == POWER_LIST_EXP_KNOWLEDGE || nList == INVOCATION_LIST_EXTRA) ? GetExpandedChoicesList(nClass, oPC)
: GetEpicExpandedChoicesList(nClass, oPC);
return (JsonObjectGet(expList, IntToString(spellId)) != JsonNull());
}
json GetChosenReplaceListObject(object oPC=OBJECT_SELF)
{
json replaceList = GetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR);
if (replaceList == JsonNull())
replaceList = JsonObject();
else
return replaceList;
SetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR, replaceList);
return replaceList;
}
////////////////////////////////////////////////////////////////////////////
/// ///
/// Psionics ///
/// ///
////////////////////////////////////////////////////////////////////////////
int IsExpKnowledgePower(int nClass, int spellbookId)
{
string sFile = GetClassSpellbookFile(nClass);
int isExp = StringToInt(Get2DACache(sFile, "Exp", spellbookId));
return isExp;
}
json GetCurrentPowerList(object oPC=OBJECT_SELF)
{
json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR);
if (retValue == JsonNull())
retValue = JsonArray();
else
return retValue;
SetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR, retValue);
return retValue;
}
int ShouldAddPower(int nClass, int spellbookId, object oPC=OBJECT_SELF)
{
string sFile = GetClassSpellbookFile(nClass);
int featId = StringToInt(Get2DACache(sFile, "FeatID", spellbookId));
int isExp = StringToInt(Get2DACache(sFile, "Exp", spellbookId));
// if you don't have the prereqs for a power then don't add it. Specific for
// psions
if (!CheckPowerPrereqs(featId, oPC))
return FALSE;
// if the power is a expanded knowledge power
if (isExp)
{
// and we have a expanded knowledge choice left to make then show
// the button
int addPower = FALSE;
int maxLevel = GetMaxPowerLevelForClass(nClass, oPC);
int currentCircle = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR);
int choicesLeft = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC);
if (choicesLeft && (currentCircle <= (maxLevel-1)))
addPower = TRUE;
choicesLeft = GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC);
if (choicesLeft)
addPower = TRUE;
// otherwise don't show the button.
return addPower;
}
return TRUE;
}
void LearnPowers(int nClass, object oPC=OBJECT_SELF)
{
// add normal powers
json powerList = GetCurrentPowerList(oPC);
int totalPowers = JsonGetLength(powerList);
int i;
for (i = 0; i < totalPowers; i++)
{
int nSpellbookID = JsonGetInt(JsonArrayGet(powerList, i));
// get the expanded knowledge list we are adding to if any
int expKnow = GetExpKnowledgePowerListRequired(nClass, nSpellbookID, oPC);
AddPowerKnown(oPC, nClass, nSpellbookID, TRUE, GetManifesterLevel(oPC, nClass, TRUE), expKnow);
}
}
int GetExpKnowledgePowerListRequired(int nClass, int spellbookId, object oPC=OBJECT_SELF)
{
string sFile = GetClassSpellbookFile(nClass);
int i;
// expanded knowledge is -1, epic epxanded knowledge is -2
for (i = -1; i >= -2; i--)
{
int spellId = StringToInt(Get2DACache(sFile, "SpellID", spellbookId));
if (IsSpellInExpandedChoices(nClass, i, spellId, oPC))
return i;
}
return 0;
}
int GetMaxPowerLevelForClass(int nClass, object oPC=OBJECT_SELF)
{
string sFile = GetAMSKnownFileName(nClass);
int nLevel = GetManifesterLevel(oPC, nClass, TRUE);
// index is level - 1 since it starts at 0.
int maxLevel = StringToInt(Get2DACache(sFile, "MaxPowerLevel", nLevel-1));
return maxLevel;
}
int GetRemainingPowerChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE)
{
int remaining = 0;
int maxLevel = GetMaxPowerLevelForClass(nClass, oPC);
if (chosenCircle > maxLevel)
return 0;
json choices = GetCurrentPowerList(oPC);
int totalChoices = JsonGetLength(choices);
int allowedChoices = GetMaxPowerCount(oPC, nClass);
int alreadyChosen = GetPowerCount(oPC, nClass);
string sFile = GetClassSpellbookFile(nClass);
int i = 0;
for (i = 0; i < totalChoices; i++)
{
int spellbookId = JsonGetInt(JsonArrayGet(choices, i));
int spellId = StringToInt(Get2DACache(sFile, "SpellID", spellbookId));
//if the power is a expanded knowledge choice, don't count it
if (!IsSpellInExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, spellId, oPC)
&& !IsSpellInExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, spellId, oPC))
remaining++;
}
remaining = (allowedChoices - remaining - alreadyChosen);
// if this is true we count expanded knowledge choices
if (extra)
{
if (GetHasFeat(FEAT_EXPANDED_KNOWLEDGE_1, oPC) && (chosenCircle <= (maxLevel-1)))
{
int totalExp = GetMaxPowerCount(oPC, POWER_LIST_EXP_KNOWLEDGE);
json expChoices = GetExpandedChoicesList(nClass, oPC);
int choicesCount = JsonGetLength(JsonObjectKeys(expChoices));
remaining += (totalExp - choicesCount);
}
if (GetHasFeat(FEAT_EPIC_EXPANDED_KNOWLEDGE_1, oPC))
{
int totalExp = GetMaxPowerCount(oPC, POWER_LIST_EPIC_EXP_KNOWLEDGE);
json expChoices = GetEpicExpandedChoicesList(nClass, oPC);
int choicesCount = JsonGetLength(JsonObjectKeys(expChoices));
remaining += (totalExp - choicesCount);
}
}
return remaining;
}
////////////////////////////////////////////////////////////////////////////
/// ///
/// Initiators ///
/// ///
////////////////////////////////////////////////////////////////////////////
json GetDisciplineInfoObject(int nClass, object oPC=OBJECT_SELF)
{
json disciplineInfo = GetLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(nClass));
if (disciplineInfo == JsonNull())
disciplineInfo = JsonObject();
else
return disciplineInfo;
int chosenClass = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR);
string sFile = GetClassSpellbookFile(nClass);
//if this is not the chosen class then we do not have a chosen spell list
// need to go through the class's 2da and check if you know the spell or not.
if (nClass != chosenClass)
{
int totalSpells = Get2DARowCount(sFile);
int i;
for (i = 0; i < totalSpells; i++)
{
int featId = StringToInt(Get2DACache(sFile, "FeatID", i));
if (featId && GetHasFeat(featId, oPC, TRUE))
disciplineInfo = AddSpellDisciplineInfo(sFile, i, disciplineInfo);
}
}
else
{
json chosenMans = GetChosenSpellListObject(nClass, oPC);
json circles = JsonObjectKeys(chosenMans);
int totalCircles = JsonGetLength(circles);
int i;
for (i = 0; i < totalCircles; i++)
{
string currentCircle = JsonGetString(JsonArrayGet(circles, i));
json currentList = JsonObjectGet(chosenMans, currentCircle);
int totalSpells = JsonGetLength(currentList);
int y;
for (y = 0; y < totalSpells; y++)
{
int spellbookId = JsonGetInt(JsonArrayGet(currentList, y));
disciplineInfo = AddSpellDisciplineInfo(sFile, spellbookId, disciplineInfo);
}
}
}
SetLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(nClass), disciplineInfo);
return disciplineInfo;
}
int IsRequiredForOtherManeuvers(int nClass, int prereq, string discipline, object oPC=OBJECT_SELF)
{
json discInfo = GetDisciplineInfoObject(nClass, oPC);
int total = 0;
// loop through each prereq level and add up it's totals (ie how many maneuevrs
// do we know with 0,1,2...,n prereqs.
int i;
for (i = 0; i <= prereq; i++)
{
json currDisc = JsonObjectGet(discInfo, discipline);
string discKey = ("Prereq_" + IntToString(i));
int currentDiscPrereq = JsonGetInt(JsonObjectGet(currDisc, discKey));
total += currentDiscPrereq;
}
// then from above the given prereq check if we have any prereq maneuevers taken
for (i = (prereq+1); i < NUI_LEVEL_UP_MANEUVER_PREREQ_LIMIT; i++)
{
json currDisc = JsonObjectGet(discInfo, discipline);
string discKey = ("Prereq_" + IntToString(i));
json discPrereq = JsonObjectGet(currDisc, discKey);
if (discPrereq != JsonNull())
{
// if we do take the total and subtract by one, if it is lower than
// than the prereq needed, it is required
if (total - 1 < i)
return TRUE;
// otherwise add how many we have and move up and keep trying.
int currentDiscPrereq = JsonGetInt(discPrereq);
total += currentDiscPrereq;
}
}
return FALSE;
}
int HasPreRequisitesForManeuver(int nClass, int spellbookId, object oPC=OBJECT_SELF)
{
string sFile = GetClassSpellbookFile(nClass);
int prereqs = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId));
if (!prereqs)
return TRUE;
string discipline = Get2DACache(sFile, "Discipline", spellbookId);
json discInfo = GetDisciplineInfoObject(nClass, oPC);
json chosenDisc = JsonObjectGet(discInfo, discipline);
if (chosenDisc != JsonNull())
{
int nManCount = JsonGetInt(JsonObjectGet(chosenDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
if (nManCount >= prereqs)
return TRUE;
}
return FALSE;
}
int GetMaxInitiatorCircle(int nClass, object oPC=OBJECT_SELF)
{
int initiatorLevel = GetInitiatorLevel(oPC, nClass);
// initiators learn by ceiling(classLevel)
int highestCircle = (initiatorLevel + 1) / 2;
if (highestCircle > 9)
return 9;
return highestCircle;
}
int GetRemainingManeuverChoices(int nClass, object oPC=OBJECT_SELF)
{
json discInfo = GetDisciplineInfoObject(nClass, oPC);
json jManAmount = JsonObjectGet(discInfo, NUI_LEVEL_UP_MANEUVER_TOTAL);
int nManAmount = 0;
if (jManAmount != JsonNull())
nManAmount = JsonGetInt(jManAmount);
int maxAmount = GetMaxManeuverCount(oPC, nClass, MANEUVER_TYPE_MANEUVER);
return maxAmount - nManAmount;
}
int GetRemainingStanceChoices(int nClass, object oPC=OBJECT_SELF)
{
json discInfo = GetDisciplineInfoObject(nClass, oPC);
json jStanceAmount = JsonObjectGet(discInfo, NUI_LEVEL_UP_STANCE_TOTAL);
int nStanceAmount = 0;
if (jStanceAmount != JsonNull())
nStanceAmount = JsonGetInt(jStanceAmount);
int maxAmount = GetMaxManeuverCount(oPC, nClass, MANEUVER_TYPE_STANCE);
return maxAmount - nStanceAmount;
}
int IsAllowedDiscipline(int nClass, int spellbookId, object oPC=OBJECT_SELF)
{
// logic carried over from private function in discipline inc functions
// uses bitwise matching to tell if the discipline is allowed or not
string sFile = GetClassSpellbookFile(nClass);
int discipline = StringToInt(Get2DACache(sFile, "Discipline", spellbookId));
int nOverride = GetPersistantLocalInt(oPC, "AllowedDisciplines");
if(nOverride == 0)
{
switch(nClass)
{
case CLASS_TYPE_CRUSADER: nOverride = 322; break;//DISCIPLINE_DEVOTED_SPIRIT + DISCIPLINE_STONE_DRAGON + DISCIPLINE_WHITE_RAVEN
case CLASS_TYPE_SWORDSAGE: nOverride = 245; break;//DISCIPLINE_DESERT_WIND + DISCIPLINE_DIAMOND_MIND + DISCIPLINE_SETTING_SUN + DISCIPLINE_SHADOW_HAND + DISCIPLINE_STONE_DRAGON + DISCIPLINE_TIGER_CLAW
case CLASS_TYPE_WARBLADE: nOverride = 460; break;//DISCIPLINE_DIAMOND_MIND + DISCIPLINE_IRON_HEART + DISCIPLINE_STONE_DRAGON + DISCIPLINE_TIGER_CLAW + DISCIPLINE_WHITE_RAVEN
}
}
return nOverride & discipline;
}
int IsRequiredForToBPRCClass(int nClass, int spellbookId, object oPC=OBJECT_SELF)
{
int currentClassPos = 1;
// loop through all the classes and look for a PRC
while (currentClassPos)
{
int currentClass = GetClassByPosition(currentClassPos, oPC);
// if we reached a non existant class, we reached the end.
if (currentClass != CLASS_TYPE_INVALID)
{
string sFile = GetClassSpellbookFile(nClass);
int discipline = StringToInt(Get2DACache(sFile, "Discipline", spellbookId));
// check if the class is a ToB PRC Class and if the current spell's
// discipline is used for it.
int isUsed = FALSE;
if (currentClass == CLASS_TYPE_DEEPSTONE_SENTINEL
&& (discipline == DISCIPLINE_STONE_DRAGON))
isUsed = TRUE;
if (currentClass == CLASS_TYPE_BLOODCLAW_MASTER
&& (discipline == DISCIPLINE_TIGER_CLAW))
isUsed = TRUE;
if (currentClass == CLASS_TYPE_RUBY_VINDICATOR
&& (discipline == DISCIPLINE_DEVOTED_SPIRIT))
isUsed = TRUE;
if (currentClass == CLASS_TYPE_JADE_PHOENIX_MAGE)
isUsed = TRUE;
if (currentClass == CLASS_TYPE_MASTER_OF_NINE)
isUsed = TRUE;
if (currentClass == CLASS_TYPE_ETERNAL_BLADE
&& (discipline == DISCIPLINE_DEVOTED_SPIRIT
|| discipline == DISCIPLINE_DIAMOND_MIND))
isUsed = TRUE;
if (currentClass == CLASS_TYPE_SHADOW_SUN_NINJA
&& (discipline == DISCIPLINE_SETTING_SUN
|| discipline == DISCIPLINE_SHADOW_HAND))
isUsed = TRUE;
// if any of the above was true than we need to check if this spell
// is required for a PRC
if (isUsed)
{
// get the discipline info for all BladeMagic classes if we have them.
json discInfo = GetDisciplineInfoObject(nClass, oPC);
json classDisc2Info = JsonObject();
json classDisc3Info = JsonObject();
if (nClass == CLASS_TYPE_SWORDSAGE)
{
if (GetLevelByClass(CLASS_TYPE_WARBLADE, oPC))
classDisc2Info = GetDisciplineInfoObject(CLASS_TYPE_WARBLADE, oPC);
if (GetLevelByClass(CLASS_TYPE_CRUSADER, oPC))
classDisc3Info = GetDisciplineInfoObject(CLASS_TYPE_CRUSADER, oPC);
}
if (nClass == CLASS_TYPE_CRUSADER)
{
if (GetLevelByClass(CLASS_TYPE_WARBLADE, oPC))
classDisc2Info = GetDisciplineInfoObject(CLASS_TYPE_WARBLADE, oPC);
if (GetLevelByClass(CLASS_TYPE_SWORDSAGE, oPC))
classDisc3Info = GetDisciplineInfoObject(CLASS_TYPE_SWORDSAGE, oPC);
}
if (nClass == CLASS_TYPE_WARBLADE)
{
if (GetLevelByClass(CLASS_TYPE_CRUSADER, oPC))
classDisc2Info = GetDisciplineInfoObject(CLASS_TYPE_CRUSADER, oPC);
if (GetLevelByClass(CLASS_TYPE_SWORDSAGE, oPC))
classDisc3Info = GetDisciplineInfoObject(CLASS_TYPE_SWORDSAGE, oPC);
}
// Time to begin checking the PRCs
// this should follow the same logic as here
// https://gitea.raptio.us/Jaysyn/PRC8/src/commit/797442d3da7c9c8e1fcf585b97e2ff1cbe56045b/nwn/nwnprc/trunk/scripts/prc_prereq.nss#L991
// Check Deepstone Sentinel
if (currentClass == CLASS_TYPE_DEEPSTONE_SENTINEL)
{
// we need to look for 2 Stone Dragon Maneuvers and 1 Stone Dragon
// Stance. So add up the other MagicBlade classes and see if it is satisfied.
int stoneDMan, stoneDStance = 0;
json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline));
if (currentDisc != JsonNull())
{
stoneDMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
stoneDStance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
if (stoneDMan >= 2 && stoneDStance >= 1)
return FALSE;
}
currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline));
if (currentDisc != JsonNull())
{
stoneDMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
stoneDStance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
if (stoneDMan >= 2 && stoneDStance >= 1)
return FALSE;
}
// if it still isn't satisfied than the current class is required
// for it to exist. Check to see if it is safe to remove the maneuever
currentDisc = JsonObjectGet(discInfo, IntToString(discipline));
if (currentDisc != JsonNull())
{
stoneDMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
stoneDStance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
int type = StringToInt(Get2DACache(sFile, "Type", spellbookId));
//if the current maneuver is a stance, check to see if it is safe to remove
if (type == MANEUVER_TYPE_STANCE)
{
if (stoneDStance - 1 >= 1)
return FALSE;
}
else
{
// if it is not a stance we can just check the maneuevers in general
if (stoneDMan - 1 >= 2)
return FALSE;
}
// this maneuver is required and should not be removed.
return TRUE;
}
}
// Check Bloodclaw Master
if (currentClass == CLASS_TYPE_BLOODCLAW_MASTER)
{
// bloodclaw needs 3 Tiger Claw maneuevers
int tigerCMan = 0;
json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline));
if (currentDisc != JsonNull())
{
tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
if (tigerCMan >= 3)
return FALSE;
}
currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline));
if (currentDisc != JsonNull())
{
tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
if (tigerCMan >= 3)
return FALSE;
}
currentDisc = JsonObjectGet(discInfo, IntToString(discipline));
if (currentDisc != JsonNull())
{
tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
if (tigerCMan-1 >= 3)
return FALSE;
return TRUE;
}
}
if (currentClass == CLASS_TYPE_RUBY_VINDICATOR)
{
// Ruby Vindicator needs 1 stance and 1 maneuever from Devoted Spirit
int stance = 0;
int maneuver = 0;
json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline));
if (currentDisc != JsonNull())
{
stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
if (stance >= 1 && maneuver >= 1)
return FALSE;
}
currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline));
if (currentDisc != JsonNull())
{
stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
if (stance >= 1 && maneuver >= 1)
return FALSE;
}
currentDisc = JsonObjectGet(discInfo, IntToString(discipline));
if (currentDisc != JsonNull())
{
stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
int type = StringToInt(Get2DACache(sFile, "Type", spellbookId));
if (type == MANEUVER_TYPE_STANCE)
{
if (stance - 1 >= 1)
return FALSE;
return TRUE;
}
if (maneuver - 1 >= 1)
return FALSE;
return TRUE;
}
}
if (currentClass == CLASS_TYPE_JADE_PHOENIX_MAGE)
{
// Jade Phoenix needs 1 stance and 2 maneuvers of any type
int stance = 0;
int maneuver = 0;
json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline));
if (currentDisc != JsonNull())
{
stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
if (stance >= 1 && maneuver >= 2)
return FALSE;
}
currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline));
if (currentDisc != JsonNull())
{
stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
if (stance >= 1 && maneuver >= 2)
return FALSE;
}
currentDisc = JsonObjectGet(discInfo, IntToString(discipline));
if (currentDisc != JsonNull())
{
stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
int type = StringToInt(Get2DACache(sFile, "Type", spellbookId));
if (type == MANEUVER_TYPE_STANCE)
{
if ((stance - 1 >= 1) && (maneuver -1 >= 2))
return FALSE;
return TRUE;
}
if (maneuver - 1 >= 2)
return FALSE;
return TRUE;
}
}
if (currentClass == CLASS_TYPE_MASTER_OF_NINE)
{
// master of nine needs 1 maneuever from 6 different disciplines
int totalDiscCount = 0;
int currentClassAndDiscUsed = 0;
int i;
// loop through each possible discipline
for (i = 0; i <= 256; i++)
{
int found = 0;
// only disciplines that exist are stored, and only those
// that are used are stored, so we can loop through and
// find what disciplines we do or don't know.
json currentDisc = JsonObjectGet(classDisc2Info, IntToString(i));
if (currentDisc != JsonNull())
found = 1;
if (!found)
{
json currentDisc = JsonObjectGet(classDisc3Info, IntToString(i));
if (currentDisc != JsonNull())
found = 1;
}
if (!found)
{
json currentDisc = JsonObjectGet(discInfo, IntToString(i));
if (currentDisc != JsonNull())
{
if (i == discipline)
currentClassAndDiscUsed = 1;
found = 1;
}
}
totalDiscCount += found;
}
// if we have more maneuevers than 6, it is not required
if (totalDiscCount > 6)
return FALSE;
// however if we have 6 and this discipline was grabbed we need to make sure it is safe to remove
if (currentClassAndDiscUsed)
{
// if we were to remove this discipline and it is 5 or less total disciplines we have now
// it is important
if (totalDiscCount - 1 >= 6)
return FALSE;
json currentDisc = JsonObjectGet(discInfo, IntToString(discipline));
int stance = JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
int maneuver = JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
// if we were to remove this discipline and are left with no more than
// this was important and it can't be removed
if ((stance + maneuver - 1) >= 1)
return FALSE;
return TRUE;
}
return FALSE;
}
if (currentClass == CLASS_TYPE_ETERNAL_BLADE)
{
//Eternal blade 2 Devoted Spirits OR 2 Diamond Mind
int nTotal = 0;
json currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_DEVOTED_SPIRIT));
if (currentDisc != JsonNull())
{
nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
// stances here count as a maneuver so we need to count 2
// to account for that
if (nTotal >= 2)
return FALSE;
}
currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_DIAMOND_MIND));
if (currentDisc != JsonNull())
{
nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
// stances here count as a maneuver so we need to count 2
// to account for that
if (nTotal >= 2)
return FALSE;
}
currentDisc = JsonObjectGet(classDisc3Info, IntToString(DISCIPLINE_DEVOTED_SPIRIT));
if (currentDisc != JsonNull())
{
nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
// stances here count as a maneuver so we need to count 2
// to account for that
if (nTotal >= 2)
return FALSE;
}
currentDisc = JsonObjectGet(classDisc3Info, IntToString(DISCIPLINE_DIAMOND_MIND));
if (currentDisc != JsonNull())
{
nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
// stances here count as a maneuver so we need to count 2
// to account for that
if (nTotal >= 2)
return FALSE;
}
currentDisc = JsonObjectGet(discInfo, IntToString(discipline));
if (currentDisc != JsonNull())
{
nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
if ((nTotal - 1) >= 2)
return FALSE;
return TRUE;
}
}
if (currentClass == CLASS_TYPE_SHADOW_SUN_NINJA)
{
// Shadow Sun Ninja needs 1 lvl2 Setting Sun OR Shadow Hand maneuever
// 1 Setting Sun maneuver AND 1 Shadow Hand maneuver
int nLvl2 = 0;
int shadowHTotal;
int settingSTotal;
json currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_SHADOW_HAND));
if (currentDisc != JsonNull())
{
shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER)));
// stances here count as a maneuver so we need to count 2
// to account for that
if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2))
return FALSE;
}
currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_SETTING_SUN));
if (currentDisc != JsonNull())
{
settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER)));
// stances here count as a maneuver so we need to count 2
// to account for that
if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2))
return FALSE;
}
currentDisc = JsonObjectGet(classDisc3Info, IntToString(DISCIPLINE_SHADOW_HAND));
if (currentDisc != JsonNull())
{
shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER)));
// stances here count as a maneuver so we need to count 2
// to account for that
if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2))
return FALSE;
}
currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_SETTING_SUN));
if (currentDisc != JsonNull())
{
settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER)));
// stances here count as a maneuver so we need to count 2
// to account for that
if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2))
return FALSE;
}
currentDisc = JsonObjectGet(discInfo, IntToString(DISCIPLINE_SHADOW_HAND));
if (currentDisc != JsonNull())
{
shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER)));
}
currentDisc = JsonObjectGet(discInfo, IntToString(DISCIPLINE_SETTING_SUN));
if (currentDisc != JsonNull())
{
settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE)));
settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE)));
nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER)));
}
int level = StringToInt(Get2DACache(sFile, "Level", spellbookId));
if (level == 2)
nLvl2 -= 1;
if (discipline == DISCIPLINE_SHADOW_HAND)
shadowHTotal -= 1;
else
settingSTotal -= 1;
return !(nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2));
}
}
currentClassPos += 1;
}
else
currentClassPos = 0;
}
return FALSE;
}
json AddSpellDisciplineInfo(string sFile, int spellbookId, json classDisc)
{
json classDiscCopy = classDisc;
int discipline = StringToInt(Get2DACache(sFile, "Discipline", spellbookId));
int type = StringToInt(Get2DACache(sFile, "Type", spellbookId));
int level = StringToInt(Get2DACache(sFile, "Level", spellbookId));
int prereq = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId));
json jDisc = JsonObjectGet(classDisc, IntToString(discipline));
if (jDisc == JsonNull())
jDisc = JsonObject();
string levelKey = "Level" + IntToString(level) + "_" + IntToString(type);
int nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, levelKey)) + 1);
jDisc = JsonObjectSet(jDisc, levelKey, JsonInt(nTypeTotal));
nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, IntToString(type))) + 1);
jDisc = JsonObjectSet(jDisc, IntToString(type), JsonInt(nTypeTotal));
if (type != MANEUVER_TYPE_MANEUVER
&& type != MANEUVER_TYPE_STANCE)
{
levelKey = "Level" + IntToString(level) + "_" + IntToString(MANEUVER_TYPE_MANEUVER);
nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, levelKey)) + 1);
jDisc = JsonObjectSet(jDisc, levelKey, JsonInt(nTypeTotal));
nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, IntToString(MANEUVER_TYPE_MANEUVER))) + 1);
jDisc = JsonObjectSet(jDisc, IntToString(MANEUVER_TYPE_MANEUVER), JsonInt(nTypeTotal));
}
string prereqKey = "Prereq_" + IntToString(prereq);
int nPrereqTotal = (JsonGetInt(JsonObjectGet(jDisc, prereqKey)) + 1);
jDisc = JsonObjectSet(jDisc, prereqKey, JsonInt(nPrereqTotal));
if (type == MANEUVER_TYPE_STANCE)
{
nTypeTotal = (JsonGetInt(JsonObjectGet(classDisc, NUI_LEVEL_UP_STANCE_TOTAL)) + 1);
classDiscCopy = JsonObjectSet(classDiscCopy, NUI_LEVEL_UP_STANCE_TOTAL, JsonInt(nTypeTotal));
}
else
{
nTypeTotal = (JsonGetInt(JsonObjectGet(classDisc, NUI_LEVEL_UP_MANEUVER_TOTAL)) + 1);
classDiscCopy = JsonObjectSet(classDiscCopy, NUI_LEVEL_UP_MANEUVER_TOTAL, JsonInt(nTypeTotal));
}
return JsonObjectSet(classDiscCopy, IntToString(discipline), jDisc);
}
////////////////////////////////////////////////////////////////////////////
/// ///
/// Invokers ///
/// ///
////////////////////////////////////////////////////////////////////////////
json GetInvokerKnownListObject(int nClass, object oPC=OBJECT_SELF)
{
json knownObject = GetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_INVOCATIONS_CACHE_VAR + IntToString(nClass));
if (knownObject == JsonNull())
knownObject = JsonObject();
else
return knownObject;
string sFile = GetAMSKnownFileName(nClass);
int totalRows = Get2DARowCount(sFile);
int maxInvocLevel = StringToInt(Get2DACache(sFile, "MaxInvocationLevel", totalRows-1));
json previousInvocList = JsonObject();
int i;
for (i = 1; i <= maxInvocLevel; i++)
{
previousInvocList = JsonObjectSet(previousInvocList, IntToString(i), JsonInt(0));
}
for (i = 0; i < totalRows; i++)
{
int maxInvocation = StringToInt(Get2DACache(sFile, "MaxInvocationLevel", i));
int invocationKnown = StringToInt(Get2DACache(sFile, "InvocationKnown", i));
json invocList = previousInvocList;
int previousInvocTotal = 0;
if (i > 0)
previousInvocTotal = StringToInt(Get2DACache(sFile, "InvocationKnown", i-1));
int previousInvocAmount = JsonGetInt(JsonObjectGet(previousInvocList, IntToString(maxInvocation)));
int currentInvocationAmount = (invocationKnown - previousInvocTotal + previousInvocAmount);
invocList = JsonObjectSet(invocList, IntToString(maxInvocation), JsonInt(currentInvocationAmount));
knownObject = JsonObjectSet(knownObject, IntToString(i+1), invocList);
previousInvocList = invocList;
}
SetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_INVOCATIONS_CACHE_VAR + IntToString(nClass), knownObject);
return knownObject;
}
int GetRemainingInvocationChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE)
{
int remaining = 0;
int nLevel = GetInvokerLevel(oPC, nClass);
json knownObject = GetInvokerKnownListObject(nClass, oPC);
json chosenInv = GetChosenSpellListObject(nClass, oPC);
json currentLevelKnown = JsonObjectGet(knownObject, IntToString(nLevel));
int totalCircles = JsonGetLength(JsonObjectKeys(currentLevelKnown));
// logic goes we are given a set amount of invocations at each circle. We can
// take from a circle above us, but not below us. So we need to make sure
// we have a legal amount of choices
int i;
for (i = 1; i <= totalCircles; i++)
{
int currentChosen = 0;
json chosenSpells = JsonObjectGet(chosenInv, IntToString(i));
if (chosenSpells != JsonNull())
{
int totalChosen = JsonGetLength(chosenSpells);
int j;
for (j = 0; j < totalChosen; j++)
{
int spellbookId = JsonGetInt(JsonArrayGet(chosenSpells, j));
// only count non extra invocation choices
if (!IsExtraChoiceInvocation(nClass, spellbookId, oPC))
currentChosen += 1;
}
}
int allowedAtCircle = JsonGetInt(JsonObjectGet(currentLevelKnown, IntToString(i)));
remaining = (allowedAtCircle - currentChosen + remaining);
// if the circle is below the chosen circle and we have a positive remaining,
// we set it to 0 because we cannot use lower circle spells on higher circle.
// however if thge value is negative then we carry it over because we
// have a deficit and need to account for it by using the spells of the
// next circle.
if (i < chosenCircle && remaining > 0)
remaining = 0;
}
// count extra and epic invocation choices
if (extra)
{
string sFile = GetAMSKnownFileName(nClass);
int maxCircle = StringToInt(Get2DACache(sFile, "MaxInvocationLevel", nLevel-1));
if (GetHasFeat(FEAT_EXTRA_INVOCATION_I, oPC) && (chosenCircle <= (maxCircle-1)))
{
int totalExp = GetMaxInvocationCount(oPC, INVOCATION_LIST_EXTRA);
json expChoices = GetExpandedChoicesList(nClass, oPC);
int choicesCount = JsonGetLength(JsonObjectKeys(expChoices));
remaining += (totalExp - choicesCount);
}
if (GetHasFeat(FEAT_EPIC_EXTRA_INVOCATION_I, oPC))
{
int totalExp = GetMaxInvocationCount(oPC, INVOCATION_LIST_EXTRA_EPIC);
json expChoices = GetEpicExpandedChoicesList(nClass, oPC);
int choicesCount = JsonGetLength(JsonObjectKeys(expChoices));
remaining += (totalExp - choicesCount);
}
}
return remaining;
}
int IsExtraChoiceInvocation(int nClass, int spellbookId, object oPC=OBJECT_SELF)
{
string sFile = GetClassSpellbookFile(nClass);
string spellId = Get2DACache(sFile, "SpellID", spellbookId);
json extraChoices = GetExpandedChoicesList(nClass, oPC);
json chosenSpell = JsonObjectGet(extraChoices, spellId);
if (chosenSpell != JsonNull())
return TRUE;
extraChoices = GetEpicExpandedChoicesList(nClass, oPC);
chosenSpell = JsonObjectGet(extraChoices, spellId);
if (chosenSpell != JsonNull())
return TRUE;
return FALSE;
}
////////////////////////////////////////////////////////////////////////////
/// ///
/// Truenamer ///
/// ///
////////////////////////////////////////////////////////////////////////////
int GetRemainingTruenameChoices(int nClass, int nType, object oPC=OBJECT_SELF)
{
string sFile = GetClassSpellbookFile(nClass);
json chosenSpells = GetChosenSpellListObject(nClass, oPC);
json circles = JsonObjectKeys(chosenSpells);
int totalCircles = JsonGetLength(circles);
int remainingChoices = 0;
int i;
for (i = 0; i < totalCircles; i++)
{
json spellList = JsonObjectGet(chosenSpells, JsonGetString(JsonArrayGet(circles, i)));
if (spellList != JsonNull())
{
int totalChoices = JsonGetLength(spellList);
int j;
for (j = 0; j < totalChoices; j++)
{
int spellbookId = JsonGetInt(JsonArrayGet(spellList, j));
int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", spellbookId));
// -1 means count all lexicons
if (nType == -1 || lexicon == nType)
remainingChoices += 1;
}
}
}
int maxChoices;
// if -1 we count all lexicons to get total remaining
if (nType == -1)
maxChoices = (GetMaxUtteranceCount(oPC, nClass, LEXICON_CRAFTED_TOOL)
+ GetMaxUtteranceCount(oPC, nClass, LEXICON_EVOLVING_MIND)
+ GetMaxUtteranceCount(oPC, nClass, LEXICON_PERFECTED_MAP));
else
maxChoices = GetMaxUtteranceCount(oPC, nClass, nType);
return (maxChoices - remainingChoices);
}
int GetLexiconCircleKnownAtLevel(int nLevel, int nType)
{
string sFile = "cls_true_maxlvl";
string columnName;
if (nType == LEXICON_EVOLVING_MIND)
columnName = "EvolvingMind";
else if (nType == LEXICON_CRAFTED_TOOL)
columnName = "CraftedTool";
else
columnName = "PerfectedMap";
return StringToInt(Get2DACache(sFile, columnName, nLevel-1));
}
////////////////////////////////////////////////////////////////////////////
/// ///
/// Archivist ///
/// ///
////////////////////////////////////////////////////////////////////////////
json GetArchivistNewSpellsList(object oPC=OBJECT_SELF)
{
json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR);
if (retValue == JsonNull())
retValue = JsonArray();
else
return retValue;
SetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR, retValue);
return retValue;
}