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.
This commit is contained in:
Jaysyn904
2025-11-25 09:00:22 -05:00
parent 80070703b4
commit 257cb23488
41 changed files with 3440 additions and 621 deletions

View File

@@ -0,0 +1,67 @@
//:://////////////////////////////////////////////
//:: Created By: Jason Stephenson
//:: Created On: August 3, 2004
//:://////////////////////////////////////////////
//:://////////////////////////////////////////////
//:: Changed By: Jason Stephenson
//:: Changed On: November 24, 2004
//:: Note: Changed to use if instead of switch().
//:: Also fixed code for X2_ITEM_EVENT_SPELLCAST_AT.
//:: Changed On: December 12, 2004
//:: Note: Using helper functions from Axe Murderer's example.
//:: Click Here
//:: Changed On: February 03, 2005
//:: Note: Fix Axe Murderer's SetTagBasedScriptExitBehavior function to
//:: only clear the variables if the nEndContinue is set to
//:: X2_EXECUTE_SCRIPT_END.
//:: Changed By: Jaysyn
//:: Changed On: 2025-11-25 08:31:43
//:: Note: Modified for use with DM info tool
//:://////////////////////////////////////////////
#include "x2_inc_switches"
int GetTagBasedItemEventNumber()
{
int nEvent = GetLocalInt(OBJECT_SELF, "X2_L_LAST_ITEM_EVENT");
return (nEvent ? nEvent : GetLocalInt(GetModule(), "X2_L_LAST_ITEM_EVENT"));
}
void SetTagBasedScriptExitBehavior(int nEndContinue)
{
if (nEndContinue == X2_EXECUTE_SCRIPT_END)
{
DeleteLocalInt(OBJECT_SELF, "X2_L_LAST_ITEM_EVENT");
DeleteLocalInt(GetModule(), "X2_L_LAST_ITEM_EVENT");
}
SetExecutedScriptReturnValue(nEndContinue);
}
void main()
{
//:: Get which event was fired.
int nEvent = GetTagBasedItemEventNumber();
//:: Declare major variables
object oPC;
object oItem;
//:: Our unique power was activated.
if (nEvent == X2_ITEM_EVENT_ACTIVATE)
{
oPC = GetItemActivator();
oItem = GetItemActivated();
object oTarget = GetItemActivatedTarget();
SetLocalObject(oPC, "EXAMINE_TARGET", oTarget);
if(!GetIsDM(oPC))
{
SendMessageToPC(oPC, "This tool is for DM's, not players");
}
else
{
ExecuteScript("prc_playerinfo", oPC);
}
}
//:: Set the return value, and then fall through.
SetTagBasedScriptExitBehavior(X2_EXECUTE_SCRIPT_END);
}

View File

