PRC8/nwn/nwnprc/trunk/scripts/prc_nui_sb_view.nss
Jaysyn904 619b7c62c9 2025/06/03 Update
Updated NUI spellcast menu (@Rakiov)
Updated Tornado Throw.
Updated Midnight Augmentation.
Updated Lucky Dice's tlk entries.
Fixed Heartwarder's marker feat.
2025-06-03 23:13:56 -04:00

572 lines
19 KiB
Plaintext

//::///////////////////////////////////////////////
//:: PRC Spellbook NUI View
//:: prc_nui_sb_view
//:://////////////////////////////////////////////
/*
This is the NUI view for the PRC Spellbook
*/
//:://////////////////////////////////////////////
//:: Created By: Rakiov
//:: Created On: 24.05.2005
//:://////////////////////////////////////////////
#include "nw_inc_nui"
//#include "nw_inc_nui_insp"
#include "prc_nui_sb_inc"
#include "prc_nui_consts"
//
// CreateSpellBookClassButtons
// Gets the list of classes that have Spells, "Spells" and /Spells/ the player has
// that are allowed to use the NUI Spellbook.
//
// Returns:
// json NuiRow the list of class buttons allowed to use the NUI Spellbook
//
json CreateSpellBookClassButtons();
//
// CreateSpellbookSpellButtons
// Creates the NUI buttons for the spells a player knows in the specified class
// and circle provided.
//
// Arguments:
// nClass int the class currently being checked for spells
// circle int the circle level of the spells we want to check for
//
// Returns:
// json:Array<NuiRow> the list of NuiRows of spells we have memorized
//
json CreateSpellbookSpellButtons(int nClass, int circle);
//
// CreateSpellbookSpellButtons
// Creates the buttons for what circles the class is allowed to cast in
// ranging from Cantrips to 9th circle or equivalent for classes that don't have
// a concept of spell circles, like ToB and Psionics
//
// Arguments:
// nClass int the class id this is being constructed for.
//
// Returns:
// json NuiRow the level at which the caster can or does know as buttons
//
json CreateSpellbookCircleButtons(int nClass);
//
// CreateMetaMagicFeatButtons
// Takes a class and creates the appropriate meta feat buttons it can use or
// possibly use.
//
// Arguments:
// nClass:int the ClassID we are checking
//
// Returns:
// json:Array<NuiRow> the list of meta feats the class can use. Can return an
// empty JsonArray if no meta feats are allowed for the class.
//
json CreateMetaMagicFeatButtons(int nClass);
//
// CreateMetaFeatButtonRow
// a helper function for CreateMetaMagicFeatButtons that takes a list of featIds
// and creates buttons for them.
//
// Arguments:
// featList:json:Array<int> the list of featIDs to render
//
// Returns:
// json:Array<NuiButtons> the row of buttons rendered for the FeatIDs.
//
json CreateMetaFeatButtonRow(json spellList);
//
// 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 by using the FeatID instead
//
// Arguments:
// nClass:int the class Id
// 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 nClass=0);
//
// HighlightButton
// Takes NUI Button along with it's width and height and heighlights it with a drawn
// colored rectangle to represent it's been selected.
//
// 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 highlighted
//
json HighlightButton(json jButton, float w, float h);
void main()
{
// look for existing window and destroy
int nPreviousToken = NuiFindWindow(OBJECT_SELF, PRC_SPELLBOOK_NUI_WINDOW_ID);
if(nPreviousToken != 0)
{
NuiDestroy(OBJECT_SELF, nPreviousToken);
}
json jRoot = JsonArray();
json jRow = CreateSpellBookClassButtons();
jRoot = JsonArrayInsert(jRoot, jRow);
int selectedClassId = GetLocalInt(OBJECT_SELF, PRC_SPELLBOOK_SELECTED_CLASSID_VAR);
// GetLocalInt returns 0 if not set, which is Barb class which conveniently doesn't have spells :)
// if there was no selected class then there is nothing to render
if (selectedClassId != CLASS_TYPE_BARBARIAN)
{
// create the metamagic/metapsionic/metamystery/sudden buttons if applicable
// suddens are on their own row so its possible we can have 2 NuiRows in the list
jRow = CreateMetaMagicFeatButtons(selectedClassId);
int i;
for(i = 0; i < JsonGetLength(jRow); i++)
{
jRoot = JsonArrayInsert(jRoot, JsonArrayGet(jRow, i));
}
// create the spell/feat circle buttons for the class (most use 0-9, but
// ToB uses something similar that ranges from 1-9 and Invokers essentially
// go 1-4 as examples
jRow = CreateSpellbookCircleButtons(selectedClassId);
jRoot = JsonArrayInsert(jRoot, jRow);
// Get the currently selected circle's spell buttons
int currentCircle = GetLocalInt(OBJECT_SELF, PRC_SPELLBOOK_SELECTED_CIRCLE_VAR);
jRow = CreateSpellbookSpellButtons(selectedClassId, currentCircle);
// since we limit how many buttons a row can have here we need to add
// multiple NuiRows if they exist
for(i = 0; i < JsonGetLength(jRow); i++)
{
jRoot = JsonArrayInsert(jRoot, JsonArrayGet(jRow, i));
}
}
jRoot = NuiCol(jRoot);
string title = "PRC Spellbook";
if (selectedClassId != CLASS_TYPE_BARBARIAN)
title = title + ": " + GetStringByStrRef(StringToInt(Get2DACache("classes", "Name", selectedClassId)));
// This is the main window with jRoot as the main pane. It includes titles and parameters (more on those later)
json nui = NuiWindow(jRoot, JsonString(title), NuiBind("geometry"), NuiBind("resizable"), NuiBind("collapsed"), NuiBind("closable"), NuiBind("transparent"), NuiBind("border"));
// finally create it and it'll return us a non-zero token.
int nToken = NuiCreate(OBJECT_SELF, nui, PRC_SPELLBOOK_NUI_WINDOW_ID);
// get the geometry of the window in case we opened this before and have a
// preference for location
json geometry = GetLocalJson(OBJECT_SELF, PRC_SPELLBOOK_NUI_GEOMETRY_VAR);
// Default to put this near the middle and let the person adjust its location
if (geometry == JsonNull())
{
geometry = NuiRect(893.0f,346.0f, 489.0f, 351.0f);
}
else
{
float x = JsonGetFloat(JsonObjectGet(geometry, "x"));
float y = JsonGetFloat(JsonObjectGet(geometry, "y"));
geometry = NuiRect(x, y, 489.0f, 351.0f);
}
// Set the binds to their default values
NuiSetBind(OBJECT_SELF, nToken, "geometry", geometry);
NuiSetBind(OBJECT_SELF, nToken, "collapsed", JsonBool(FALSE));
NuiSetBind(OBJECT_SELF, nToken, "resizable", JsonBool(FALSE));
NuiSetBind(OBJECT_SELF, nToken, "closable", JsonBool(TRUE));
NuiSetBind(OBJECT_SELF, nToken, "transparent", JsonBool(TRUE));
NuiSetBind(OBJECT_SELF, nToken, "border", JsonBool(FALSE));
}
json CreateSpellBookClassButtons()
{
json jRow = JsonArray();
// Get all the Classes that can use the NUI Spellbook
json classList = GetSupportedNUISpellbookClasses(OBJECT_SELF);
// if we have selected a class already due to re-rendering, we need to disable
// the button for it.
int selectedClassId = GetLocalInt(OBJECT_SELF, PRC_SPELLBOOK_SELECTED_CLASSID_VAR);
int i;
for (i = 0; i < JsonGetLength(classList); i++)
{
int classId = JsonGetInt(JsonArrayGet(classList, i));
// if the selected class doen't exist, automatically use the first class allowed
if (selectedClassId == 0)
{
selectedClassId = classId;
SetLocalInt(OBJECT_SELF, PRC_SPELLBOOK_SELECTED_CLASSID_VAR, selectedClassId);
}
float width = 32.0f;
float height = 32.0f;
// Get the class icon from the classes.2da
json jClassButton = NuiId(NuiButtonImage(JsonString(Get2DACache("classes", "Icon", classId))), PRC_SPELLBOOK_NUI_CLASS_BUTTON_BASEID + IntToString(classId));
if (classId == selectedClassId)
jClassButton = HighlightButton(jClassButton, width, height);
jClassButton = NuiWidth(jClassButton, width);
jClassButton = NuiHeight(jClassButton, height);
// Get the class name from the classes.2da and set it to the tooltip
jClassButton = NuiTooltip(jClassButton, JsonString(GetStringByStrRef(StringToInt(Get2DACache("classes", "Name", classId)))));
jRow = JsonArrayInsert(jRow, jClassButton);
}
jRow = NuiRow(jRow);
return jRow;
}
json CreateSpellbookCircleButtons(int nClass)
{
json jRow = JsonArray();
int i;
// Get the current selected circle and the class caster level.
int currentCircle = GetLocalInt(OBJECT_SELF, PRC_SPELLBOOK_SELECTED_CIRCLE_VAR);
int casterLevel = GetCasterLevelByClass(nClass, OBJECT_SELF);
// Get what the lowest level of a circle is for the class (some start at 1,
// some start higher, some start at cantrips)
int minSpellLevel = GetMinSpellLevel(nClass);
if (minSpellLevel >= 0)
{
// get what is the highest circle the class can cast at
int currentMaxSpellLevel = GetCurrentSpellLevel(nClass, casterLevel);
// Get what the max circle the class can reach at is
int totalMaxSpellLevel = GetMaxSpellLevel(nClass);
// if the current circle is less than the minimum level (possibly due to
// switching classes) then set it to that.
if (currentCircle < minSpellLevel)
{
currentCircle = minSpellLevel;
SetLocalInt(OBJECT_SELF, PRC_SPELLBOOK_SELECTED_CIRCLE_VAR, currentCircle);
}
// conversily if it is higher than the max the class has (possibly due to
// switching classes) then set it to that.
if (currentCircle > currentMaxSpellLevel)
{
currentCircle = currentMaxSpellLevel;
SetLocalInt(OBJECT_SELF, PRC_SPELLBOOK_SELECTED_CIRCLE_VAR, currentCircle);
}
for (i = minSpellLevel; i <= totalMaxSpellLevel; i++)
{
json enabled;
json jButton = NuiId(NuiButtonImage(JsonString(GetSpellLevelIcon(i))), PRC_SPELLBOOK_NUI_CIRCLE_BUTTON_BASEID + IntToString(i));
float width = 42.0f;
float height = 42.0f;
jButton = NuiWidth(jButton, width);
jButton = NuiHeight(jButton, height);
jButton = NuiTooltip(jButton, JsonString(GetSpellLevelToolTip(i)));
// if the current circle is selected or if the person can't cast at
// that circle yet then disable the button.
if (i > currentMaxSpellLevel)
enabled = JsonBool(FALSE);
else
enabled = JsonBool(TRUE);
jButton = NuiEnabled(jButton, enabled);
if (i == currentCircle)
jButton = HighlightButton(jButton, width, height);
jRow = JsonArrayInsert(jRow, jButton);
}
}
jRow = NuiRow(jRow);
return jRow;
}
json CreateSpellbookSpellButtons(int nClass, int circle)
{
json jRows = JsonArray();
// we only want to get spells at the currently selected circle.
int currentCircle = GetLocalInt(OBJECT_SELF, PRC_SPELLBOOK_SELECTED_CIRCLE_VAR);
json spellListAtCircle = GetSpellListForCircle(OBJECT_SELF, nClass, currentCircle);
string sFile = GetClassSpellbookFile(nClass);
// how many buttons a row can have before we have to make a new row.
int rowLimit = NUI_SPELLBOOK_SPELL_BUTTON_LENGTH;
json tempRow = JsonArray();
int i;
for (i = 0; i < JsonGetLength(spellListAtCircle); i++)
{
int spellbookId = JsonGetInt(JsonArrayGet(spellListAtCircle, i));
int spellId;
// Binders don't have a spellbook, so spellbookId is actually SpellID
if (nClass == CLASS_TYPE_BINDER)
spellId = spellbookId;
else
spellId = StringToInt(Get2DACache(sFile, "SpellID", spellbookId));
json jSpellButton = NuiId(NuiButtonImage(GetSpellIcon(spellId, nClass)), PRC_SPELLBOOK_NUI_SPELL_BUTTON_BASEID + IntToString(spellbookId));
jSpellButton = NuiWidth(jSpellButton, 38.0f);
jSpellButton = NuiHeight(jSpellButton, 38.0f);
// the RealSpellID has the accurate descriptions for the spells/abilities
int realSpellId = StringToInt(Get2DACache(sFile, "RealSpellID", spellbookId));
if (!realSpellId)
realSpellId = spellId;
jSpellButton = NuiTooltip(jSpellButton, JsonString(GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", realSpellId)))));
// if the row limit has been reached, make a new row
tempRow = JsonArrayInsert(tempRow, jSpellButton);
if (JsonGetLength(tempRow) >= rowLimit)
{
tempRow = NuiRow(tempRow);
jRows = JsonArrayInsert(jRows, tempRow);
tempRow = JsonArray();
}
}
// if the row was cut short (a remainder) then we finish the row and add it
// to the list
if (JsonGetLength(tempRow) > 0)
{
tempRow = NuiRow(tempRow);
jRows = JsonArrayInsert(jRows, tempRow);
}
return jRows;
}
json CreateMetaMagicFeatButtons(int nClass)
{
json jRows = JsonArray();
json currentRow = JsonArray();
// if an invoker, add the invoker shapes and essences as its own row of buttons
if (nClass == CLASS_TYPE_WARLOCK
|| nClass == CLASS_TYPE_DRAGONFIRE_ADEPT
|| nClass == CLASS_TYPE_DRAGON_SHAMAN)
{
currentRow = CreateMetaFeatButtonRow(GetInvokerShapeSpellList(nClass));
if (JsonGetLength(currentRow) > 0)
{
currentRow = NuiRow(currentRow);
jRows = JsonArrayInsert(jRows, currentRow);
}
currentRow = CreateMetaFeatButtonRow(GetInvokerEssenceSpellList(nClass));
if (JsonGetLength(currentRow) > 0)
{
currentRow = NuiRow(currentRow);
jRows = JsonArrayInsert(jRows, currentRow);
}
}
// if a ToB class, add its stances as its own row of buttons
if (nClass == CLASS_TYPE_WARBLADE
|| nClass == CLASS_TYPE_CRUSADER
|| nClass == CLASS_TYPE_SWORDSAGE)
{
currentRow = CreateMetaFeatButtonRow(GetToBStanceSpellList(nClass));
if (JsonGetLength(currentRow) > 0)
{
currentRow = NuiRow(currentRow);
jRows = JsonArrayInsert(jRows, currentRow);
}
}
currentRow = JsonArray();
// check to see if the class can use any particular meta feats
if (CanClassUseMetamagicFeats(nClass))
currentRow = CreateMetaFeatButtonRow(GetMetaMagicFeatList());
else if (CanClassUseMetaPsionicFeats(nClass))
currentRow = CreateMetaFeatButtonRow(GetMetaPsionicFeatList());
else if (CanClassUseMetaMysteryFeats(nClass))
currentRow = CreateMetaFeatButtonRow(GetMetaMysteryFeatList());
if (JsonGetLength(currentRow) > 0)
{
currentRow = NuiRow(currentRow);
jRows = JsonArrayInsert(jRows, currentRow);
}
// and check to see if the class can use sudden meta feats
currentRow = JsonArray();
if (CanClassUseSuddenMetamagicFeats(nClass))
currentRow = CreateMetaFeatButtonRow(GetSuddenMetaMagicFeatList());
if (JsonGetLength(currentRow) > 0)
{
currentRow = NuiRow(currentRow);
jRows = JsonArrayInsert(jRows, currentRow);
}
return jRows;
}
json CreateMetaFeatButtonRow(json spellList)
{
json jRow = JsonArray();
int i;
for (i = 0; i < JsonGetLength(spellList); i++)
{
int spellId = JsonGetInt(JsonArrayGet(spellList, i));
int featId;
int masterSpell = StringToInt(Get2DACache("spells", "Master", spellId));
if (masterSpell)
featId = StringToInt(Get2DACache("spells", "FeatID", masterSpell));
else
featId = StringToInt(Get2DACache("spells", "FeatID", spellId));
if (GetHasFeat(featId, OBJECT_SELF, TRUE))
{
string featName = GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", spellId)));
json jMetaButton = NuiId(NuiButtonImage(GetSpellIcon(spellId)), PRC_SPELLBOOK_NUI_META_BUTTON_BASEID + IntToString(spellId));
jMetaButton = NuiWidth(jMetaButton, 32.0f);
jMetaButton = NuiHeight(jMetaButton, 32.0f);
jMetaButton = NuiTooltip(jMetaButton, JsonString(featName));
jRow = JsonArrayInsert(jRow, jMetaButton);
}
}
return jRow;
}
json GetSpellIcon(int spellId,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 featId = JsonGetInt(JsonObjectGet(binderDict, IntToString(spellId)));
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 featId = StringToInt(Get2DACache("spells", "FeatID", spellId));
return JsonString(Get2DACache("feat", "Icon", featId));
}
json HighlightButton(json jButton, float w, float h)
{
json retValue = jButton;
json jBorders = JsonArray();
// 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));
jBorders = JsonArrayInsert(jBorders, NuiDrawListPolyLine(JsonBool(TRUE), NuiColor(71, 140, 32), JsonBool(FALSE), JsonFloat(2.0), jPoints));
return NuiDrawList(jButton, JsonBool(FALSE), jBorders);
}
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 "";
}