Files
PRC8/nwn/nwnprc/trunk/scripts/prc_playerinfo.nss
Jaysyn904 257cb23488 2025/11/25
Added Spell Cancelation tool to end spells early.
Made several Exalted feats available generally.
Fixed prereqs for several Exalted feats.
Fixed typo in Chasing Perfection related itemprops.
Grouped Exalted feats under a masterfeat.
Moved PRC8 Packages far down the packages.2da so as to not conflict with modules.
Updated PRC8 Tester module.
Epic Spell: Summon Aberration no longer sucks.  Creatures were updated to match PnP and now level with caster.
Twinfiend summon now receives the correct number of skill points for their bonus HD.
Added LevelUpSummon() function for handling creatures w/ class levels.
Further tweaking for the prc_2da_cache creature to prevent NPCs from attacking it.
Add paragon & psuedonatural template related json functions.
Gated errant debug message in prc_amagsys_gain.nss.
Add DM Tool for viewing PC's current character sheet, templates & spell effects.
Arrow of Bone shouldn't provide free mundane arrows anymore.  Needs testing.
Fixed a bunch of minor TLK typos.
2025-11-25 09:00:22 -05:00

407 lines
17 KiB
Plaintext

//::////////////////////////////////////////////////////////
//:: ;-. ,-. ,-. ,-.
//:: | ) | ) / ( )
//:: |-' |-< | ;-:
//:: | | \ \ ( )
//:: ' ' ' `-' `-'
//::////////////////////////////////////////////////////////
//:: FileName: "prc_playerinfo"
//:: Created By: Jaysyn
//:: Last Updated On: 2025-11-25 08:26:22
//::
//::////////////////////////////////////////////////////////
/*
Displays a lot of relevant PC info in an NUI window
*/
//::////////////////////////////////////////////////////////
#include "nw_inc_nui"
#include "prc_inc_template"
const string CHAR_SHEET_WINDOW_ID = "char_sheet_window";
void ShowCharacterSheet(object oPC, object oTarget)
{
//:: Close existing window if open
int nToken = NuiFindWindow(oPC, CHAR_SHEET_WINDOW_ID);
if (nToken > 0)
{
NuiDestroy(oPC, nToken);
return;
}
//:: Build the layout
json jCol = JsonArray();
//:: === CHARACTER NAME & RACE ===
json jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Name:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_name"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Race:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_race"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Subrace:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_subrace"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Deity:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_deity"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Templates:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiText(NuiBind("char_templates"), TRUE, NUI_SCROLLBARS_AUTO));
jCol = JsonArrayInsert(jCol, NuiHeight(NuiRow(jRow), 60.0));
//:: === CLASSES ===
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Classes:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiText(NuiBind("char_classes"), TRUE, NUI_SCROLLBARS_AUTO));
jCol = JsonArrayInsert(jCol, NuiHeight(NuiRow(jRow), 120.0));
//:: === LEVEL & EXPERIENCE ===
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Total Level:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_level"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: Only show XP for PCs
if (GetIsPC(oTarget))
{
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Experience:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_xp"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
}
//:: === HIT POINTS ===
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Hit Points:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_hp"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: === ABILITY SCORES - Two columns ===
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Ability Scores:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: STR and DEX
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("STR:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_str"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("DEX:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_dex"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: CON and INT
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("CON:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_con"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("INT:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_int"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: WIS and CHA
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("WIS:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_wis"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("CHA:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_cha"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: === ENCUMBRANCE ===
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Encumbrance:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_encumbrance"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: === SAVING THROWS ===
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Saving Throws:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: Fortitude
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Fortitude:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_fort"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: Reflex
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Reflex:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_ref"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: Will
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Will:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_will"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: === ARMOR CLASS ===
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Armor Class:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jRow = JsonArrayInsert(jRow, NuiSpacer());
jRow = JsonArrayInsert(jRow, NuiLabel(NuiBind("char_ac"), JsonInt(NUI_HALIGN_RIGHT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
//:: === ACTIVE EFFECTS ===
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiLabel(JsonString("Active Spells:"), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_MIDDLE)));
jCol = JsonArrayInsert(jCol, NuiRow(jRow));
jRow = JsonArray();
jRow = JsonArrayInsert(jRow, NuiText(NuiBind("char_effects"), TRUE, NUI_SCROLLBARS_AUTO));
jCol = JsonArrayInsert(jCol, NuiHeight(NuiRow(jRow), 100.0));
//:: Create window
json jRoot = NuiCol(jCol);
string sTitle = "Character Sheet: " + GetName(oTarget);
json jNui = NuiWindow(
jRoot,
JsonString(sTitle),
NuiBind("geometry"),
JsonBool(TRUE),
JsonBool(FALSE),
JsonBool(TRUE),
JsonBool(FALSE),
JsonBool(TRUE)
);
nToken = NuiCreate(oPC, jNui, CHAR_SHEET_WINDOW_ID);
//:: Set geometry
NuiSetBind(oPC, nToken, "geometry", NuiRect(100.0, 100.0, 400.0, 850.0));
//:: === POPULATE DATA ===
//:: Name
NuiSetBind(oPC, nToken, "char_name", JsonString(GetName(oTarget)));
//:: Race
string sRace = GetStringByStrRef(StringToInt(Get2DAString("racialtypes", "Name", GetRacialType(oTarget))));
if (sRace == "") sRace = "Unknown";
NuiSetBind(oPC, nToken, "char_race", JsonString(sRace));
//:: Subrace
string sSubrace = GetSubRace(oTarget);
if (sSubrace == "") sSubrace = "None";
NuiSetBind(oPC, nToken, "char_subrace", JsonString(sSubrace));
//:: Deity
string sDeity = GetDeity(oTarget);
if (sDeity == "") sDeity = "None";
NuiSetBind(oPC, nToken, "char_deity", JsonString(sDeity));
//:: Templates - Check for persistent local variables named "template_X"
string sTemplates = "";
int nTemplateCount = 0;
int i;
int nMaxTemplates = 128;
for (i = 0; i <= nMaxTemplates; i++)
{
string sVarName = "template_" + IntToString(i);
//:: Check if this persistent local variable exists and is TRUE
if (GetPersistantLocalInt(oTarget, sVarName))
{
string sNameEntry = Get2DAString("templates", "Name", i);
//:: Only process if we got a valid name entry
if (sNameEntry != "")
{
int nNameStrRef = StringToInt(sNameEntry);
string sTemplateName = GetStringByStrRef(nNameStrRef);
if (sTemplateName == "")
sTemplateName = "Template " + IntToString(i);
if (nTemplateCount > 0)
sTemplates += "\n";
sTemplates += sTemplateName;
nTemplateCount++;
}
}
}
if (nTemplateCount == 0)
sTemplates = "None";
NuiSetBind(oPC, nToken, "char_templates", JsonString(sTemplates));
//:: Classes
string sClasses = "";
for (i = 1; i <= 8; i++)
{
int nClass = GetClassByPosition(i, oTarget);
if (nClass != CLASS_TYPE_INVALID)
{
string sClassName = GetStringByStrRef(StringToInt(Get2DAString("classes", "Name", nClass)));
if (sClassName == "") sClassName = "Class " + IntToString(nClass);
int nLevel = GetLevelByClass(nClass, oTarget);
if (sClasses != "")
sClasses += "\n";
sClasses += sClassName + " " + IntToString(nLevel);
}
}
if (sClasses == "")
sClasses = "No classes found";
NuiSetBind(oPC, nToken, "char_classes", JsonString(sClasses));
//:: Level
NuiSetBind(oPC, nToken, "char_level", JsonString(IntToString(GetHitDice(oTarget))));
//:: XP (only for PCs)
if (GetIsPC(oTarget))
{
NuiSetBind(oPC, nToken, "char_xp", JsonString(IntToString(GetXP(oTarget))));
}
//:: Hit Points
string sHP = IntToString(GetCurrentHitPoints(oTarget)) + " / " + IntToString(GetMaxHitPoints(oTarget));
NuiSetBind(oPC, nToken, "char_hp", JsonString(sHP));
//:: Ability Scores - Individual binds
NuiSetBind(oPC, nToken, "char_str", JsonString(IntToString(GetAbilityScore(oTarget, ABILITY_STRENGTH))));
NuiSetBind(oPC, nToken, "char_dex", JsonString(IntToString(GetAbilityScore(oTarget, ABILITY_DEXTERITY))));
NuiSetBind(oPC, nToken, "char_con", JsonString(IntToString(GetAbilityScore(oTarget, ABILITY_CONSTITUTION))));
NuiSetBind(oPC, nToken, "char_int", JsonString(IntToString(GetAbilityScore(oTarget, ABILITY_INTELLIGENCE))));
NuiSetBind(oPC, nToken, "char_wis", JsonString(IntToString(GetAbilityScore(oTarget, ABILITY_WISDOM))));
NuiSetBind(oPC, nToken, "char_cha", JsonString(IntToString(GetAbilityScore(oTarget, ABILITY_CHARISMA))));
//:: Encumbrance
int nEncumbrance = GetWeight(oTarget) / 10;
string sEncumbrance = IntToString(nEncumbrance) + " lbs";
//:: Only check for gold bag on PCs
if (GetIsPC(oTarget) && GetIsObjectValid(GetItemPossessedBy(oTarget, "NW_IT_MNYBAG01")))
{
nEncumbrance += GetGold(oTarget) / 50;
sEncumbrance += " (+" + IntToString(GetGold(oTarget) / 50) + " gold)";
}
NuiSetBind(oPC, nToken, "char_encumbrance", JsonString(sEncumbrance));
//:: Saves - Individual binds
NuiSetBind(oPC, nToken, "char_fort", JsonString(IntToString(GetFortitudeSavingThrow(oTarget))));
NuiSetBind(oPC, nToken, "char_ref", JsonString(IntToString(GetReflexSavingThrow(oTarget))));
NuiSetBind(oPC, nToken, "char_will", JsonString(IntToString(GetWillSavingThrow(oTarget))));
//:: AC
NuiSetBind(oPC, nToken, "char_ac", JsonString(IntToString(GetAC(oTarget))));
//:: Active Spells - Track unique spell IDs to avoid duplicates
string sEffects = "";
effect eEffect = GetFirstEffect(oTarget);
int nEffectCount = 0;
string sTrackedSpells = ""; //:: Use this to track which spells we've already listed
while (GetIsEffectValid(eEffect))
{
int nSpellId = GetEffectSpellId(eEffect);
//:: Only process if this is a valid spell and we haven't already listed it
if (nSpellId > 0)
{
string sSpellIdStr = IntToString(nSpellId);
//:: Check if we've already processed this spell
if (FindSubString(sTrackedSpells, ":" + sSpellIdStr + ":") == -1)
{
//:: Add to tracked list
sTrackedSpells += ":" + sSpellIdStr + ":";
//:: Get spell name from spells.2da -> TLK
int nNameStrRef = StringToInt(Get2DAString("spells", "Name", nSpellId));
string sSpellName = GetStringByStrRef(nNameStrRef);
if (sSpellName == "")
sSpellName = "Unknown Spell (ID: " + sSpellIdStr + ")";
//:: Get duration for this spell effect
float fDuration = IntToFloat(GetEffectDurationRemaining(eEffect));
string sDuration = "";
if (fDuration > 0.0)
{
int nSeconds = FloatToInt(fDuration);
int nMinutes = nSeconds / 60;
nSeconds = nSeconds % 60;
if (nMinutes > 0)
sDuration = IntToString(nMinutes) + "m " + IntToString(nSeconds) + "s";
else
sDuration = IntToString(nSeconds) + "s";
}
else
{
sDuration = "Permanent";
}
if (nEffectCount > 0)
sEffects += "\n";
sEffects += sSpellName + " (" + sDuration + ")";
nEffectCount++;
}
}
eEffect = GetNextEffect(oTarget);
}
if (nEffectCount == 0)
sEffects = "No active spells";
NuiSetBind(oPC, nToken, "char_effects", JsonString(sEffects));
}
void main()
{
object oPC = OBJECT_SELF;
object oTarget = oPC;
//:: If PC is targeting something else, show that target's sheet instead
object oTargeted = GetLocalObject(oPC, "EXAMINE_TARGET");
if (GetIsObjectValid(oTargeted) && GetObjectType(oTargeted) == OBJECT_TYPE_CREATURE)
{
oTarget = oTargeted;
}
//:: Show the window
ShowCharacterSheet(oPC, oTarget);
}