@@ -1,164 +0,0 @@
//::////////////////////////////////////////////////////////
//:: ;-. ,-. ,-. ,-.
//:: | ) | ) / ( )
//:: |-' |-< | ;-:
//:: | | \ \ ( )
//:: ' ' ' `-' `-'
//::///////////////////////////////////////////////////////
//::
/*
Impactscript for Shadow Servant.
(this is handled in the Familiar script)
Shadow Servant (Su): At 1st level, your shadow familiar permanently
transforms into a Medium shadow elemental. It loses all familiar
traits, but gains new abilities as your shadow servant.
Should your shadow servant die, you can summon a replacement after
24 hours pass. Your shadow servant cannot travel farther from you
than 30 feet + 10 feet for each of your master of shadow levels
(40 feet at 1st level and a maximum of 130 feet at 10th level). If
it is forcibly separated from you by more than this distance, the
servant dissipates instantly, and you must wait 24 hours to summon
a new one.
*/
//::
//:://////////////////////////////////////////////
//:: Script: mshadw_shadserv.nss
//:: Author: Jaysyn
//:: Created: 2025-11-11 19:25:58
//:://////////////////////////////////////////////
#include "prc_inc_json"
#include "prc_inc_spells"
const string SHADOW_SERVANT_RESREF = "prc_shadow_serv";
// Watch function: despawns Shadow Servant if master is dead or out of range
void ShadowServantWatch(object oShadow, object oPC)
{
if(DEBUG) DoDebug("mshadw_shadserv >> ShadowServantWatch: Starting function.");
int nMaster = GetLevelByClass(CLASS_TYPE_MASTER_OF_SHADOW, oPC);
float fRange = 30.0 + (nMaster * 10);
if (!GetIsObjectValid(oShadow) || !GetIsObjectValid(oPC)) return;
if (GetIsDead(oPC) ||
GetDistanceBetween(oShadow, oPC) > FeetToMeters(fRange))
{
DestroyObject(oShadow);
return;
}
DelayCommand(1.0, ShadowServantWatch(oShadow, oPC));
}
void main()
{
object oPC = OBJECT_SELF;
int nMaster = GetLevelByClass(CLASS_TYPE_MASTER_OF_SHADOW, oPC);
int nDexBonus = (nMaster >= 5 && (nMaster % 2)) ? (nMaster - 3) : 0;
float fRange = 30.0 + (nMaster * 10);
// Target location
location lTarget = GetSpellTargetLocation();
// Distance check
if (GetDistanceBetweenLocations(GetLocation(oPC), lTarget) > FeetToMeters(fRange))
{
SendMessageToPC(oPC, "That location is too far away.");
return;
}
// Load template
json jShadow = TemplateToJson(SHADOW_SERVANT_RESREF, RESTYPE_UTC);
if (jShadow == JSON_NULL)
{
SendMessageToPC(oPC, "mshdw_shadserv: TemplateToJson failed <20> bad resref or resource missing.");
return;
}
// Original HD
int nOriginalHD = json_GetCreatureHD(jShadow);
if (nOriginalHD <= 0)
{
SendMessageToPC(oPC, "mshdw_shadserv: json_GetCreatureHD failed <20> template missing HD data.");
return;
}
//:: Add Hit Dice
int nHDToAdd = nMaster -1;
if (nHDToAdd < 0) nHDToAdd = 0;
jShadow = json_AddHitDice(jShadow, nHDToAdd);
if (jShadow == JSON_NULL)
{
SendMessageToPC(oPC, "mshdw_shadserv: json_AddHitDice failed - JSON became invalid.");
return;
}
//:: Update feats
jShadow = json_AddFeatsFromCreatureVars(jShadow, nOriginalHD);
if (jShadow == JSON_NULL)
{
SendMessageToPC(oPC, "mshdw_shadserv: json_AddFeatsFromCreatureVars failed <20> JSON became invalid.");
return;
}
//:: Update stats
jShadow = json_ApplyAbilityBoostFromHD(jShadow, nOriginalHD);
if (jShadow == JSON_NULL)
{
SendMessageToPC(oPC, "mshdw_shadserv: json_ApplyAbilityBoostFromHD failed <20> JSON became invalid.");
return;
}
//:: Bonus DEX from Shadow Servant class ability
jShadow = json_UpdateTemplateStats(jShadow, 0, nDexBonus);
// Size increase
if (nMaster > 2)
{
jShadow = json_AdjustCreatureSize(jShadow, 1, TRUE);
if (jShadow == JSON_NULL)
{
SendMessageToPC(oPC, "mshdw_shadserv: json_AdjustCreatureSize failed - JSON became invalid.");
return;
}
}
object oShadow = JsonToObject(jShadow, lTarget);
effect eSummon = ExtraordinaryEffect(EffectSummonCreature("", VFX_FNF_SUMMON_UNDEAD, 0.0, 0, VFX_IMP_UNSUMMON, oShadow));
ApplyEffectAtLocation(DURATION_TYPE_PERMANENT, eSummon, lTarget);
if (!GetIsObjectValid(oShadow))
{
SendMessageToPC(oPC, "mshdw_shadserv: JsonToObject failed - could not create creature from edited template.");
return;
}
// Set faction to caster<65>s
ChangeFaction(oShadow, oPC);
SetLocalObject(oShadow, "ANIMATOR", oPC);
SetCurrentHitPoints(oShadow, GetMaxPossibleHP(oShadow));
effect eGhost = EffectVisualEffect(VFX_DUR_GHOST_TRANSPARENT);
eGhost = UnyieldingEffect(eGhost);
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eGhost, oShadow);
// Start watch loop
DelayCommand(6.1, ShadowServantWatch(oShadow, oPC));
}

View File

