generated from Jaysyn/ModuleTemplate
1993 lines
63 KiB
Plaintext
1993 lines
63 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 |
|
||
//::---------------------------------------------|
|
||
int GetHealerCompanionBonus(int nHealerLvl)
|
||
{
|
||
// No bonus before 12th level
|
||
if (nHealerLvl < 12)
|
||
return 0;
|
||
|
||
int nBonus = 0;
|
||
|
||
// Non-epic improvements: 12, 15, 18, 21 (every 3 levels)
|
||
if (nHealerLvl >= 12)
|
||
{
|
||
int nPreEpicIntervals = ( (nHealerLvl < 21) ? (nHealerLvl - 12) : (21 - 12) ) / 3;
|
||
nBonus += 2 + (nPreEpicIntervals * 2);
|
||
}
|
||
|
||
// Epic improvements: 24, 28, 32, 36... (every 4 levels)
|
||
if (nHealerLvl >= 24)
|
||
{
|
||
int nEpicIntervals = (nHealerLvl - 24) / 4;
|
||
// First epic improvement is +2 at 24
|
||
nBonus += 2 + (nEpicIntervals * 2);
|
||
}
|
||
|
||
return nBonus;
|
||
}
|
||
|
||
/* int GetHealerCompanionBonus(int nHealerLvl)
|
||
{
|
||
if (nHealerLvl < 12)
|
||
return 0;
|
||
|
||
// Shift so that 12–14 yields interval 0
|
||
int nIntervals = (nHealerLvl - 12) / 3;
|
||
|
||
return 2 + (nIntervals * 2);
|
||
} */
|
||
|
||
|
||
//:: 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);
|
||
}
|
||
|
||
// Build and return all effects for the Celestial Template
|
||
effect CelestialTemplateEffects(int nHD)
|
||
{
|
||
int nResist;
|
||
int nDRAmount;
|
||
int nDRBypass;
|
||
|
||
// -------------------------
|
||
// Elemental Resistances
|
||
// -------------------------
|
||
// 1–7 HD = 5
|
||
// 8+ HD = 10
|
||
if (nHD >= 8)
|
||
{
|
||
nResist = 10;
|
||
}
|
||
else
|
||
{
|
||
nResist = 5;
|
||
}
|
||
|
||
// -------------------------
|
||
// Damage Reduction
|
||
// -------------------------
|
||
// 1–3 HD = none
|
||
// 4–11 HD = 5/magic
|
||
// 12+ HD = 10/magic
|
||
if (nHD >= 12)
|
||
{
|
||
nDRAmount = 10;
|
||
nDRBypass = DAMAGE_POWER_PLUS_ONE; // DR 10/magic
|
||
}
|
||
else if (nHD >= 4)
|
||
{
|
||
nDRAmount = 5;
|
||
nDRBypass = DAMAGE_POWER_PLUS_ONE; // DR 5/magic
|
||
}
|
||
else
|
||
{
|
||
nDRAmount = 0;
|
||
nDRBypass = 0; // no DR
|
||
}
|
||
|
||
// -------------------------
|
||
// Build Effects
|
||
// -------------------------
|
||
effect eEffects;
|
||
effect eRes;
|
||
|
||
// Acid
|
||
eRes = EffectDamageResistance(DAMAGE_TYPE_ACID, nResist, 0);
|
||
eEffects = eRes;
|
||
|
||
// Cold
|
||
eRes = EffectDamageResistance(DAMAGE_TYPE_COLD, nResist, 0);
|
||
eEffects = EffectLinkEffects(eEffects, eRes);
|
||
|
||
// Electricity
|
||
eRes = EffectDamageResistance(DAMAGE_TYPE_ELECTRICAL, nResist, 0);
|
||
eEffects = EffectLinkEffects(eEffects, eRes);
|
||
|
||
// DR if any
|
||
if (nDRAmount > 0)
|
||
{
|
||
effect eDR = EffectDamageReduction(nDRAmount, nDRBypass, 0);
|
||
eEffects = EffectLinkEffects(eEffects, eDR);
|
||
}
|
||
|
||
eEffects = UnyieldingEffect(eEffects);
|
||
|
||
return eEffects;
|
||
}
|
||
|
||
|
||
|
||
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;
|
||
}
|
||
|
||
//:: Updates CR for Celestial template
|
||
json json_UpdateCelestialCR(json jCreature, int nBaseCR, int nHD)
|
||
{
|
||
int nNewCR;
|
||
|
||
//:: Calculate CR based on HD
|
||
if (nHD <= 3)
|
||
{
|
||
nNewCR = nBaseCR;
|
||
}
|
||
else if (nHD <= 7)
|
||
{
|
||
nNewCR = nBaseCR + 1;
|
||
}
|
||
else
|
||
{
|
||
nNewCR = nBaseCR + 2;
|
||
}
|
||
|
||
//:: Modify Challenge Rating
|
||
jCreature = GffReplaceFloat(jCreature, "ChallengeRating", IntToFloat(nNewCR));
|
||
return jCreature;
|
||
}
|
||
|
||
//:: Adds Celestial SLA's to creature
|
||
json json_AddCelestialPowers(json jCreature)
|
||
{
|
||
// Get the existing SpecAbilityList
|
||
json jSpecAbilityList = GffGetList(jCreature, "SpecAbilityList");
|
||
if (jSpecAbilityList == JsonNull())
|
||
{
|
||
jSpecAbilityList = JsonArray();
|
||
}
|
||
|
||
//:: Add Smite Evil 1x / day
|
||
json jSpecAbility = JsonObject();
|
||
jSpecAbility = GffAddWord(jSpecAbility, "Spell", SPELLABILITY_SMITE_EVIL);
|
||
jSpecAbility = GffAddByte(jSpecAbility, "SpellCasterLevel", json_GetCreatureHD(jCreature));
|
||
jSpecAbility = GffAddByte(jSpecAbility, "SpellFlags", 1);
|
||
jSpecAbilityList = JsonArrayInsert(jSpecAbilityList, jSpecAbility);
|
||
|
||
//:: Add the list to the creature
|
||
jCreature = GffAddList(jCreature, "SpecAbilityList", jSpecAbilityList);
|
||
return jCreature;
|
||
}
|
||
|
||
//:: Apply Celestial template to a creature JSON template
|
||
json json_MakeCelestial(json jCreature, int nBaseHD, int nBaseCR)
|
||
{
|
||
if (jCreature == JsonNull())
|
||
return JsonNull();
|
||
|
||
//:: Get current HD for scaling
|
||
int nHD = json_GetCreatureHD(jCreature);
|
||
if (nHD <= 0)
|
||
{
|
||
DoDebug("prc_inc_json >> json_MakeCelestial: Invalid HD");
|
||
return JsonNull();
|
||
}
|
||
|
||
//:: Get current CR
|
||
float fCR = json_GetChallengeRating(jCreature);
|
||
|
||
//:: Update CR using Celestial formula
|
||
jCreature = json_UpdateCelestialCR(jCreature, FloatToInt(fCR), nHD);
|
||
if (jCreature == JsonNull())
|
||
{
|
||
DoDebug("prc_inc_json >> json_MakeCelestial: json_UpdateCelestialCR failed");
|
||
return JsonNull();
|
||
}
|
||
|
||
//:: Ensure Intelligence is at least 4
|
||
json jInt = GffGetByte(jCreature, "Int");
|
||
if (jInt != JsonNull() && JsonGetInt(jInt) < 4)
|
||
{
|
||
jCreature = GffReplaceByte(jCreature, "Int", 4);
|
||
}
|
||
|
||
//:: Add celestial Smite Evil
|
||
jCreature = json_AddCelestialPowers(jCreature);
|
||
if (jCreature == JsonNull())
|
||
{
|
||
DoDebug("prc_inc_json >> json_MakeCelestial: json_AddCelestialPowers failed");
|
||
return JsonNull();
|
||
}
|
||
|
||
//:: Change creature type if animal/beast/vermin to magical beast
|
||
int nRacialType = JsonGetInt(GffGetByte(jCreature, "Race"));
|
||
if (nRacialType == RACIAL_TYPE_ANIMAL || nRacialType == RACIAL_TYPE_VERMIN || nRacialType == RACIAL_TYPE_BEAST)
|
||
{
|
||
jCreature = json_ModifyRacialType(jCreature, RACIAL_TYPE_MAGICAL_BEAST);
|
||
}
|
||
|
||
//:: Update creature CR
|
||
jCreature = json_UpdateCelestialCR(jCreature, nBaseCR, nHD);
|
||
if (jCreature == JsonNull())
|
||
{
|
||
DoDebug("prc_inc_json >> json_MakeCelestial: json_UpdateCelestialCR failed");
|
||
return JsonNull();
|
||
}
|
||
|
||
return jCreature;
|
||
}
|
||
|
||
//:: Spawns a Celestial Companion from a template
|
||
object MakeCelestialCompanionFromTemplate(string sResref, location lSpawnLoc, int nHealerLvl)
|
||
{
|
||
int nBonus = GetHealerCompanionBonus(nHealerLvl);
|
||
|
||
json jCelestial = TemplateToJson(sResref, RESTYPE_UTC);
|
||
if (jCelestial == JSON_NULL)
|
||
{
|
||
DoDebug("prc_inc_json >> MakeCelestialCompanionFromTemplate: TemplateToJson failed — bad resref or resource missing.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: Get local vars to transfer over.
|
||
int iMinHD = json_GetLocalIntFromVarTable(jCelestial, "iMinHD");
|
||
int iMaxHD = json_GetLocalIntFromVarTable(jCelestial, "iMaxHD");
|
||
int nOriginalHD = json_GetLocalIntFromVarTable(jCelestial, "nOriginalHD");
|
||
int iClass2 = json_GetLocalIntFromVarTable(jCelestial, "Class2");
|
||
int iClass2Package = json_GetLocalIntFromVarTable(jCelestial, "Class2Package");
|
||
int iClass2Start = json_GetLocalIntFromVarTable(jCelestial, "Class2Start");
|
||
int iBaseCL = json_GetLocalIntFromVarTable(jCelestial, "iBaseCL");
|
||
int iMagicUse = json_GetLocalIntFromVarTable(jCelestial, "X2_L_BEH_MAGIC");
|
||
string sAI = json_GetLocalStringFromVarTable(jCelestial, "X2_SPECIAL_COMBAT_AI_SCRIPT");
|
||
|
||
//:: Get the original Challenge Rating
|
||
int nBaseCR = FloatToInt(json_GetChallengeRating(jCelestial));
|
||
|
||
//:: Apply celestial template modifications
|
||
jCelestial = json_MakeCelestial(jCelestial, nBonus, nBaseCR);
|
||
if (jCelestial == JSON_NULL)
|
||
{
|
||
DoDebug("prc_inc_json >> MakeCelestialCompanionFromTemplate failed — json_MakeCelestial returned invalid JSON.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: Apply +2 Natural AC bonus per 3 Healer levels
|
||
jCelestial = json_IncreaseBaseAC(jCelestial, nBonus);
|
||
if (jCelestial == JSON_NULL)
|
||
{
|
||
DoDebug("prc_inc_json >> MakeCelestialCompanionFromTemplate failed — json_IncreaseBaseAC returned invalid JSON.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: +2 STR, DEX & INT per 3 Healer levels
|
||
jCelestial = json_UpdateTemplateStats(jCelestial, nBonus, nBonus, 0, nBonus, 0, 0);
|
||
if (jCelestial == JSON_NULL)
|
||
{
|
||
DoDebug("prc_inc_json >> MakeCelestialCompanionFromTemplate failed — json_UpdateTemplateStats returned invalid JSON.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: The Companion always has Improved Evasion if the healer qualifies,
|
||
//:: but adding it this way gives the base creature more utility for builders.
|
||
if (nHealerLvl > 7)
|
||
{
|
||
//:: Add Improved Evasion feat directly to FeatList
|
||
json jFeatList = GffGetList(jCelestial, "FeatList");
|
||
if (jFeatList == JsonNull())
|
||
jFeatList = JsonArray();
|
||
|
||
//:: Check if creature already has Improved Evasion
|
||
int bHasFeat = FALSE;
|
||
int nFeatCount = JsonGetLength(jFeatList);
|
||
int j;
|
||
|
||
for (j = 0; j < nFeatCount; j++)
|
||
{
|
||
json jFeatStruct = JsonArrayGet(jFeatList, j);
|
||
if (jFeatStruct != JsonNull())
|
||
{
|
||
json jFeatValue = GffGetWord(jFeatStruct, "Feat");
|
||
if (jFeatValue != JsonNull() && JsonGetInt(jFeatValue) == FEAT_IMPROVED_EVASION)
|
||
{
|
||
bHasFeat = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
//:: Add feat only if not already present
|
||
if (!bHasFeat)
|
||
{
|
||
json jNewFeat = JsonObject();
|
||
jNewFeat = JsonObjectSet(jNewFeat, "__struct_id", JsonInt(1));
|
||
jNewFeat = GffAddWord(jNewFeat, "Feat", FEAT_IMPROVED_EVASION);
|
||
|
||
jFeatList = JsonArrayInsert(jFeatList, jNewFeat);
|
||
jCelestial = GffReplaceList(jCelestial, "FeatList", jFeatList);
|
||
}
|
||
}
|
||
|
||
//:: Spawn the creature
|
||
object oCelestial = JsonToObject(jCelestial, lSpawnLoc);
|
||
|
||
//:: Set variables for LevelUpSummon()
|
||
SetLocalInt(oCelestial, "TEMPLATE_CELESTIAL", 1);
|
||
SetLocalInt(oCelestial, "iMinHD", iMinHD);
|
||
SetLocalInt(oCelestial, "iMaxHD", iMaxHD);
|
||
SetLocalInt(oCelestial, "nOriginalHD", nOriginalHD);
|
||
SetLocalInt(oCelestial, "Class2", iClass2);
|
||
SetLocalInt(oCelestial, "Class2Package", iClass2Package);
|
||
SetLocalInt(oCelestial, "Class2Start", iClass2Start);
|
||
SetLocalInt(oCelestial, "iBaseCL", iBaseCL);
|
||
SetLocalInt(oCelestial, "X2_L_BEH_MAGIC", iMagicUse);
|
||
SetLocalString(oCelestial, "X2_SPECIAL_COMBAT_AI_SCRIPT", sAI);
|
||
|
||
return oCelestial;
|
||
}
|
||
|
||
//:: Spawns a Celestial creature from a template
|
||
object MakeCelestialCreatureFromTemplate(string sResref, location lSpawnLoc)
|
||
{
|
||
json jCelestial = TemplateToJson(sResref, RESTYPE_UTC);
|
||
if (jCelestial == JSON_NULL)
|
||
{
|
||
DoDebug("prc_inc_json >> MakeCelestialCreatureFromTemplate: TemplateToJson failed — bad resref or resource missing.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: Get current HD
|
||
int nCurrentHD = json_GetCreatureHD(jCelestial);
|
||
if (nCurrentHD <= 0)
|
||
{
|
||
DoDebug("make_celestial >> MakeCelestialCreatureFromTemplate failed — template missing HD data.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: Get current CR
|
||
int nBaseCR = 1;
|
||
nBaseCR = FloatToInt(json_GetChallengeRating(jCelestial));
|
||
if (nBaseCR <= 0)
|
||
{
|
||
DoDebug("make_celestial >> MakeCelestialCreatureFromTemplate failed — template missing CR data.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: Get local vars to transfer over.
|
||
int iMinHD = json_GetLocalIntFromVarTable(jCelestial, "iMinHD");
|
||
int iMaxHD = json_GetLocalIntFromVarTable(jCelestial, "iMaxHD");
|
||
int nOriginalHD = json_GetLocalIntFromVarTable(jCelestial, "nOriginalHD");
|
||
int iClass2 = json_GetLocalIntFromVarTable(jCelestial, "Class2");
|
||
int iClass2Package = json_GetLocalIntFromVarTable(jCelestial, "Class2Package");
|
||
int iClass2Start = json_GetLocalIntFromVarTable(jCelestial, "Class2Start");
|
||
int iBaseCL = json_GetLocalIntFromVarTable(jCelestial, "iBaseCL");
|
||
int iMagicUse = json_GetLocalIntFromVarTable(jCelestial, "X2_L_BEH_MAGIC");
|
||
string sAI = json_GetLocalStringFromVarTable(jCelestial, "X2_SPECIAL_COMBAT_AI_SCRIPT");
|
||
|
||
//:: Apply celestial template modifications
|
||
jCelestial = json_MakeCelestial(jCelestial, nCurrentHD, nBaseCR);
|
||
if (jCelestial == JSON_NULL)
|
||
{
|
||
DoDebug("make_celestial >> MakeCelestialCreatureFromTemplate failed — MakeCelestial returned invalid JSON.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: Spawn the creature
|
||
object oCelestial = JsonToObject(jCelestial, lSpawnLoc);
|
||
|
||
//:: Set variables
|
||
SetLocalInt(oCelestial, "TEMPLATE_CELESTIAL", 1);
|
||
SetLocalInt(oCelestial, "iMinHD", iMinHD);
|
||
SetLocalInt(oCelestial, "iMaxHD", iMaxHD);
|
||
SetLocalInt(oCelestial, "nOriginalHD", nOriginalHD);
|
||
SetLocalInt(oCelestial, "Class2", iClass2);
|
||
SetLocalInt(oCelestial, "Class2Package", iClass2Package);
|
||
SetLocalInt(oCelestial, "Class2Start", iClass2Start);
|
||
SetLocalInt(oCelestial, "Class2Start", iClass2Start);
|
||
SetLocalInt(oCelestial, "iBaseCL", iBaseCL);
|
||
SetLocalInt(oCelestial, "X2_L_BEH_MAGIC", iMagicUse);
|
||
SetLocalString(oCelestial, "X2_SPECIAL_COMBAT_AI_SCRIPT", sAI);
|
||
|
||
return oCelestial;
|
||
}
|
||
|
||
//:: 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 — bad resref or resource missing.");
|
||
return OBJECT_INVALID;
|
||
}
|
||
|
||
//:: Get current HD
|
||
int nCurrentHD = json_GetCreatureHD(jPsuedo);
|
||
if (nCurrentHD <= 0)
|
||
{
|
||
DoDebug("make_psuedonat >> MakePsuedonaturalCreatureFromTemplate failed — 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 — 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 (){} |