Reverted to 4.56 to track down a bug that started in 4.57. Bugfixed back to 4.61. It's amazing the damage that one skipped case statement can do.
750 lines
25 KiB
Plaintext
750 lines
25 KiB
Plaintext
//:://////////////////////////////////////////////
|
|
//:: ;-. ,-. ,-. ,-.
|
|
//:: | ) | ) / ( )
|
|
//:: |-' |-< | ;-:
|
|
//:: | | \ \ ( )
|
|
//:: ' ' ' `-' `-'
|
|
//:://////////////////////////////////////////////
|
|
//::
|
|
/*
|
|
Library for json related functions.
|
|
|
|
*/
|
|
//::
|
|
//:://////////////////////////////////////////////
|
|
//:: Script: prc_inc_json.nss
|
|
//:: Author: Jaysyn
|
|
//:: Created: 2025-08-14 12:52:32
|
|
//:://////////////////////////////////////////////
|
|
#include "nw_inc_gff"
|
|
#include "inc_debug"
|
|
|
|
|
|
//::---------------------------------------------|
|
|
//:: Helper functions |
|
|
//::---------------------------------------------|
|
|
|
|
//:: Function to calculate the maximum possible hitpoints for oCreature
|
|
int GetMaxPossibleHP(object oCreature)
|
|
{
|
|
int nMaxHP = 0; // Stores the total maximum hitpoints
|
|
int i = 1; // Initialize position for class index
|
|
int nConb = GetAbilityModifier(ABILITY_CONSTITUTION, oCreature);
|
|
|
|
// Loop through each class position the creature may have, checking each class in turn
|
|
while (TRUE)
|
|
{
|
|
// Get the class ID at position i
|
|
int nClassID = GetClassByPosition(i, oCreature);
|
|
|
|
// If class is invalid (no more classes to check), break out of loop
|
|
if (nClassID == CLASS_TYPE_INVALID)
|
|
break;
|
|
|
|
// Get the number of levels in this class
|
|
int nClassLevels = GetLevelByClass(nClassID, oCreature);
|
|
|
|
// Get the row index of the class in classes.2da by using class ID as the row index
|
|
int nHitDie = StringToInt(Get2DAString("classes", "HitDie", nClassID));
|
|
|
|
// Add maximum HP for this class (Hit Die * number of levels in this class)
|
|
nMaxHP += nClassLevels * nHitDie;
|
|
|
|
// Move to the next class position
|
|
i++;
|
|
}
|
|
|
|
nMaxHP += nConb * GetHitDice(oCreature);
|
|
|
|
return nMaxHP;
|
|
}
|
|
|
|
// Returns how many feats a creature should gain when its HD increases
|
|
int CalculateFeatsFromHD(int nOriginalHD, int nNewHD)
|
|
{
|
|
// HD increase
|
|
int nHDIncrease = nNewHD - nOriginalHD;
|
|
|
|
if (nHDIncrease <= 0)
|
|
return 0; // No new feats if HD did not increase
|
|
|
|
// D&D 3E: 1 feat per 3 HD
|
|
int nBonusFeats = nHDIncrease / 3;
|
|
|
|
return nBonusFeats;
|
|
}
|
|
|
|
// Returns how many stat boosts a creature needs based on its HD
|
|
int GetStatBoostsFromHD(int nCreatureHD, int nModiferCap)
|
|
{
|
|
// Make sure we don't get negative boosts
|
|
int nBoosts = (40 - nCreatureHD) / 4;
|
|
if (nBoosts < 0)
|
|
{
|
|
nBoosts = 0;
|
|
}
|
|
return nBoosts;
|
|
}
|
|
|
|
// Struct to hold size modifiers
|
|
struct SizeModifiers
|
|
{
|
|
int strMod;
|
|
int dexMod;
|
|
int conMod;
|
|
int naturalAC;
|
|
int attackBonus;
|
|
int dexSkillMod;
|
|
};
|
|
|
|
//::---------------------------------------------|
|
|
//:: JSON functions |
|
|
//::---------------------------------------------|
|
|
|
|
//:: Returns the integer value of a VarTable entry named sVarName, or 0 if not found.
|
|
int json_GetLocalIntFromVarTable(json jCreature, string sVarName)
|
|
{
|
|
json jVarTable = GffGetList(jCreature, "VarTable");
|
|
if (jVarTable == JsonNull())
|
|
return 0;
|
|
|
|
int nCount = JsonGetLength(jVarTable);
|
|
int i;
|
|
for (i = 0; i < nCount; i++)
|
|
{
|
|
json jEntry = JsonArrayGet(jVarTable, i);
|
|
if (jEntry == JsonNull()) continue;
|
|
|
|
// Get the Name field using GFF functions
|
|
json jName = GffGetString(jEntry, "Name");
|
|
if (jName == JsonNull()) continue;
|
|
string sName = JsonGetString(jName);
|
|
|
|
if (sName == sVarName)
|
|
{
|
|
// Get the Type field to verify it's an integer
|
|
json jType = GffGetDword(jEntry, "Type");
|
|
if (jType != JsonNull())
|
|
{
|
|
int nType = JsonGetInt(jType);
|
|
if (nType == 1) // Type 1 = integer
|
|
{
|
|
// Get the Value field using GFF functions
|
|
json jValue = GffGetInt(jEntry, "Value");
|
|
if (jValue == JsonNull()) return 0;
|
|
return JsonGetInt(jValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//:: Returns the total Hit Dice from a JSON creature GFF.
|
|
int json_GetCreatureHD(json jGff)
|
|
{
|
|
int nHD = 0;
|
|
|
|
json jClasses = GffGetList(jGff, "ClassList");
|
|
if (jClasses == JsonNull())
|
|
return 0;
|
|
|
|
int nCount = JsonGetLength(jClasses);
|
|
int i;
|
|
for (i = 0; i < nCount; i = i + 1)
|
|
{
|
|
json jClass = JsonArrayGet(jClasses, i);
|
|
if (jClass == JsonNull())
|
|
continue;
|
|
|
|
json jLevel = GffGetShort(jClass, "ClassLevel"); // Use GffGetShort, not GffGetField
|
|
if (jLevel != JsonNull())
|
|
{
|
|
int nLevel = JsonGetInt(jLevel);
|
|
nHD += nLevel;
|
|
}
|
|
}
|
|
|
|
if (nHD <= 0) nHD = 1;
|
|
return nHD;
|
|
}
|
|
|
|
//:: Reads ABILITY_TO_INCREASE from creature's VarTable and applies stat boosts based on increased HD
|
|
json json_ApplyAbilityBoostFromHD(json jCreature, int nOriginalHD, int nModifierCap)
|
|
{
|
|
if (jCreature == JsonNull())
|
|
return jCreature;
|
|
|
|
// Get the ability to increase from VarTable
|
|
int nAbilityToIncrease = json_GetLocalIntFromVarTable(jCreature, "ABILITY_TO_INCREASE");
|
|
if (nAbilityToIncrease < 0 || nAbilityToIncrease > 5)
|
|
{
|
|
if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Invalid ABILITY_TO_INCREASE value: " + IntToString(nAbilityToIncrease));
|
|
return jCreature; // Invalid ability index
|
|
}
|
|
|
|
// Calculate total current HD from ClassList
|
|
json jClassList = GffGetList(jCreature, "ClassList");
|
|
if (jClassList == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Failed to get ClassList");
|
|
return jCreature;
|
|
}
|
|
|
|
int nCurrentTotalHD = 0;
|
|
int nClassCount = JsonGetLength(jClassList);
|
|
int i;
|
|
|
|
for (i = 0; i < nClassCount; i++)
|
|
{
|
|
json jClass = JsonArrayGet(jClassList, i);
|
|
if (jClass != JsonNull())
|
|
{
|
|
json jClassLevel = GffGetShort(jClass, "ClassLevel");
|
|
if (jClassLevel != JsonNull())
|
|
{
|
|
nCurrentTotalHD += JsonGetInt(jClassLevel);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nCurrentTotalHD <= 0)
|
|
{
|
|
if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: No valid Hit Dice found");
|
|
return jCreature;
|
|
}
|
|
|
|
// Calculate stat boosts based on crossing level thresholds
|
|
// Characters get stat boosts at levels 4, 8, 12, 16, 20, etc.
|
|
int nOriginalBoosts = nOriginalHD / 4; // How many boosts they already had
|
|
int nCurrentBoosts = nCurrentTotalHD / 4; // How many they should have now
|
|
int nBoosts = nCurrentBoosts - nOriginalBoosts; // Additional boosts to apply
|
|
|
|
if (nBoosts <= 0)
|
|
{
|
|
if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: No boosts needed (Original boosts: " + IntToString(nOriginalBoosts) + ", Current boosts: " + IntToString(nCurrentBoosts) + ")");
|
|
return jCreature;
|
|
}
|
|
|
|
if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Applying " + IntToString(nBoosts) + " boosts to ability " + IntToString(nAbilityToIncrease) + " for HD increase from " + IntToString(nOriginalHD) + " to " + IntToString(nCurrentTotalHD));
|
|
|
|
// Determine which ability to boost and apply the increases
|
|
string sAbilityField;
|
|
switch (nAbilityToIncrease)
|
|
{
|
|
case 0: sAbilityField = "Str"; break;
|
|
case 1: sAbilityField = "Dex"; break;
|
|
case 2: sAbilityField = "Con"; break;
|
|
case 3: sAbilityField = "Int"; break;
|
|
case 4: sAbilityField = "Wis"; break;
|
|
case 5: sAbilityField = "Cha"; break;
|
|
default:
|
|
if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Unknown ability index: " + IntToString(nAbilityToIncrease));
|
|
return jCreature;
|
|
}
|
|
|
|
// Get current ability score
|
|
json jCurrentAbility = GffGetByte(jCreature, sAbilityField);
|
|
if (jCurrentAbility == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Failed to get " + sAbilityField + " score");
|
|
return jCreature;
|
|
}
|
|
|
|
int nCurrentScore = JsonGetInt(jCurrentAbility);
|
|
int nNewScore = nCurrentScore + nBoosts;
|
|
|
|
// Clamp to valid byte range
|
|
if (nNewScore < 1) nNewScore = 1;
|
|
if (nNewScore > 255) nNewScore = 255;
|
|
|
|
if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Increasing " + sAbilityField + " from " + IntToString(nCurrentScore) + " to " + IntToString(nNewScore));
|
|
|
|
// Apply the ability score increase
|
|
jCreature = GffReplaceByte(jCreature, sAbilityField, nNewScore);
|
|
if (jCreature == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Failed to update " + sAbilityField);
|
|
return JsonNull();
|
|
}
|
|
|
|
if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Successfully applied ability boosts");
|
|
return jCreature;
|
|
}
|
|
|
|
//:: Adjust a skill by its ID (more efficient than name lookup)
|
|
json json_AdjustCreatureSkillByID(json jCreature, int nSkillID, int nMod)
|
|
{
|
|
// Get the SkillList
|
|
json jSkillList = GffGetList(jCreature, "SkillList");
|
|
if (jSkillList == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to get SkillList");
|
|
return jCreature;
|
|
}
|
|
|
|
// Check if we have enough skills in the list
|
|
int nSkillCount = JsonGetLength(jSkillList);
|
|
if (nSkillID >= nSkillCount)
|
|
{
|
|
if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Skill ID " + IntToString(nSkillID) + " exceeds skill list length " + IntToString(nSkillCount));
|
|
return jCreature;
|
|
}
|
|
|
|
// Get the skill struct at the correct index
|
|
json jSkill = JsonArrayGet(jSkillList, nSkillID);
|
|
if (jSkill == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to get skill at index " + IntToString(nSkillID));
|
|
return jCreature;
|
|
}
|
|
|
|
// Get current rank
|
|
json jRank = GffGetByte(jSkill, "Rank");
|
|
if (jRank == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to get Rank for skill ID " + IntToString(nSkillID));
|
|
return jCreature;
|
|
}
|
|
|
|
int nCurrentRank = JsonGetInt(jRank);
|
|
int nNewRank = nCurrentRank + nMod;
|
|
|
|
// Clamp to valid range
|
|
if (nNewRank < 0) nNewRank = 0;
|
|
if (nNewRank > 255) nNewRank = 255;
|
|
|
|
// Update the rank in the skill struct
|
|
jSkill = GffReplaceByte(jSkill, "Rank", nNewRank);
|
|
if (jSkill == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to replace Rank for skill ID " + IntToString(nSkillID));
|
|
return JsonNull();
|
|
}
|
|
|
|
// Replace the skill in the array
|
|
jSkillList = JsonArraySet(jSkillList, nSkillID, jSkill);
|
|
|
|
// Replace the SkillList in the creature
|
|
jCreature = GffReplaceList(jCreature, "SkillList", jSkillList);
|
|
|
|
return jCreature;
|
|
}
|
|
|
|
//:: Reads FutureFeat1..FutureFeatN from the template's VarTable and appends them to FeatList if missing.
|
|
json json_AddFeatsFromCreatureVars(json jCreature, int nOriginalHD)
|
|
{
|
|
if (jCreature == JsonNull())
|
|
return jCreature;
|
|
|
|
// Calculate current total HD
|
|
int nCurrentHD = json_GetCreatureHD(jCreature);
|
|
int nAddedHD = nCurrentHD - nOriginalHD;
|
|
|
|
if (nAddedHD <= 0)
|
|
{
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: No additional HD to process (Current: " + IntToString(nCurrentHD) + ", Original: " + IntToString(nOriginalHD) + ")");
|
|
return jCreature;
|
|
}
|
|
|
|
// Calculate how many feats the creature should get based on added HD
|
|
// Characters get a feat at levels 1, 3, 6, 9, 12, 15, 18, etc.
|
|
// For added levels, we need to check what feat levels they cross
|
|
int nOriginalFeats = (nOriginalHD + 2) / 3; // Feats from original HD
|
|
int nCurrentFeats = (nCurrentHD + 2) / 3; // Feats from current HD
|
|
int nNumFeats = nCurrentFeats - nOriginalFeats; // Additional feats earned
|
|
|
|
if (nNumFeats <= 0)
|
|
{
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: No additional feats earned from " + IntToString(nAddedHD) + " added HD");
|
|
return jCreature;
|
|
}
|
|
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Processing " + IntToString(nNumFeats) + " feats for " + IntToString(nAddedHD) + " added HD (Original: " + IntToString(nOriginalHD) + ", Current: " + IntToString(nCurrentHD) + ")");
|
|
|
|
// Get or create FeatList
|
|
json jFeatArray = GffGetList(jCreature, "FeatList");
|
|
if (jFeatArray == JsonNull())
|
|
jFeatArray = JsonArray();
|
|
|
|
int nOriginalFeatCount = JsonGetLength(jFeatArray);
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Original feat count: " + IntToString(nOriginalFeatCount));
|
|
|
|
int nAdded = 0;
|
|
int i = 1;
|
|
int nMaxIterations = 100; // Safety valve
|
|
int nIterations = 0;
|
|
|
|
while (nAdded < nNumFeats && nIterations < nMaxIterations)
|
|
{
|
|
nIterations++;
|
|
string sVarName = "FutureFeat" + IntToString(i);
|
|
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Checking " + sVarName);
|
|
|
|
int nFeat = json_GetLocalIntFromVarTable(jCreature, sVarName);
|
|
|
|
if (nFeat <= 0)
|
|
{
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: " + sVarName + " not found or invalid");
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Found " + sVarName + " = " + IntToString(nFeat));
|
|
|
|
// Check if feat already exists
|
|
int bHasFeat = FALSE;
|
|
int nFeatCount = JsonGetLength(jFeatArray);
|
|
int j;
|
|
|
|
for (j = 0; j < nFeatCount; j++)
|
|
{
|
|
json jFeatStruct = JsonArrayGet(jFeatArray, j);
|
|
if (jFeatStruct != JsonNull())
|
|
{
|
|
json jFeatValue = GffGetWord(jFeatStruct, "Feat");
|
|
if (jFeatValue != JsonNull() && JsonGetInt(jFeatValue) == nFeat)
|
|
{
|
|
bHasFeat = TRUE;
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Feat " + IntToString(nFeat) + " already exists");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Insert if missing
|
|
if (!bHasFeat)
|
|
{
|
|
json jNewFeat = JsonObject();
|
|
jNewFeat = JsonObjectSet(jNewFeat, "__struct_id", JsonInt(1));
|
|
jNewFeat = GffAddWord(jNewFeat, "Feat", nFeat);
|
|
|
|
if (jNewFeat == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Failed to create feat struct for feat " + IntToString(nFeat));
|
|
break;
|
|
}
|
|
|
|
jFeatArray = JsonArrayInsert(jFeatArray, jNewFeat);
|
|
nAdded++;
|
|
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Added feat " + IntToString(nFeat) + " (" + IntToString(nAdded) + "/" + IntToString(nNumFeats) + ")");
|
|
}
|
|
|
|
i++;
|
|
|
|
// Safety break if we've checked too many variables
|
|
if (i > 100)
|
|
{
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Safety break - checked too many FutureFeat variables");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Completed. Added " + IntToString(nAdded) + " feats in " + IntToString(nIterations) + " iterations");
|
|
|
|
// Save back the modified FeatList only if we added something
|
|
if (nAdded > 0)
|
|
{
|
|
jCreature = GffReplaceList(jCreature, "FeatList", jFeatArray);
|
|
if (jCreature == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Failed to replace FeatList");
|
|
return JsonNull();
|
|
}
|
|
}
|
|
|
|
return jCreature;
|
|
}
|
|
|
|
//:: Get the size of a JSON array
|
|
int json_GetArraySize(json jArray)
|
|
{
|
|
int iSize = 0;
|
|
while (JsonArrayGet(jArray, iSize) != JsonNull())
|
|
{
|
|
iSize++;
|
|
}
|
|
return iSize;
|
|
}
|
|
|
|
//:: Directly modifies oCreature's Base Natural AC if iNewAC is higher.
|
|
//::
|
|
json json_UpdateBaseAC(json jCreature, int iNewAC)
|
|
{
|
|
//json jBaseAC = GffGetByte(jCreature, "Creature/value/NaturalAC/value");
|
|
json jBaseAC = GffGetByte(jCreature, "NaturalAC");
|
|
|
|
if (jBaseAC == JsonNull())
|
|
{
|
|
return JsonNull();
|
|
}
|
|
else if (JsonGetInt(jBaseAC) > iNewAC)
|
|
{
|
|
return jCreature;
|
|
}
|
|
else
|
|
{
|
|
jCreature = GffReplaceByte(jCreature, "NaturalAC", iNewAC);
|
|
|
|
return jCreature;
|
|
}
|
|
}
|
|
|
|
//:: Directly modifies jCreature's Challenge Rating.
|
|
//:: This is useful for most XP calculations.
|
|
json json_UpdateCR(json jCreature, int nBaseCR, int nCRMod)
|
|
{
|
|
int nNewCR;
|
|
|
|
//:: Add CRMod to current CR
|
|
nNewCR = nBaseCR + nCRMod;
|
|
|
|
//:: Modify Challenge Rating
|
|
jCreature = GffReplaceFloat(jCreature, "ChallengeRating", IntToFloat(nNewCR));
|
|
|
|
return jCreature;
|
|
}
|
|
|
|
//:: Directly modifies ability scores in a creature's JSON GFF.
|
|
//::
|
|
json json_UpdateTemplateStats(json jCreature, int iModStr = 0, int iModDex = 0, int iModCon = 0,
|
|
int iModInt = 0, int iModWis = 0, int iModCha = 0)
|
|
{
|
|
int iCurrent;
|
|
|
|
// STR
|
|
if (!GffGetFieldExists(jCreature, "Str", GFF_FIELD_TYPE_BYTE))
|
|
jCreature = GffAddByte(jCreature, "Str", 10);
|
|
iCurrent = JsonGetInt(GffGetByte(jCreature, "Str"));
|
|
jCreature = GffReplaceByte(jCreature, "Str", iCurrent + iModStr);
|
|
|
|
// DEX
|
|
if (!GffGetFieldExists(jCreature, "Dex", GFF_FIELD_TYPE_BYTE))
|
|
jCreature = GffAddByte(jCreature, "Dex", 10);
|
|
iCurrent = JsonGetInt(GffGetByte(jCreature, "Dex"));
|
|
jCreature = GffReplaceByte(jCreature, "Dex", iCurrent + iModDex);
|
|
|
|
// CON
|
|
if (!GffGetFieldExists(jCreature, "Con", GFF_FIELD_TYPE_BYTE))
|
|
jCreature = GffAddByte(jCreature, "Con", 10);
|
|
iCurrent = JsonGetInt(GffGetByte(jCreature, "Con"));
|
|
jCreature = GffReplaceByte(jCreature, "Con", iCurrent + iModCon);
|
|
|
|
// INT
|
|
if (!GffGetFieldExists(jCreature, "Int", GFF_FIELD_TYPE_BYTE))
|
|
jCreature = GffAddByte(jCreature, "Int", 10);
|
|
iCurrent = JsonGetInt(GffGetByte(jCreature, "Int"));
|
|
jCreature = GffReplaceByte(jCreature, "Int", iCurrent + iModInt);
|
|
|
|
// WIS
|
|
if (!GffGetFieldExists(jCreature, "Wis", GFF_FIELD_TYPE_BYTE))
|
|
jCreature = GffAddByte(jCreature, "Wis", 10);
|
|
iCurrent = JsonGetInt(GffGetByte(jCreature, "Wis"));
|
|
jCreature = GffReplaceByte(jCreature, "Wis", iCurrent + iModWis);
|
|
|
|
// CHA
|
|
if (!GffGetFieldExists(jCreature, "Cha", GFF_FIELD_TYPE_BYTE))
|
|
jCreature = GffAddByte(jCreature, "Cha", 10);
|
|
iCurrent = JsonGetInt(GffGetByte(jCreature, "Cha"));
|
|
jCreature = GffReplaceByte(jCreature, "Cha", iCurrent + iModCha);
|
|
|
|
return jCreature;
|
|
}
|
|
|
|
//:: Directly modifies oCreature's ability scores.
|
|
//::
|
|
json json_UpdateCreatureStats(json jCreature, object oBaseCreature, int iModStr = 0, int iModDex = 0, int iModCon = 0, int iModInt = 0, int iModWis = 0, int iModCha = 0)
|
|
{
|
|
//:: Retrieve and modify ability scores
|
|
int iCurrentStr = GetAbilityScore(oBaseCreature, ABILITY_STRENGTH);
|
|
int iCurrentDex = GetAbilityScore(oBaseCreature, ABILITY_DEXTERITY);
|
|
int iCurrentCon = GetAbilityScore(oBaseCreature, ABILITY_CONSTITUTION);
|
|
int iCurrentInt = GetAbilityScore(oBaseCreature, ABILITY_INTELLIGENCE);
|
|
int iCurrentWis = GetAbilityScore(oBaseCreature, ABILITY_WISDOM);
|
|
int iCurrentCha = GetAbilityScore(oBaseCreature, ABILITY_CHARISMA);
|
|
|
|
jCreature = GffReplaceByte(jCreature, "Str", iCurrentStr + iModStr);
|
|
jCreature = GffReplaceByte(jCreature, "Dex", iCurrentDex + iModDex);
|
|
jCreature = GffReplaceByte(jCreature, "Con", iCurrentCon + iModCon);
|
|
jCreature = GffReplaceByte(jCreature, "Int", iCurrentInt + iModInt);
|
|
jCreature = GffReplaceByte(jCreature, "Wis", iCurrentWis + iModWis);
|
|
jCreature = GffReplaceByte(jCreature, "Cha", iCurrentCha + iModCha);
|
|
|
|
return jCreature;
|
|
}
|
|
|
|
//:: Increases a creature's Hit Dice in its JSON GFF data by nAmount
|
|
json json_AddHitDice(json jCreature, int nAmount)
|
|
{
|
|
if (jCreature == JsonNull() || nAmount <= 0)
|
|
return jCreature;
|
|
|
|
// Get the ClassList
|
|
json jClasses = GffGetList(jCreature, "ClassList");
|
|
if (jClasses == JsonNull() || JsonGetLength(jClasses) == 0)
|
|
return jCreature;
|
|
|
|
// Grab the first class entry
|
|
json jFirstClass = JsonArrayGet(jClasses, 0);
|
|
|
|
// Only touch ClassLevel; do NOT modify Class type
|
|
json jCurrentLevel = GffGetShort(jFirstClass, "ClassLevel");
|
|
int nCurrentLevel = JsonGetInt(jCurrentLevel);
|
|
int nNewLevel = nCurrentLevel + nAmount;
|
|
|
|
// Replace ClassLevel only
|
|
jFirstClass = GffReplaceShort(jFirstClass, "ClassLevel", nNewLevel);
|
|
|
|
// Put modified class back into the array
|
|
jClasses = JsonArraySet(jClasses, 0, jFirstClass);
|
|
|
|
// Replace ClassList in the creature JSON
|
|
jCreature = GffReplaceList(jCreature, "ClassList", jClasses);
|
|
|
|
return jCreature;
|
|
}
|
|
|
|
//:: Adjusts a creature's size by nSizeChange (-4 to +4) and updates ability scores accordingly.
|
|
json json_AdjustCreatureSize(json jCreature, int nSizeDelta)
|
|
{
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Entering function. nSizeDelta=" + IntToString(nSizeDelta));
|
|
|
|
if (jCreature == JsonNull() || nSizeDelta == 0)
|
|
{
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Exiting: jCreature is null or nSizeDelta is 0");
|
|
return jCreature;
|
|
}
|
|
|
|
// Get Appearance_Type using GFF functions
|
|
json jAppearanceType = GffGetWord(jCreature, "Appearance_Type");
|
|
if (jAppearanceType == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to get Appearance_Type");
|
|
return jCreature;
|
|
}
|
|
|
|
int nAppearance = JsonGetInt(jAppearanceType);
|
|
int nCurrentSize = StringToInt(Get2DAString("appearances", "Size", nAppearance));
|
|
|
|
// Default to Medium (4) if invalid
|
|
if (nCurrentSize < 0 || nCurrentSize > 8) nCurrentSize = 4;
|
|
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Appearance_Type =" + IntToString(nAppearance) + ", Size =" + IntToString(nCurrentSize));
|
|
|
|
int nSteps = nSizeDelta;
|
|
|
|
// Calculate modifiers based on size change
|
|
int strMod = nSteps * 4;
|
|
int dexMod = nSteps * -1;
|
|
int conMod = nSteps * 2;
|
|
int naturalAC = nSteps * 1;
|
|
int dexSkillMod = nSteps * -2;
|
|
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Applying stat modifiers: STR=" + IntToString(strMod) +
|
|
" DEX=" + IntToString(dexMod) +
|
|
" CON=" + IntToString(conMod));
|
|
|
|
// Update ability scores using GFF functions with error checking
|
|
json jStr = GffGetByte(jCreature, "Str");
|
|
if (jStr != JsonNull())
|
|
{
|
|
int nNewStr = JsonGetInt(jStr) + strMod;
|
|
if (nNewStr < 1) nNewStr = 1;
|
|
if (nNewStr > 255) nNewStr = 255;
|
|
|
|
jCreature = GffReplaceByte(jCreature, "Str", nNewStr);
|
|
if (jCreature == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update Str");
|
|
return JsonNull();
|
|
}
|
|
}
|
|
|
|
json jDex = GffGetByte(jCreature, "Dex");
|
|
if (jDex != JsonNull())
|
|
{
|
|
int nNewDex = JsonGetInt(jDex) + dexMod;
|
|
if (nNewDex < 1) nNewDex = 1;
|
|
if (nNewDex > 255) nNewDex = 255;
|
|
|
|
jCreature = GffReplaceByte(jCreature, "Dex", nNewDex);
|
|
if (jCreature == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update Dex");
|
|
return JsonNull();
|
|
}
|
|
}
|
|
|
|
json jCon = GffGetByte(jCreature, "Con");
|
|
if (jCon != JsonNull())
|
|
{
|
|
int nNewCon = JsonGetInt(jCon) + conMod;
|
|
if (nNewCon < 1) nNewCon = 1;
|
|
if (nNewCon > 255) nNewCon = 255;
|
|
|
|
jCreature = GffReplaceByte(jCreature, "Con", nNewCon);
|
|
if (jCreature == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update Con");
|
|
return JsonNull();
|
|
}
|
|
}
|
|
|
|
// Update Natural AC
|
|
json jNaturalAC = GffGetByte(jCreature, "NaturalAC");
|
|
if (jNaturalAC != JsonNull())
|
|
{
|
|
int nCurrentNA = JsonGetInt(jNaturalAC);
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Current NaturalAC: " + IntToString(nCurrentNA));
|
|
|
|
int nNewNA = nCurrentNA + naturalAC;
|
|
if (nNewNA < 0) nNewNA = 0;
|
|
if (nNewNA > 255) nNewNA = 255;
|
|
|
|
jCreature = GffReplaceByte(jCreature, "NaturalAC", nNewNA);
|
|
if (jCreature == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update NaturalAC");
|
|
return JsonNull();
|
|
}
|
|
}
|
|
|
|
// Adjust all Dexterity-based skills by finding them in skills.2da
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Adjusting DEX-based skills");
|
|
|
|
int nSkillID = 0;
|
|
while (TRUE)
|
|
{
|
|
string sKeyAbility = Get2DAString("skills", "KeyAbility", nSkillID);
|
|
|
|
// Break when we've reached the end of skills
|
|
if (sKeyAbility == "")
|
|
break;
|
|
|
|
// If this skill uses Dexterity, adjust it
|
|
if (sKeyAbility == "DEX")
|
|
{
|
|
string sSkillLabel = Get2DAString("skills", "Label", nSkillID);
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Adjusting DEX skill: " + sSkillLabel + " (ID: " + IntToString(nSkillID) + ")");
|
|
|
|
jCreature = json_AdjustCreatureSkillByID(jCreature, nSkillID, dexSkillMod);
|
|
if (jCreature == JsonNull())
|
|
{
|
|
if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed adjusting skill ID " + IntToString(nSkillID));
|
|
return JsonNull();
|
|
}
|
|
}
|
|
|
|
nSkillID++;
|
|
}
|
|
|
|
if(DEBUG) DoDebug("json_AdjustCreatureSize completed successfully");
|
|
return jCreature;
|
|
}
|
|
|
|
|
|
//:: Test void
|
|
//:: void main (){} |