@@ -1,5 +1,6 @@
#include "prc_alterations"
#include "prc_compan_inc"
#include "inc_npc"
void main()
{
@@ -11,11 +12,13 @@ void main()
//:: Used for the Twinfiend Pit Fiend summon
int nUltravision = GetLocalInt(oNPC,"INNATE_ULTRAVISION");
if(nUltravision)
{
effect eUltra = EffectUltravision();
eUltra = UnyieldingEffect(eUltra);
DelayCommand(0.0f, ApplyEffectToObject(DURATION_TYPE_PERMANENT, eUltra, oNPC));
}
{
effect eUltra = EffectUltravision();
eUltra = UnyieldingEffect(eUltra);
DelayCommand(0.0f, ApplyEffectToObject(DURATION_TYPE_PERMANENT, eUltra, oNPC));
}
//use companion appearances
/*if(GetPRCSwitch(MARKER_PRC_COMPANION))

View File

@@ -0,0 +1,406 @@
//::////////////////////////////////////////////////////////
//:: ;-. ,-. ,-. ,-.
//:: | ) | ) / ( )
//:: |-' |-< | ;-:
//:: | | \ \ ( )
//:: ' ' ' `-' `-'
//::////////////////////////////////////////////////////////
//:: 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);
}

View File

@@ -0,0 +1,9 @@
#include "inc_dynconv"
void main()
{
object oPC = OBJECT_SELF;
StartDynamicConversation("prc_remo_spl_cv", oPC);
}

View File

@@ -0,0 +1,130 @@
//:://///////////////////////////////////////////////////////////////
//:: End Self-Cast Active Spells - DynConv
//:: prc_remo_spl_fx.nss
//::
//:: Created by: Jaysyn
//:: Created on: 2025-11-22 15:45:50
//:://///////////////////////////////////////////////////////////////
/*
- Lists all active spell effects on and cast by the PC.
- Allows the player to end any of them (removes all effects
- from that spell cast by the PC).
- Start with:
- StartDynamicConversation("prc_remo_spl_cv", OBJECT_SELF);
*/
//:://///////////////////////////////////////////////////////////////
#include "prc_effect_inc"
#include "inc_dynconv"
const int STAGE_ENTRY = 0;
// Get a localized spell name from spells.2da; fallback to "Spell #"
string PRCGetSpellName(int nSpell)
{
string sName = "";
string sRef = Get2DAString("spells", "Name", nSpell);
if (sRef != "")
{
int nStrRef = StringToInt(sRef);
if (nStrRef > 0)
{
sName = GetStringByStrRef(nStrRef);
}
}
if (sName == "")
{
sName = "Spell #" + IntToString(nSpell);
}
return sName;
}
// Build list of unique self-cast active spells on oPC, one choice per spell id.
// Returns count of unique spells found.
int BuildSpellChoiceList(object oPC)
{
int nCount = 0;
array_create(oPC, "PC_SPELL_LIST");
effect e = GetFirstEffect(oPC);
while (GetIsEffectValid(e))
{
int nSpell = GetEffectSpellId(e);
if (nSpell >= 0 && GetEffectCreator(e) == oPC)
{
if (array_get_int(oPC, "PC_SPELL_LIST", nSpell) == 0)
{
AddChoice(PRCGetSpellName(nSpell), nSpell, oPC);
array_set_int(oPC, "PC_SPELL_LIST", nSpell, 1);
nCount++;
}
}
e = GetNextEffect(oPC);
}
array_delete(oPC, "PC_SPELL_LIST");
return nCount;
}
void main()
{
object oPC = GetPCSpeaker();
int nValue = GetLocalInt(oPC, DYNCONV_VARIABLE);
int nStage = GetStage(oPC);
if (nValue == 0)
return;
if (nValue == DYNCONV_SETUP_STAGE)
{
if (!GetIsStageSetUp(nStage, oPC))
{
if (nStage == STAGE_ENTRY)
{
int nList = BuildSpellChoiceList(oPC);
if (nList > 0)
{
SetHeader("Which of your active spells would you like to end:");
}
else
{
SetHeader("You have no self-cast active spells available to end.");
}
MarkStageSetUp(nStage, oPC);
SetDefaultTokens();
}
}
SetupTokens(oPC);
}
else if (nValue == DYNCONV_EXITED)
{
// no-op
}
else if (nValue == DYNCONV_ABORTED)
{
// no-op
}
else
{
// Player selected a spell id
int nSpellId = GetChoice(oPC);
if (nStage == STAGE_ENTRY)
{
// End the spell
PRCRemoveSpellEffects(nSpellId, oPC, oPC);
// Rebuild list
ClearCurrentStage(oPC);
}
SetStage(nStage, oPC);
}
}

View File

@@ -77,7 +77,7 @@ void main()
}
// Target location
location lTarget = GetSpellTargetLocation();
location lTarget = PRCGetSpellTargetLocation();
// Distance check
if (GetDistanceBetweenLocations(GetLocation(oPC), lTarget) > FeetToMeters(TREE_RANGE_FEET))