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.
1605 lines
50 KiB
Plaintext
1605 lines
50 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"
|
||
#include "prc_inc_racial"
|
||
#include "prc_inc_nwscript"
|
||
#include "prc_inc_spells"
|
||
#include "prc_inc_util"
|
||
#include "prc_inc_fork"
|
||
#include "prc_inc_natweap"
|
||
|
||
//:: Get a random General feat.
|
||
void ApplyParagonBonusFeat(object oCreature, int iFeat);
|
||
|
||
//::---------------------------------------------|
|
||
//:: 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);
|
||
int nRacial = MyPRCGetRacialType(oCreature);
|
||
int nSize = PRCGetCreatureSize(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++;
|
||
}
|
||
|
||
if(nRacial == RACIAL_TYPE_CONSTRUCT || nRacial == RACIAL_TYPE_UNDEAD)
|
||
{
|
||
nConb = 0;
|
||
}
|
||
|
||
nMaxHP += nConb * GetHitDice(oCreature);
|
||
|
||
if(nRacial == RACIAL_TYPE_CONSTRUCT)
|
||
{
|
||
if(nSize == CREATURE_SIZE_FINE) nMaxHP += 0;
|
||
if(nSize == CREATURE_SIZE_DIMINUTIVE) nMaxHP += 0;
|
||
if(nSize == CREATURE_SIZE_TINY) nMaxHP += 0;
|
||
if(nSize == CREATURE_SIZE_SMALL) nMaxHP += 10;
|
||
if(nSize == CREATURE_SIZE_MEDIUM) nMaxHP += 20;
|
||
if(nSize == CREATURE_SIZE_LARGE) nMaxHP += 30;
|
||
if(nSize == CREATURE_SIZE_HUGE) nMaxHP += 40;
|
||
if(nSize == CREATURE_SIZE_GARGANTUAN) nMaxHP += 60;
|
||
}
|
||
|
||
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;
|
||
};
|
||
|
||
//:: Returns ability mod for score
|
||
int GetAbilityModFromValue(int nAbilityValue)
|
||
{
|
||
int nMod = (nAbilityValue - 10) / 2;
|
||
|
||
// Adjust if below 10 and odd
|
||
if (nAbilityValue < 10 && (nAbilityValue % 2) != 0)
|
||
{
|
||
nMod = nMod - 1;
|
||
}
|
||
return nMod;
|
||
}
|
||
|
||
//:: Get a random General feat.
|
||
void PickParagonBonusFeat(object oCreature)
|
||
{
|
||
//:: Paragon creatures get a +15 to all ability scores,
|
||
//:: so can always meet feat pre-reqs.
|
||
|
||
//:: Detect spellcasting classes (FOR FUTURE USE)
|
||
int i;
|
||
for (i = 1; i <= 8; i++)
|
||
{
|
||
if (GetIsArcaneClass(GetClassByPosition(i, oCreature)))
|
||
{
|
||
SetLocalInt(oCreature, "ParagonArcaneCaster", 0);
|
||
}
|
||
if (GetIsDivineClass(GetClassByPosition(i, oCreature)))
|
||
{
|
||
SetLocalInt(oCreature, "ParagonDivineCaster", 0);
|
||
}
|
||
}
|
||
switch (Random(18))
|
||
{
|
||
//:: Dodge -> Mobility -> Spring Attack
|
||
case 0:
|
||
{
|
||
int iDodge = GetHasFeat(FEAT_DODGE, oCreature);
|
||
int iMobility = GetHasFeat(FEAT_MOBILITY, oCreature);
|
||
int iSpringAttack = GetHasFeat(FEAT_SPRING_ATTACK, oCreature);
|
||
|
||
//:: Grant only the first missing feat in the chain
|
||
if (iDodge == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_DODGE);
|
||
}
|
||
else if (iMobility == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_MOBILITY);
|
||
}
|
||
else if (iSpringAttack == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_SPRING_ATTACK);
|
||
}
|
||
}
|
||
break;
|
||
//:: Power Attack -> Cleave -> Imp Power Attack -> Great Cleave
|
||
case 1:
|
||
{
|
||
int iPower = GetHasFeat(FEAT_POWER_ATTACK, oCreature);
|
||
int iCleave = GetHasFeat(FEAT_CLEAVE, oCreature);
|
||
int iImpPower = GetHasFeat(FEAT_IMPROVED_POWER_ATTACK, oCreature);
|
||
int iGrCleave = GetHasFeat(FEAT_GREAT_CLEAVE, oCreature);
|
||
|
||
//:: Grant only the first missing feat in the chain
|
||
if (iPower == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_POWER_ATTACK);
|
||
}
|
||
else if (iCleave == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_CLEAVE);
|
||
}
|
||
else if (iImpPower == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_POWER_ATTACK);
|
||
}
|
||
else if (iGrCleave == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_GREAT_CLEAVE);
|
||
}
|
||
}
|
||
break;
|
||
//:: Expertise -> Imp Expertise -> Whirlwind Attack -> Imp Whirlwind Attack
|
||
case 2:
|
||
{
|
||
int iEx = GetHasFeat(FEAT_EXPERTISE, oCreature);
|
||
int iImpEx = GetHasFeat(FEAT_IMPROVED_EXPERTISE, oCreature);
|
||
int iWhirl = GetHasFeat(FEAT_WHIRLWIND_ATTACK, oCreature);
|
||
int iImpWhirl = GetHasFeat(FEAT_IMPROVED_WHIRLWIND, oCreature);
|
||
|
||
//:: Grant only the first missing feat in the chain
|
||
if (iEx == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_EXPERTISE);
|
||
}
|
||
else if (iImpEx == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_EXPERTISE);
|
||
}
|
||
else if (iWhirl == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_WHIRLWIND_ATTACK);
|
||
}
|
||
else if (iImpWhirl == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_WHIRLWIND);
|
||
}
|
||
}
|
||
break;
|
||
//:: Disarm -> Expertise -> Improved Disarm -> Imp Expertise
|
||
case 3:
|
||
{
|
||
int iDisarm = GetHasFeat(FEAT_DISARM, oCreature);
|
||
int iEx = GetHasFeat(FEAT_EXPERTISE, oCreature);
|
||
int iImpDisarm = GetHasFeat(FEAT_IMPROVED_DISARM, oCreature);
|
||
int iImpEx = GetHasFeat(FEAT_IMPROVED_EXPERTISE, oCreature);
|
||
|
||
//:: Grant only the first missing feat in the chain
|
||
if (iDisarm == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_DISARM);
|
||
}
|
||
else if (iEx == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_EXPERTISE);
|
||
}
|
||
else if (iImpDisarm == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_DISARM);
|
||
}
|
||
else if (iImpEx == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_EXPERTISE);
|
||
}
|
||
}
|
||
break;
|
||
//:: Toughness
|
||
case 4:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_TOUGHNESS);
|
||
}
|
||
break;
|
||
//:: Great Fortitude
|
||
case 5:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_GREAT_FORTITUDE);
|
||
}
|
||
break;
|
||
//:: Lightining Reflexes
|
||
case 6:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_LIGHTNING_REFLEXES);
|
||
}
|
||
break;
|
||
//:: Iron Will -> Unnatural Will
|
||
case 7:
|
||
{
|
||
int iIronWill = GetHasFeat(FEAT_IRON_WILL, oCreature);
|
||
int iUnnaturalWill = GetHasFeat(FEAT_UNNATURAL_WILL, oCreature);
|
||
|
||
//:: Grant only the first missing feat in the chain
|
||
if (iIronWill == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_IRON_WILL);
|
||
}
|
||
else if (iUnnaturalWill == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_UNNATURAL_WILL);
|
||
}
|
||
}
|
||
break;
|
||
//:: Blind-Fight
|
||
case 8:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_BLIND_FIGHT);
|
||
}
|
||
break;
|
||
//:: Improved Initiative
|
||
case 9:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_INITIATIVE);
|
||
}
|
||
break;
|
||
//:: Alertness
|
||
case 10:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_ALERTNESS);
|
||
}
|
||
break;
|
||
//:: Blooded
|
||
case 11:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_BLOODED);
|
||
}
|
||
break;
|
||
//:: Side-step Charge
|
||
case 12:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_SIDESTEP_CHARGE);
|
||
}
|
||
break;
|
||
//:: Thug
|
||
case 13:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_THUG);
|
||
}
|
||
break;
|
||
//:: Dive for Cover
|
||
case 14:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_DIVE_FOR_COVER);
|
||
}
|
||
break;
|
||
//:: Endurance -> Strong Stomach
|
||
case 15:
|
||
{
|
||
int iEndurance = GetHasFeat(FEAT_ENDURANCE, oCreature);
|
||
int iStrStomach = GetHasFeat(FEAT_STRONG_STOMACH, oCreature);
|
||
|
||
//:: Grant only the first missing feat in the chain
|
||
if (iEndurance == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_ENDURANCE);
|
||
}
|
||
else if (iStrStomach == 0)
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_STRONG_STOMACH);
|
||
}
|
||
}
|
||
break;
|
||
//:: Resist Disease
|
||
case 16:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_RESIST_DISEASE);
|
||
}
|
||
break;
|
||
//:: Resist Poison
|
||
case 17:
|
||
{
|
||
ApplyParagonBonusFeat(oCreature, FEAT_RESIST_POISON);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
//:: Check & apply the feat using EffectBonusFeat if it
|
||
//:: doesn't exist on the creature already
|
||
void ApplyParagonBonusFeat(object oCreature, int iFeat)
|
||
{
|
||
// If the creature does not already have the feat, apply it
|
||
if (!GetHasFeat(iFeat, oCreature))
|
||
{
|
||
effect eFeat = EffectBonusFeat(iFeat);
|
||
effect eLink = UnyieldingEffect(eFeat);
|
||
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eLink, oCreature);
|
||
}
|
||
else
|
||
{
|
||
DelayCommand(0.0f, PickParagonBonusFeat(oCreature));
|
||
}
|
||
}
|
||
|
||
//:: Apply Paragon effects to a non-PC creature
|
||
void ApplyParagonEffects(object oCreature, int nBaseHD, int nBaseCR)
|
||
{
|
||
//:: Declare major variables
|
||
int nNewCR;
|
||
|
||
effect eParagon;
|
||
|
||
//:: Set maximum hit points for each HD
|
||
int nParagonHP = (GetMaxPossibleHP(oCreature) + (nBaseHD * GetAbilityModifier(ABILITY_CONSTITUTION, oCreature)));
|
||
SetCurrentHitPoints(oCreature, nParagonHP);
|
||
|
||
//:: Tripling the speed for all movement types
|
||
eParagon = EffectLinkEffects(eParagon, EffectMovementSpeedIncrease(300));
|
||
|
||
//:: +25 luck bonus on all attack rolls
|
||
eParagon = EffectLinkEffects(eParagon, EffectAttackIncrease(25));
|
||
|
||
//:: +20 luck bonus on damage rolls for melee and thrown ranged attacks
|
||
eParagon = EffectLinkEffects(eParagon, EffectDamageIncrease(20));
|
||
|
||
//:: AC Bonuses: +12 insight, +12 luck
|
||
eParagon = EffectLinkEffects(eParagon, EffectACIncrease(12, AC_DODGE_BONUS));
|
||
eParagon = EffectLinkEffects(eParagon, EffectACIncrease(12, AC_DEFLECTION_BONUS));
|
||
|
||
//:: Boost caster & SLA level by 15
|
||
SetLocalInt(oCreature, PRC_CASTERLEVEL_ADJUSTMENT, 15);
|
||
|
||
//:: Fire and cold resistance 10, or keep the higher existing resistance if applicable
|
||
eParagon = EffectLinkEffects(eParagon, EffectDamageResistance(DAMAGE_TYPE_FIRE, 10));
|
||
eParagon = EffectLinkEffects(eParagon, EffectDamageResistance(DAMAGE_TYPE_COLD, 10));
|
||
|
||
//:: Damage Reduction 20/epic or retain existing DR if higher
|
||
eParagon = EffectLinkEffects(eParagon, EffectDamageReduction(20, DAMAGE_POWER_ENERGY));
|
||
|
||
//:: Spell Resistance equal to CR +10, or retain existing SR if higher
|
||
int iExSR = GetSpellResistance(oCreature);
|
||
int nSpellResistance;
|
||
|
||
if (iExSR < nBaseCR + 10)
|
||
{
|
||
nSpellResistance = nBaseCR + 10;
|
||
}
|
||
else
|
||
{
|
||
nSpellResistance = 0;
|
||
}
|
||
|
||
eParagon = EffectLinkEffects(eParagon, EffectSpellResistanceIncrease(nSpellResistance));
|
||
|
||
//:: Fast Healing 20
|
||
eParagon = EffectLinkEffects(eParagon, EffectRegenerate(20, 6.0f));
|
||
|
||
//:: Saving Throws: +10 insight bonus on all saving throws
|
||
eParagon = EffectLinkEffects(eParagon, EffectSavingThrowIncrease(SAVING_THROW_ALL, 10));
|
||
|
||
//:: Skills: +10 competence bonus to all skill checks
|
||
int nSkillID = 0;
|
||
|
||
while (TRUE)
|
||
{
|
||
//:: Get & check skill
|
||
string sSkillLabel = Get2DACache("skills", "Label", nSkillID);
|
||
|
||
//:: Break when out of skills
|
||
if (sSkillLabel == "")
|
||
break;
|
||
|
||
//:: Apply the skill increase effect for the current skill
|
||
eParagon = EffectLinkEffects(eParagon, EffectSkillIncrease(nSkillID, 10));
|
||
|
||
|
||
//:: Move to the next skill ID
|
||
nSkillID++;
|
||
}
|
||
|
||
//:: Two free general feats.
|
||
PickParagonBonusFeat(oCreature);
|
||
PickParagonBonusFeat(oCreature);
|
||
|
||
eParagon = UnyieldingEffect(eParagon);
|
||
|
||
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eParagon, oCreature);
|
||
}
|
||
|
||
void ReallyEquipItemInSlot(object oNPC, object oItem, int nSlot)
|
||
{
|
||
if (GetItemInSlot(nSlot) != oItem)
|
||
{
|
||
//ClearAllActions();
|
||
AssignCommand(oNPC, ActionEquipItem(oItem, nSlot));
|
||
DelayCommand(0.5, ReallyEquipItemInSlot(oNPC, oItem, nSlot));
|
||
}
|
||
}
|
||
|
||
// Get the size of a JSON array
|
||
int GetJsonArraySize(json jArray)
|
||
{
|
||
int iSize = 0;
|
||
while (JsonArrayGet(jArray, iSize) != JsonNull())
|
||
{
|
||
iSize++;
|
||
}
|
||
return iSize;
|
||
}
|
||
|
||
int CheckForWeapon(object oCreature)
|
||
{
|
||
if (GetIsWeapon(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature)) == 1 || GetIsWeapon(GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oCreature)) == 1)
|
||
{
|
||
// oCreature has a weapon in at least one hand
|
||
return TRUE;
|
||
}
|
||
else
|
||
{
|
||
// oCreature doesn't have a weapon in either hand
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
//:: Adds Psuedonatural resistances & DR.
|
||
void ApplyPseudonaturalEffects(object oCreature)
|
||
{
|
||
if(!GetIsObjectValid(oCreature)) return;
|
||
|
||
int nHD = GetHitDice(oCreature);
|
||
if(DEBUG) DoDebug("prc_inc_json >> ApplyPseudonaturalEffects: nHD is: "+IntToString(nHD)+".");
|
||
// -------------------------
|
||
// Spell Resistance
|
||
// SR = 10 + HD (max 25)
|
||
// -------------------------
|
||
int nSR = 10 + nHD;
|
||
if(nSR > 25) nSR = 25;
|
||
|
||
effect eSR = EffectSpellResistanceIncrease(nSR);
|
||
eSR = TagEffect(eSR, "PSEUDO_SR");
|
||
eSR = EffectLinkEffects(eSR, UnyieldingEffect(eSR));
|
||
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eSR, oCreature);
|
||
|
||
// -------------------------
|
||
// Acid/Electricity Resistance
|
||
// Reference Table:
|
||
// HD 1<>3 : Resist 5
|
||
// HD 4<>7 : Resist 5
|
||
// HD 8<>11 : Resist 10
|
||
// HD >=12 : Resist 15
|
||
// -------------------------
|
||
int nResist;
|
||
|
||
if(nHD <= 7) nResist = 5;
|
||
else if(nHD <=11) nResist = 10;
|
||
else nResist = 15;
|
||
|
||
effect eResAcid = EffectDamageResistance(DAMAGE_TYPE_ACID, nResist);
|
||
eResAcid = TagEffect(eResAcid, "PSEUDO_RES_ACID");
|
||
eResAcid = EffectLinkEffects(eResAcid, UnyieldingEffect(eResAcid));
|
||
|
||
effect eResElec = EffectDamageResistance(DAMAGE_TYPE_ELECTRICAL, nResist);
|
||
eResElec = TagEffect(eResElec, "PSEUDO_RES_ELEC");
|
||
eResElec = EffectLinkEffects(eResElec, UnyieldingEffect(eResElec));
|
||
|
||
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eResAcid, oCreature);
|
||
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eResElec, oCreature);
|
||
|
||
// -------------------------
|
||
// Damage Reduction
|
||
// Reference Table:
|
||
// HD 1<>3 : none
|
||
// HD 4<>7 : DR 5 / magic
|
||
// HD 8<>11 : DR 5 / magic
|
||
// HD >=12 : DR 10 / magic
|
||
// -------------------------
|
||
|
||
int nDR;
|
||
if(nHD <= 3) { nDR = 0; }
|
||
else if(nHD <= 11) { nDR = 5; }
|
||
else { nDR = 10; }
|
||
|
||
effect eDR = EffectDamageReduction(nDR, DAMAGE_POWER_PLUS_ONE, 0, FALSE);
|
||
eDR = TagEffect(eDR, "PSEUDO_DR_MAGIC");
|
||
eDR = EffectLinkEffects(eDR, UnyieldingEffect(eDR));
|
||
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eDR, oCreature);
|
||
}
|
||
|
||
|
||
//::---------------------------------------------|
|
||
//:: JSON functions |
|
||
//::---------------------------------------------|
|
||
|
||
//:: Returns the Constitution value from a GFF creature UTC
|
||
int json_GetCONValue(json jCreature)
|
||
{
|
||
int nCon = 0; // default if missing
|
||
|
||
// Check if the Con field exists
|
||
if (GffGetFieldExists(jCreature, "Con"))
|
||
{
|
||
nCon = JsonGetInt(GffGetByte(jCreature, "Con"));
|
||
}
|
||
|
||
return nCon;
|
||
}
|
||
|
||
//:: Returns the Challenge Rating from a GFF creature UTC
|
||
float json_GetChallengeRating(json jCreature)
|
||
{
|
||
float fCR = 0.25; // default if missing
|
||
|
||
if (GffGetFieldExists(jCreature, "ChallengeRating"))
|
||
{
|
||
json jCR = GffGetFloat(jCreature, "ChallengeRating");
|
||
if (jCR != JsonNull())
|
||
{
|
||
fCR = JsonGetFloat(jCR);
|
||
}
|
||
}
|
||
|
||
return fCR;
|
||
}
|
||
|
||
//:: 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 string value of a VarTable entry named sVarName, or "" if not found.
|
||
string json_GetLocalStringFromVarTable(json jCreature, string sVarName)
|
||
{
|
||
json jVarTable = GffGetList(jCreature, "VarTable");
|
||
if (jVarTable == JsonNull())
|
||
return "";
|
||
|
||
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 a string
|
||
json jType = GffGetDword(jEntry, "Type");
|
||
if (jType != JsonNull())
|
||
{
|
||
int nType = JsonGetInt(jType);
|
||
if (nType == 3) // Type 3 = string
|
||
{
|
||
// Get the Value field using GFF functions
|
||
json jValue = GffGetString(jEntry, "Value");
|
||
if (jValue == JsonNull()) return "";
|
||
return JsonGetString(jValue);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return "";
|
||
}
|
||
|
||
//:: Returns the total Hit Dice from a JSON'd creature GFF.
|
||
int json_GetCreatureHD(json jCreature)
|
||
{
|
||
int nHD = 0;
|
||
|
||
json jClasses = GffGetList(jCreature, "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;
|
||
}
|
||
|
||
json json_RecalcMaxHP(json jCreature, int iHitDieValue)
|
||
{
|
||
int iHD = json_GetCreatureHD(jCreature);
|
||
|
||
//:: Retrieve the RacialType field
|
||
json jRacialTypeField = JsonObjectGet(jCreature, "Race");
|
||
int nRacialType = JsonGetInt(jRacialTypeField);
|
||
|
||
//:: Retrieve the CreatureSize from the creature appearance field
|
||
json jAppearanceField = JsonObjectGet(jCreature, "Appearance_Type");
|
||
int nAppearance = JsonGetInt(jAppearanceField);
|
||
|
||
int nSize = StringToInt(Get2DAString("appearance", "SizeCategory", nAppearance));
|
||
|
||
//CEP adds other sizes, take them into account too
|
||
if(nSize == 20)
|
||
nSize = CREATURE_SIZE_DIMINUTIVE;
|
||
else if(nSize == 21)
|
||
nSize = CREATURE_SIZE_FINE;
|
||
else if(nSize == 22)
|
||
nSize = CREATURE_SIZE_GARGANTUAN;
|
||
else if(nSize == 23)
|
||
nSize = CREATURE_SIZE_COLOSSAL;
|
||
|
||
int iNewMaxHP = (iHitDieValue * iHD);
|
||
|
||
if(nRacialType == RACIAL_TYPE_CONSTRUCT)
|
||
{
|
||
if(nSize == CREATURE_SIZE_FINE) iNewMaxHP += 0;
|
||
if(nSize == CREATURE_SIZE_DIMINUTIVE) iNewMaxHP += 0;
|
||
if(nSize == CREATURE_SIZE_TINY) iNewMaxHP += 0;
|
||
if(nSize == CREATURE_SIZE_SMALL) iNewMaxHP += 10;
|
||
if(nSize == CREATURE_SIZE_MEDIUM) iNewMaxHP += 20;
|
||
if(nSize == CREATURE_SIZE_LARGE) iNewMaxHP += 30;
|
||
if(nSize == CREATURE_SIZE_HUGE) iNewMaxHP += 40;
|
||
if(nSize == CREATURE_SIZE_GARGANTUAN) iNewMaxHP += 60;
|
||
}
|
||
|
||
if(DEBUG) DoDebug("prc_inc_json >> json_RecalcMaxHP | New MaxHP is: "+IntToString(iNewMaxHP)+ ".");
|
||
|
||
jCreature = GffReplaceShort(jCreature, "MaxHitPoints", iNewMaxHP);
|
||
jCreature = GffReplaceShort(jCreature, "CurrentHitPoints", iNewMaxHP);
|
||
jCreature = GffReplaceShort(jCreature, "HitPoints", iNewMaxHP);
|
||
|
||
/* SendMessageToPC(GetFirstPC(), "HD = " + IntToString(iHD));
|
||
SendMessageToPC(GetFirstPC(), "HitDieValue = " + IntToString(iHitDieValue));
|
||
SendMessageToPC(GetFirstPC(), "CON = " + IntToString(iCON));
|
||
SendMessageToPC(GetFirstPC(), "Mod = " + IntToString(iMod));
|
||
SendMessageToPC(GetFirstPC(), "New HP = " + IntToString(iNewMaxHP)); */
|
||
|
||
return jCreature;
|
||
}
|
||
|
||
//:: Reads ABILITY_TO_INCREASE from creature's VarTable and applies stat boosts based on increased HD
|
||
json json_ApplyAbilityBoostFromHD(json jCreature, int nOriginalHD)
|
||
{
|
||
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)
|
||
{
|
||
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())
|
||
{
|
||
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(DEBUG) DoDebug("prc_inc_json >> json_ApplyAbilityBoostFromHD: nCurrentTotalHD = "+IntToString(nCurrentTotalHD)+".");
|
||
|
||
if (nCurrentTotalHD <= 0)
|
||
{
|
||
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 > 250) nNewScore = 250;
|
||
|
||
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
|
||
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 > 127) nNewRank = 127;
|
||
|
||
// 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 = 0;
|
||
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 updates 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;
|
||
}
|
||
}
|
||
|
||
//:: Increases jCreature's Natural AC by iAddAC.
|
||
//::
|
||
json json_IncreaseBaseAC(json jCreature, int iAddAC)
|
||
{
|
||
json jBaseAC = GffGetByte(jCreature, "NaturalAC");
|
||
|
||
if (jBaseAC == JsonNull())
|
||
{
|
||
return JsonNull();
|
||
}
|
||
else
|
||
{
|
||
int nBaseAC = JsonGetInt(jBaseAC); // convert JSON number -> int
|
||
int nNewAC = nBaseAC + iAddAC;
|
||
|
||
jCreature = GffReplaceByte(jCreature, "NaturalAC", nNewAC);
|
||
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);
|
||
|
||
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, int nIncorporeal = FALSE)
|
||
{
|
||
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(nIncorporeal)
|
||
{
|
||
strMod = 0;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
//:: Changes jCreature's creature type.
|
||
json json_ModifyRacialType(json jCreature, int nNewRacialType)
|
||
{
|
||
if(DEBUG)DoDebug("prc_inc_json >> json_ModifyRacialType: Entering function");
|
||
|
||
// Retrieve the RacialType field
|
||
json jRacialTypeField = JsonObjectGet(jCreature, "Race");
|
||
|
||
if (JsonGetType(jRacialTypeField) == JSON_TYPE_NULL)
|
||
{
|
||
DoDebug("prc_inc_json >> json_ModifyRacialType: JsonGetType error 1: " + JsonGetError(jRacialTypeField));
|
||
//SpeakString("JsonGetType error 1: " + JsonGetError(jRacialTypeField));
|
||
return JsonNull();
|
||
}
|
||
|
||
// Retrieve the value to modify
|
||
json jRacialTypeValue = JsonObjectGet(jRacialTypeField, "value");
|
||
|
||
if (JsonGetType(jRacialTypeValue) != JSON_TYPE_INTEGER)
|
||
{
|
||
DoDebug("prc_inc_json >> json_ModifyRacialType: JsonGetType error 2: " + JsonGetError(jRacialTypeValue));
|
||
//SpeakString("JsonGetType error 2: " + JsonGetError(jRacialTypeValue));
|
||
return JsonNull();
|
||
}
|
||
|
||
jCreature = GffReplaceByte(jCreature, "Race", nNewRacialType);
|
||
|
||
// Return the new creature object
|
||
return jCreature;
|
||
}
|
||
|
||
//:: Adds Paragon SLA's to jCreature.
|
||
//::
|
||
json json_AddParagonPowers(json jCreature)
|
||
{
|
||
// Get the existing SpecAbilityList (if it exists)
|
||
json jSpecAbilityList = GffGetList(jCreature, "SpecAbilityList");
|
||
|
||
// Create the SpecAbilityList if it doesn't exist
|
||
if (jSpecAbilityList == JsonNull())
|
||
{
|
||
jSpecAbilityList = JsonArray();
|
||
}
|
||
|
||
//:: Greater Dispelling 3x / Day
|
||
int i;
|
||
for (i = 0; i < 3; i++)
|
||
{
|
||
json jSpecAbility = JsonObject();
|
||
jSpecAbility = GffAddWord(jSpecAbility, "Spell", 67);
|
||
jSpecAbility = GffAddByte(jSpecAbility, "SpellCasterLevel", 15);
|
||
jSpecAbility = GffAddByte(jSpecAbility, "SpellFlags", 1);
|
||
|
||
// Manually add to the array
|
||
jSpecAbilityList = JsonArrayInsert(jSpecAbilityList, jSpecAbility);
|
||
}
|
||
|
||
//:: Add Haste 3x / Day
|
||
for (i = 0; i < 3; i++)
|
||
{
|
||
json jSpecAbility = JsonObject();
|
||
jSpecAbility = GffAddWord(jSpecAbility, "Spell", 78);
|
||
jSpecAbility = GffAddByte(jSpecAbility, "SpellCasterLevel", 15);
|
||
jSpecAbility = GffAddByte(jSpecAbility, "SpellFlags", 1);
|
||
|
||
// Manually add to the array
|
||
jSpecAbilityList = JsonArrayInsert(jSpecAbilityList, jSpecAbility);
|
||
}
|
||
|
||
//:: See Invisiblity 3x / Day
|
||
for (i = 0; i < 3; i++)
|
||
{
|
||
json jSpecAbility = JsonObject();
|
||
jSpecAbility = GffAddWord(jSpecAbility, "Spell", 157);
|
||
jSpecAbility = GffAddByte(jSpecAbility, "SpellCasterLevel", 15);
|
||
jSpecAbility = GffAddByte(jSpecAbility, "SpellFlags", 1);
|
||
|
||
// Manually add to the array
|
||
jSpecAbilityList = JsonArrayInsert(jSpecAbilityList, jSpecAbility);
|
||
}
|
||
|
||
//:: Add the list to the creature
|
||
jCreature = GffAddList(jCreature, "SpecAbilityList", jSpecAbilityList);
|
||
|
||
return jCreature;
|
||
}
|
||
|
||
//:: Directly modifies jCreature's Challenge Rating.
|
||
//:: This is useful for most XP calculations.
|
||
//::
|
||
json json_UpdateParagonCR(json jCreature, int nBaseCR, int nBaseHD)
|
||
{
|
||
int nNewCR;
|
||
|
||
//:: Calculate additional CR by HD
|
||
if(nBaseHD <= 6)
|
||
{
|
||
nNewCR = nBaseCR + 18;
|
||
}
|
||
else if(nBaseHD <= 16)
|
||
{
|
||
nNewCR = nBaseCR + 15;
|
||
}
|
||
else
|
||
{nNewCR = nBaseCR + 12;}
|
||
|
||
//:: Modify Challenge Rating
|
||
jCreature = GffReplaceFloat(jCreature, "ChallengeRating"/* /value" */, IntToFloat(nNewCR));
|
||
|
||
return jCreature;
|
||
}
|
||
|
||
//:: Adds Psuedonatural SLA's to jCreature.
|
||
//::
|
||
json json_AddPsuedonaturalPowers(json jCreature)
|
||
{
|
||
// Get the existing SpecAbilityList (if it exists)
|
||
json jSpecAbilityList = GffGetList(jCreature, "SpecAbilityList");
|
||
|
||
// Create the SpecAbilityList if it doesn't exist
|
||
if (jSpecAbilityList == JsonNull())
|
||
{
|
||
jSpecAbilityList = JsonArray();
|
||
}
|
||
|
||
//:: True Strike 1x / Day
|
||
int i;
|
||
for (i = 0; i < 1; i++)
|
||
{
|
||
json jSpecAbility = JsonObject();
|
||
jSpecAbility = GffAddWord(jSpecAbility, "Spell", 415);
|
||
jSpecAbility = GffAddByte(jSpecAbility, "SpellCasterLevel", 15);
|
||
jSpecAbility = GffAddByte(jSpecAbility, "SpellFlags", 1);
|
||
|
||
// Manually add to the array
|
||
jSpecAbilityList = JsonArrayInsert(jSpecAbilityList, jSpecAbility);
|
||
}
|
||
|
||
//:: Add the list to the creature
|
||
jCreature = GffAddList(jCreature, "SpecAbilityList", jSpecAbilityList);
|
||
|
||
return jCreature;
|
||
}
|
||
|
||
//:: Directly modifies jCreature's Challenge Rating.
|
||
//:: This is useful for most XP calculations.
|
||
//::
|
||
json json_UpdatePsuedonaturalCR(json jCreature, int nBaseCR, int nBaseHD)
|
||
{
|
||
int nNewCR;
|
||
|
||
//:: Calculate additional CR by HD
|
||
if (nBaseHD >= 4 && nBaseHD <= 11)
|
||
{
|
||
nNewCR = nBaseCR + 1;
|
||
}
|
||
else if (nBaseHD >= 12)
|
||
{
|
||
nNewCR = nBaseCR + 2;
|
||
}
|
||
|
||
//:: Modify Challenge Rating
|
||
jCreature = GffReplaceFloat(jCreature, "ChallengeRating"/* /value" */, IntToFloat(nNewCR));
|
||
|
||
return jCreature;
|
||
}
|
||
|
||
|
||
//:: Spawns a Psuedonatural creature from a template
|
||
object MakePsuedonaturalCreatureFromTemplate(string sResref, location lSpawnLoc)
|
||
{
|
||
json jPsuedo = TemplateToJson(sResref, RESTYPE_UTC);
|
||
if (jPsuedo == JSON_NULL)
|
||
{
|
||
DoDebug("prc_inc_json >> SpawnPsuedonaturalCreatureFromTemplate: TemplateToJson failed <20> bad resref or resource missing.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: Get current HD
|
||
int nCurrentHD = json_GetCreatureHD(jPsuedo);
|
||
if (nCurrentHD <= 0)
|
||
{
|
||
DoDebug("make_psuedonat >> MakePsuedonaturalCreatureFromTemplate failed <20> template missing HD data.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: Get current CR
|
||
int nBaseCR = 1;
|
||
nBaseCR = json_GetCreatureHD(jPsuedo);
|
||
if (nBaseCR <= 0)
|
||
{
|
||
DoDebug("make_psuedonat >> MakePsuedonaturalCreatureFromTemplate failed <20> template missing CR data.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: Get local vars to transfer over.
|
||
int iMinHD = json_GetLocalIntFromVarTable(jPsuedo, "iMinHD");
|
||
int iMaxHD = json_GetLocalIntFromVarTable(jPsuedo, "iMaxHD");
|
||
int nOriginalHD = json_GetLocalIntFromVarTable(jPsuedo, "nOriginalHD");
|
||
int iClass2 = json_GetLocalIntFromVarTable(jPsuedo, "Class2");
|
||
int iClass2Package = json_GetLocalIntFromVarTable(jPsuedo, "Class2Package");
|
||
int iClass2Start = json_GetLocalIntFromVarTable(jPsuedo, "Class2Start");
|
||
int iMagicUse = json_GetLocalIntFromVarTable(jPsuedo, "X2_L_BEH_MAGIC");
|
||
string sAI = json_GetLocalStringFromVarTable(jPsuedo, "X2_SPECIAL_COMBAT_AI_SCRIPT");
|
||
|
||
//:: Adds True Strike 1x / day to jCreature.
|
||
jPsuedo = json_AddPsuedonaturalPowers(jPsuedo);
|
||
|
||
//:: Change jCreature's racialtype to outsider
|
||
jPsuedo = json_ModifyRacialType(jPsuedo, RACIAL_TYPE_OUTSIDER);
|
||
|
||
jPsuedo = json_UpdatePsuedonaturalCR(jPsuedo, nBaseCR, nCurrentHD);
|
||
|
||
//:: Spawn the creature
|
||
object oPsuedo = JsonToObject(jPsuedo, lSpawnLoc);
|
||
|
||
//:: Set variables
|
||
SetLocalInt(oPsuedo, "TEMPLATE_PSUEDONATURAL", 1);
|
||
SetLocalInt(oPsuedo, "iMinHD", iMinHD);
|
||
SetLocalInt(oPsuedo, "iMaxHD", iMaxHD);
|
||
SetLocalInt(oPsuedo, "nOriginalHD", nOriginalHD);
|
||
SetLocalInt(oPsuedo, "Class2", iClass2);
|
||
SetLocalInt(oPsuedo, "Class2Package", iClass2Package);
|
||
SetLocalInt(oPsuedo, "Class2Start", iClass2Start);
|
||
SetLocalInt(oPsuedo, "X2_L_BEH_MAGIC", iMagicUse);
|
||
SetLocalString(oPsuedo, "X2_SPECIAL_COMBAT_AI_SCRIPT", sAI);
|
||
|
||
return oPsuedo;
|
||
|
||
}
|
||
|
||
|
||
//:: Test void
|
||
//:: void main (){} |