//::///////////////////////////////////////////////
//:: 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);
    }
	if (GetIsBladeMagicClass(nClass))
	{
		DeletePersistantLocalInt(oPC, "AllowedDisciplines");
	}
    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;
}

//:: void main(){}