Fixed Vow of Poverty. Fixed Vow of Poverty bonus feats not being restored onClientEnter. Added PRC option for spontaneous casters unlearning spells (@Rakiov) Gloura was double dipping caster level. Added NUI levelup spellpicker for most AMS classes (@Rakiov) Tweaked Tactical Insight. Updated ToB maneuver TLK entries to display correct prerequisites.
531 lines
16 KiB
Plaintext
531 lines
16 KiB
Plaintext
#include "prc_nui_consts"
|
|
#include "inc_newspellbook"
|
|
#include "psi_inc_psifunc"
|
|
#include "inc_lookups"
|
|
#include "nw_inc_nui"
|
|
|
|
//
|
|
// GetCurrentSpellLevel
|
|
// Gets the current spell level the class can achieve at the current
|
|
// caster level (ranging from 0-9)
|
|
//
|
|
// Arguments:
|
|
// nClass:int the ClassID
|
|
// nLevel:int the caster level
|
|
//
|
|
// Returns:
|
|
// int the circle the class can achieve currently
|
|
//
|
|
int GetCurrentSpellLevel(int nClass, int nLevel);
|
|
|
|
//
|
|
// GetMaxSpellLevel
|
|
// Gets the highest possible circle the class can achieve (from 0-9)
|
|
//
|
|
// Arguments:
|
|
// nClass:int the ClassID
|
|
//
|
|
// Returns:
|
|
// int the highest circle that can be achieved
|
|
//
|
|
int GetMaxSpellLevel(int nClass);
|
|
|
|
//
|
|
// GetMinSpellLevel
|
|
// Gets the lowest possible circle the class can achieve (from 0-9)
|
|
//
|
|
// Arguments:
|
|
// nClass:int the ClassID
|
|
//
|
|
// Returns:
|
|
// int the lowest circle that can be achieved
|
|
//
|
|
int GetMinSpellLevel(int nClass);
|
|
|
|
//
|
|
// GetHighestLevelPossibleInClass
|
|
// Given a class Id this will determine what the max level of a class can be
|
|
// achieved
|
|
//
|
|
// Arguments:
|
|
// nClass:int the ClassID
|
|
//
|
|
// Returns:
|
|
// int the highest possible level the class can achieve
|
|
//
|
|
int GetHighestLevelPossibleInClass(int nClass);
|
|
|
|
//
|
|
// GetClassSpellbookFile
|
|
// Gets the class 2da spellbook/ability for the given class Id
|
|
//
|
|
// Arguments:
|
|
// nClass:int the classID
|
|
//
|
|
// Returns:
|
|
// string the 2da file name for the spell/abilities of the ClassID
|
|
//
|
|
string GetClassSpellbookFile(int nClass);
|
|
|
|
//
|
|
// GetBinderSpellToFeatDictionary
|
|
// Sets up the Binder Spell Dictionary that is used to match a binder's vestige
|
|
// to their feat. This is constructed based off the binder's known location of
|
|
// their feat and spell ranges in the base 2das respectivly. After constructing
|
|
// this it will be saved to the player locally as a cached result since we do
|
|
// not need to call this again.
|
|
//
|
|
// Argument:
|
|
// oPlayer:object the player
|
|
//
|
|
// Returns:
|
|
// json:Dictionary<String,Int> a dictionary of mapping between the SpellID
|
|
// and the FeatID of a vestige ability
|
|
//
|
|
json GetBinderSpellToFeatDictionary(object oPlayer=OBJECT_SELF);
|
|
|
|
//
|
|
// GetSpellLevelIcon
|
|
// Takes the spell circle int and gets the icon appropriate for it (i.e. 0 turns
|
|
// into "ir_cantrips"
|
|
//
|
|
// Arguments:
|
|
// spellLevel:int the spell level we want the icon for
|
|
//
|
|
// Returns:
|
|
// string the spell level icon
|
|
//
|
|
string GetSpellLevelIcon(int spellLevel);
|
|
|
|
//
|
|
// GetSpellLevelToolTip
|
|
// Gets the spell level tool tip text based on the int spell level provided (i.e.
|
|
// 0 turns into "Cantrips")
|
|
//
|
|
// Arguments:
|
|
// spellLevel:int the spell level we want the tooltip for
|
|
//
|
|
// Returns:
|
|
// string the spell level toop tip
|
|
//
|
|
string GetSpellLevelToolTip(int spellLevel);
|
|
|
|
//
|
|
// GetSpellIcon
|
|
// Gets the spell icon based off the spellId, or featId supplied
|
|
//
|
|
// Arguments:
|
|
// nClass:int the class Id
|
|
// featId:int the featId we can use the icon for
|
|
// spellId:int the spell Id we want the icon for
|
|
//
|
|
// Returns:
|
|
// json:String the string of the icon we want.
|
|
//
|
|
json GetSpellIcon(int spellId, int featId=0, int nClass=0);
|
|
string GetSpellName(int spellId, int realSpellID=0, int featId=0, int nClass=0);
|
|
|
|
//
|
|
// GreyOutButton
|
|
// Takes NUI Button along with it's width and height and greys it out it with a drawn
|
|
// colored rectangle to represent it's not been selected or not valid.
|
|
//
|
|
// Arguments:
|
|
// jButton:json the NUI Button
|
|
// w:float the width of the button
|
|
// h:float the height of the button
|
|
//
|
|
// Returns:
|
|
// json the NUI button greyed out
|
|
//
|
|
json GreyOutButton(json jButton, float w, float h);
|
|
|
|
//
|
|
// CreateGreyOutRectangle
|
|
// Creates a grey out rectangle for buttons
|
|
//
|
|
// Arguments:
|
|
// w:float the width of the button
|
|
// h:float the height of the button
|
|
//
|
|
// Returns:
|
|
// json the transparant black rectangle
|
|
//
|
|
json CreateGreyOutRectangle(float w, float h);
|
|
|
|
void CreateSpellDescriptionNUI(object oPlayer, int featID, int spellId=0, int realSpellId=0, int nClass=0);
|
|
void ClearSpellDescriptionNUI(object oPlayer=OBJECT_SELF);
|
|
|
|
int GetCurrentSpellLevel(int nClass, int nLevel)
|
|
{
|
|
int currentLevel = nLevel;
|
|
|
|
// ToB doesn't have a concept of spell levels, but still match up to it
|
|
if(nClass == CLASS_TYPE_WARBLADE
|
|
|| nClass == CLASS_TYPE_SWORDSAGE
|
|
|| nClass == CLASS_TYPE_CRUSADER
|
|
|| nClass == CLASS_TYPE_SHADOWCASTER)
|
|
{
|
|
return 9;
|
|
}
|
|
|
|
|
|
// Binders don't really have a concept of spell level
|
|
if (nClass == CLASS_TYPE_BINDER
|
|
|| nClass == CLASS_TYPE_DRAGON_SHAMAN) // they can only reach 1st circle
|
|
return 1;
|
|
|
|
//Shadowsmith has no concept of spell levels
|
|
if (nClass == CLASS_TYPE_SHADOWSMITH)
|
|
return 2;
|
|
|
|
if (nClass == CLASS_TYPE_WARLOCK
|
|
|| nClass == CLASS_TYPE_DRAGONFIRE_ADEPT)
|
|
return 4;
|
|
|
|
// Spont casters have their own function
|
|
if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS
|
|
|| nClass == CLASS_TYPE_ARCHIVIST)
|
|
{
|
|
|
|
int maxLevel = GetMaxSpellLevelForCasterLevel(nClass, currentLevel);
|
|
return maxLevel;
|
|
}
|
|
else
|
|
{
|
|
// everyone else uses this
|
|
string spellLevel2da = GetAMSKnownFileName(nClass);
|
|
|
|
currentLevel = nLevel - 1; // Level is 1 off of the row in the 2da
|
|
|
|
if (nClass == CLASS_TYPE_FIST_OF_ZUOKEN
|
|
|| nClass == CLASS_TYPE_PSION
|
|
|| nClass == CLASS_TYPE_PSYWAR
|
|
|| nClass == CLASS_TYPE_WILDER
|
|
|| nClass == CLASS_TYPE_PSYCHIC_ROGUE
|
|
|| nClass == CLASS_TYPE_WARMIND)
|
|
currentLevel = GetManifesterLevel(OBJECT_SELF, nClass, TRUE) - 1;
|
|
|
|
int totalLevel = Get2DARowCount(spellLevel2da);
|
|
|
|
// in case we somehow go over bounds just don't :)
|
|
if (currentLevel >= totalLevel)
|
|
currentLevel = totalLevel - 1;
|
|
|
|
//Psionics have MaxPowerLevel as their column name
|
|
string columnName = "MaxPowerLevel";
|
|
|
|
//Invokers have MaxInvocationLevel
|
|
if (nClass == CLASS_TYPE_WARLOCK
|
|
|| nClass == CLASS_TYPE_DRAGON_SHAMAN
|
|
|| nClass == CLASS_TYPE_DRAGONFIRE_ADEPT)
|
|
columnName = "MaxInvocationLevel";
|
|
|
|
// Truenamers have 3 sets of utterances, ranging from 1-6, EvolvingMind covers the entire range
|
|
if (nClass == CLASS_TYPE_TRUENAMER)
|
|
{
|
|
columnName = "EvolvingMind";
|
|
spellLevel2da = "cls_true_maxlvl"; //has a different 2da we want to look at
|
|
}
|
|
|
|
if (nClass == CLASS_TYPE_BINDER)
|
|
{
|
|
columnName = "VestigeLvl";
|
|
spellLevel2da = "cls_bind_binder";
|
|
}
|
|
|
|
// ToB doesn't have a concept of this, but we don't care.
|
|
|
|
int maxLevel = StringToInt(Get2DACache(spellLevel2da, columnName, currentLevel));
|
|
return maxLevel;
|
|
}
|
|
}
|
|
|
|
int GetMinSpellLevel(int nClass)
|
|
{
|
|
// again sponts have their own function
|
|
if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS
|
|
|| nClass == CLASS_TYPE_ARCHIVIST)
|
|
{
|
|
return GetMinSpellLevelForCasterLevel(nClass, GetHighestLevelPossibleInClass(nClass));
|
|
}
|
|
else
|
|
{
|
|
if (nClass == CLASS_TYPE_FIST_OF_ZUOKEN
|
|
|| nClass == CLASS_TYPE_PSION
|
|
|| nClass == CLASS_TYPE_PSYWAR
|
|
|| nClass == CLASS_TYPE_WILDER
|
|
|| nClass == CLASS_TYPE_PSYCHIC_ROGUE
|
|
|| nClass == CLASS_TYPE_WARMIND
|
|
|| nClass == CLASS_TYPE_WARBLADE
|
|
|| nClass == CLASS_TYPE_SWORDSAGE
|
|
|| nClass == CLASS_TYPE_CRUSADER
|
|
|| nClass == CLASS_TYPE_WARLOCK
|
|
|| nClass == CLASS_TYPE_DRAGONFIRE_ADEPT
|
|
|| nClass == CLASS_TYPE_DRAGON_SHAMAN
|
|
|| nClass == CLASS_TYPE_SHADOWCASTER
|
|
|| nClass == CLASS_TYPE_SHADOWSMITH
|
|
|| nClass == CLASS_TYPE_BINDER)
|
|
return 1;
|
|
|
|
return GetCurrentSpellLevel(nClass, 1);
|
|
}
|
|
|
|
}
|
|
|
|
int GetMaxSpellLevel(int nClass)
|
|
{
|
|
if (nClass == CLASS_TYPE_WILDER
|
|
|| nClass == CLASS_TYPE_PSION)
|
|
return 9;
|
|
if (nClass == CLASS_TYPE_PSYCHIC_ROGUE
|
|
|| nClass == CLASS_TYPE_FIST_OF_ZUOKEN
|
|
|| nClass == CLASS_TYPE_WARMIND)
|
|
return 5;
|
|
if (nClass == CLASS_TYPE_PSYWAR)
|
|
return 6;
|
|
|
|
return GetCurrentSpellLevel(nClass, GetHighestLevelPossibleInClass(nClass));
|
|
}
|
|
|
|
int GetHighestLevelPossibleInClass(int nClass)
|
|
{
|
|
string sFile;
|
|
|
|
//sponts have their spells in the classes.2da
|
|
if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS
|
|
|| nClass == CLASS_TYPE_ARCHIVIST)
|
|
{
|
|
sFile = Get2DACache("classes", "SpellGainTable", nClass);
|
|
}
|
|
else
|
|
{
|
|
// everyone else uses this
|
|
sFile = GetAMSKnownFileName(nClass);
|
|
|
|
if (nClass == CLASS_TYPE_TRUENAMER)
|
|
{
|
|
sFile = "cls_true_maxlvl"; //has a different 2da we want to look at
|
|
}
|
|
|
|
if (nClass == CLASS_TYPE_BINDER)
|
|
{
|
|
sFile = "cls_bind_binder";
|
|
}
|
|
}
|
|
|
|
return Get2DARowCount(sFile);
|
|
}
|
|
|
|
string GetClassSpellbookFile(int nClass)
|
|
{
|
|
string sFile;
|
|
// Spontaneous casters use a specific file name structure
|
|
if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS
|
|
|| nClass == CLASS_TYPE_ARCHIVIST)
|
|
{
|
|
sFile = GetFileForClass(nClass);
|
|
}
|
|
// everyone else uses this structure
|
|
else
|
|
{
|
|
sFile = GetAMSDefinitionFileName(nClass);
|
|
|
|
if (nClass == CLASS_TYPE_BINDER)
|
|
{
|
|
sFile = "vestiges";
|
|
}
|
|
}
|
|
|
|
return sFile;
|
|
}
|
|
|
|
string GetSpellLevelIcon(int spellLevel)
|
|
{
|
|
switch (spellLevel)
|
|
{
|
|
case 0: return "ir_cantrips";
|
|
case 1: return "ir_level1";
|
|
case 2: return "ir_level2";
|
|
case 3: return "ir_level3";
|
|
case 4: return "ir_level4";
|
|
case 5: return "ir_level5";
|
|
case 6: return "ir_level6";
|
|
case 7: return "ir_level789";
|
|
case 8: return "ir_level789";
|
|
case 9: return "ir_level789";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
string GetSpellLevelToolTip(int spellLevel)
|
|
{
|
|
switch (spellLevel)
|
|
{
|
|
case 0: return "Cantrips";
|
|
case 1: return "Level 1";
|
|
case 2: return "Level 2";
|
|
case 3: return "Level 3";
|
|
case 4: return "Level 4";
|
|
case 5: return "Level 5";
|
|
case 6: return "Level 6";
|
|
case 7: return "Level 7";
|
|
case 8: return "Level 8";
|
|
case 9: return "Level 9";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
|
|
json GetSpellIcon(int spellId,int featId=0,int nClass=0)
|
|
{
|
|
// Binder's spells don't have the FeatID on the spells.2da, so we have to use
|
|
// the mapping we constructed to get it.
|
|
if (nClass == CLASS_TYPE_BINDER)
|
|
{
|
|
json binderDict = GetBinderSpellToFeatDictionary();
|
|
int nFeatID = JsonGetInt(JsonObjectGet(binderDict, IntToString(spellId)));
|
|
return JsonString(Get2DACache("feat", "Icon", featId));
|
|
}
|
|
|
|
if (featId)
|
|
return JsonString(Get2DACache("feat", "Icon", featId));
|
|
|
|
int masterSpellID = StringToInt(Get2DACache("spells", "Master", spellId));
|
|
|
|
// if this is a sub radial spell, then we use spell's icon instead
|
|
if (masterSpellID)
|
|
return JsonString(Get2DACache("spells", "IconResRef", spellId));
|
|
|
|
// the FeatID holds the accurate spell icon, not the SpellID
|
|
int nFeatID = StringToInt(Get2DACache("spells", "FeatID", spellId));
|
|
|
|
return JsonString(Get2DACache("feat", "Icon", nFeatID));
|
|
}
|
|
|
|
string GetSpellName(int spellId, int realSpellID=0, int featId=0, int nClass=0)
|
|
{
|
|
if ((nClass == CLASS_TYPE_SHADOWSMITH
|
|
|| nClass == CLASS_TYPE_SHADOWCASTER) && spellId)
|
|
return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", spellId)));
|
|
if (nClass == CLASS_TYPE_TRUENAMER && featId)
|
|
return GetStringByStrRef(StringToInt(Get2DACache("feat", "FEAT", featId)));
|
|
if (realSpellID)
|
|
return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", realSpellID)));
|
|
if (spellId)
|
|
return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", spellId)));
|
|
if (featId)
|
|
return GetStringByStrRef(StringToInt(Get2DACache("feat", "FEAT", featId)));
|
|
|
|
return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", spellId)));
|
|
}
|
|
|
|
json GetBinderSpellToFeatDictionary(object oPlayer=OBJECT_SELF)
|
|
{
|
|
// a dictionary of <SpellID, FeatID>
|
|
json binderDict = GetLocalJson(oPlayer, NUI_SPELLBOOK_BINDER_DICTIONARY_CACHE_VAR);
|
|
// if this hasn't been created, create it now.
|
|
if (binderDict == JsonNull())
|
|
binderDict = JsonObject();
|
|
else
|
|
return binderDict;
|
|
|
|
// the starting row for binder spells
|
|
int spellIndex = 19070;
|
|
// the starting row for binder feats
|
|
int featIndex = 9030;
|
|
//the end of the binder spells/feats
|
|
while (spellIndex <= 19156 && featIndex <= 9104)
|
|
{
|
|
// get the SpellID tied to the feat
|
|
int spellID = StringToInt(Get2DACache("feat", "SPELLID", featIndex));
|
|
// if the spellID matches the current index, then this is the spell
|
|
// attached to the feat
|
|
if (spellID == spellIndex)
|
|
{
|
|
binderDict = JsonObjectSet(binderDict, IntToString(spellID), JsonInt(featIndex));
|
|
|
|
// move to next spell/feat
|
|
featIndex++;
|
|
spellIndex++;
|
|
}
|
|
// else we have reached a subdial spell
|
|
else
|
|
{
|
|
// loop through until we reach back at spellID
|
|
while (spellIndex < spellID)
|
|
{
|
|
int masterSpell = StringToInt(Get2DACache("spells", "Master", spellIndex));
|
|
|
|
// add the sub radial to the dict, tied to the master's FeatID
|
|
int featId = JsonGetInt(JsonObjectGet(binderDict, IntToString(masterSpell)));
|
|
binderDict = JsonObjectSet(binderDict, IntToString(spellIndex), JsonInt(featId));
|
|
|
|
spellIndex++;
|
|
}
|
|
|
|
|
|
// some feats overlap the same FeatID, can cause this to get stuck.
|
|
// if it happens then move on
|
|
if (spellIndex > spellID)
|
|
featIndex++;
|
|
}
|
|
}
|
|
|
|
// cache the result
|
|
SetLocalJson(oPlayer, NUI_SPELLBOOK_BINDER_DICTIONARY_CACHE_VAR, binderDict);
|
|
return binderDict;
|
|
}
|
|
|
|
json GreyOutButton(json jButton, float w, float h)
|
|
{
|
|
json retValue = jButton;
|
|
|
|
json jBorders = JsonArray();
|
|
jBorders = JsonArrayInsert(jBorders, CreateGreyOutRectangle(w, h));
|
|
|
|
return NuiDrawList(jButton, JsonBool(FALSE), jBorders);
|
|
}
|
|
|
|
json CreateGreyOutRectangle(float w, float h)
|
|
{
|
|
// set the points of the button shape
|
|
json jPoints = JsonArray();
|
|
jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0));
|
|
jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0));
|
|
|
|
jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0));
|
|
jPoints = JsonArrayInsert(jPoints, JsonFloat(h));
|
|
|
|
jPoints = JsonArrayInsert(jPoints, JsonFloat(w));
|
|
jPoints = JsonArrayInsert(jPoints, JsonFloat(h));
|
|
|
|
jPoints = JsonArrayInsert(jPoints, JsonFloat(w));
|
|
jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0));
|
|
|
|
jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0));
|
|
jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0));
|
|
|
|
return NuiDrawListPolyLine(JsonBool(TRUE), NuiColor(0, 0, 0, 127), JsonBool(TRUE), JsonFloat(2.0), jPoints);
|
|
}
|
|
|
|
void CreateSpellDescriptionNUI(object oPlayer, int featID, int spellId=0, int realSpellId=0, int nClass=0)
|
|
{
|
|
SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_FEATID_VAR, featID);
|
|
SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_SPELLID_VAR, spellId);
|
|
SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_REAL_SPELLID_VAR, realSpellId);
|
|
SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_CLASSID_VAR, nClass);
|
|
ExecuteScript("prc_nui_dsc_view", oPlayer);
|
|
}
|
|
|
|
void ClearSpellDescriptionNUI(object oPlayer=OBJECT_SELF)
|
|
{
|
|
DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_FEATID_VAR);
|
|
DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_SPELLID_VAR);
|
|
DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_REAL_SPELLID_VAR);
|
|
DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_CLASSID_VAR);
|
|
}
|
|
|