diff --git a/README.md b/README.md index a40a65b..ecde8ac 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Repository for the development of the PRC8 version of Endless Nights III, a roug 2.) [Original module resources](https://neverwintervault.org/project/nwn1/module/endless-nights-iii) -3.) [PRC8](https://gitea.raptio.us/Jaysyn/PRC8/src/branch/main/Release) +3.) [PRC8](https://gitea.raptio.us/Jaysyn/PRC8/releases) 4.) [CEP3](https://neverwintervault.org/project/nwnee/hakpak/combined/cep-3-community-expansion-pack) diff --git a/nasher.cfg b/nasher.cfg index 4229b84..f35bfdf 100644 --- a/nasher.cfg +++ b/nasher.cfg @@ -1,7 +1,7 @@ [package] name = "Endless Nights III [PRC8-CEP3]" description = "PRC8 version of Endless Nights III." -version = "1.36prc8" +version = "1.37prc8" url = "https://discord.gg/ca2ru3KxYd" author = "Havlen" author = "Jaysyn904 <68194417+Jaysyn904@users.noreply.github.com>" @@ -231,5 +231,12 @@ description = "PRC8 version of Endless Nights III." filter = "prc_nui_sc_inc.nss" filter = "prc_nui_scd_inc.nss" filter = "prc_nui_consts.nss" - filter = "nw_inc_nui" + filter = "prc_nui_sb_inc.nss" + filter = "prc_nui_sbd_inc.nss" + filter = "prc_nui_lv_inc.nss" + filter = "prc_nui_com_inc.nss" + filter = "nw_inc_nui.nss" + filter = "inc_infusion.nss" + filter = "nw_inc_gff.nss" + filter = "prc_inc_json.nss" filter = "xchst_inc.nss" \ No newline at end of file diff --git a/src/include/inc_epicspelldef.nss b/src/include/inc_epicspelldef.nss index 78b89b4..613b0f1 100644 --- a/src/include/inc_epicspelldef.nss +++ b/src/include/inc_epicspelldef.nss @@ -47,77 +47,78 @@ const string MES_CONTINGENCIES_YES2 = "The contingencies must expire to allo */ //Primogenitors SpellID constants -const int SPELL_EPIC_A_STONE = 0;//4007; -const int SPELL_EPIC_ACHHEEL = 1;//4000; -const int SPELL_EPIC_AL_MART = 2;//4002; -const int SPELL_EPIC_ALLHOPE = 3;//4001; -const int SPELL_EPIC_ANARCHY = 4;//4003; -const int SPELL_EPIC_ANBLAST = 5;//4004; -const int SPELL_EPIC_ANBLIZZ = 6;//4005; -const int SPELL_EPIC_ARMY_UN = 7;//4006; -const int SPELL_EPIC_BATTLEB = 999;//4008; -const int SPELL_EPIC_CELCOUN = 8;//4009; -const int SPELL_EPIC_CHAMP_V = 9;//4010; -const int SPELL_EPIC_CON_RES =10;//4011; -const int SPELL_EPIC_CON_REU =11;//4012; -const int SPELL_EPIC_DEADEYE =12;//4013; -const int SPELL_EPIC_DIREWIN =13;//4015; -const int SPELL_EPIC_DREAMSC =14;//4017; -const int SPELL_EPIC_DRG_KNI =15;//4016; -const int SPELL_EPIC_DTHMARK =1000;//4014; -const int SPELL_EPIC_DULBLAD =16;//4018; -const int SPELL_EPIC_DWEO_TH =17;//4019; -const int SPELL_EPIC_ENSLAVE =18;//4020; -const int SPELL_EPIC_EP_M_AR =19;//4021; -const int SPELL_EPIC_EP_RPLS =20;//4022; -const int SPELL_EPIC_EP_SP_R =21;//4023; -const int SPELL_EPIC_EP_WARD =22;//4024; -const int SPELL_EPIC_ET_FREE =23;//4025; -const int SPELL_EPIC_FIEND_W =24;//4026; -const int SPELL_EPIC_FLEETNS =25;//4027; -const int SPELL_EPIC_GEMCAGE =26;//4028; -const int SPELL_EPIC_GODSMIT =27;//4029; -const int SPELL_EPIC_GR_RUIN =28;//4030; -const int SPELL_EPIC_GR_SP_RE=29;//4031; -const int SPELL_EPIC_GR_TIME =30;//4032; -const int SPELL_EPIC_HELBALL =31;//4034; -const int SPELL_EPIC_HELSEND =1001;//4033; -const int SPELL_EPIC_HERCALL =32;//4035; -const int SPELL_EPIC_HERCEMP =33;//4036; -const int SPELL_EPIC_IMPENET =34;//4037; -const int SPELL_EPIC_LEECH_F =35;//4038; -const int SPELL_EPIC_LEG_ART =1002;//4039; -const int SPELL_EPIC_LIFE_FT =1003;//4040; -const int SPELL_EPIC_MAGMA_B =36;//4041; -const int SPELL_EPIC_MASSPEN =37;//4042; -const int SPELL_EPIC_MORI = 38;//4043; -const int SPELL_EPIC_MUMDUST =39;//4044; -const int SPELL_EPIC_NAILSKY =40;//4045; -const int SPELL_EPIC_NIGHTSU =1004;//4046; -const int SPELL_EPIC_ORDER_R =41;//4047; -const int SPELL_EPIC_PATHS_B =42;//4048; -const int SPELL_EPIC_PEERPEN =43;//4049; -const int SPELL_EPIC_PESTIL = 44;//4050; -const int SPELL_EPIC_PIOUS_P =45;//4051; -const int SPELL_EPIC_PLANCEL =46;//4052; -const int SPELL_EPIC_PSION_S =47;//4053; -const int SPELL_EPIC_RAINFIR =48;//4054; -const int SPELL_EPIC_RISEN_R =1005;//4055; -const int SPELL_EPIC_RUINN = 49;//4056; //NON_STANDARD -const int SPELL_EPIC_SINGSUN =50;//4057; -const int SPELL_EPIC_SP_WORM =51;//4058; -const int SPELL_EPIC_STORM_M =52;//4059; -const int SPELL_EPIC_SUMABER =53;//4060; -const int SPELL_EPIC_SUP_DIS =54;//4061; -const int SPELL_EPIC_SYMRUST =1006;//4062; -const int SPELL_EPIC_THEWITH =55;//4063; -const int SPELL_EPIC_TOLO_KW =56;//4064; -const int SPELL_EPIC_TRANVIT =57;//4065; -const int SPELL_EPIC_TWINF = 58;//4066; -const int SPELL_EPIC_UNHOLYD =59;//4067; -const int SPELL_EPIC_UNIMPIN =60;//4068; -const int SPELL_EPIC_UNSEENW =61;//4069; -const int SPELL_EPIC_WHIP_SH =62;//4070; +const int SPELL_EPIC_A_STONE = 0;//4007; +const int SPELL_EPIC_ACHHEEL = 1;//4000; +const int SPELL_EPIC_AL_MART = 2;//4002; +const int SPELL_EPIC_ALLHOPE = 3;//4001; +const int SPELL_EPIC_ANARCHY = 4;//4003; +const int SPELL_EPIC_ANBLAST = 5;//4004; +const int SPELL_EPIC_ANBLIZZ = 6;//4005; +const int SPELL_EPIC_ARMY_UN = 7;//4006; +const int SPELL_EPIC_BATTLEB = 999;//4008; +const int SPELL_EPIC_CELCOUN = 8;//4009; +const int SPELL_EPIC_CHAMP_V = 9;//4010; +const int SPELL_EPIC_CON_RES = 10;//4011; +const int SPELL_EPIC_CON_REU = 11;//4012; +const int SPELL_EPIC_DEADEYE = 12;//4013; +const int SPELL_EPIC_DIREWIN = 13;//4015; +const int SPELL_EPIC_DREAMSC = 14;//4017; +const int SPELL_EPIC_DRG_KNI = 15;//4016; +const int SPELL_EPIC_DTHMARK = 1000;//4014; +const int SPELL_EPIC_DULBLAD = 16;//4018; +const int SPELL_EPIC_DWEO_TH = 17;//4019; +const int SPELL_EPIC_ENSLAVE = 18;//4020; +const int SPELL_EPIC_EP_M_AR = 19;//4021; +const int SPELL_EPIC_EP_RPLS = 20;//4022; +const int SPELL_EPIC_EP_SP_R = 21;//4023; +const int SPELL_EPIC_EP_WARD = 22;//4024; +const int SPELL_EPIC_ET_FREE = 23;//4025; +const int SPELL_EPIC_FIEND_W = 24;//4026; +const int SPELL_EPIC_FLEETNS = 25;//4027; +const int SPELL_EPIC_GEMCAGE = 26;//4028; +const int SPELL_EPIC_GODSMIT = 27;//4029; +const int SPELL_EPIC_GR_RUIN = 28;//4030; +const int SPELL_EPIC_GR_SP_RE = 29;//4031; +const int SPELL_EPIC_GR_TIME = 30;//4032; +const int SPELL_EPIC_HELBALL = 31;//4034; +const int SPELL_EPIC_HELSEND = 1001;//4033; +const int SPELL_EPIC_HERCALL = 32;//4035; +const int SPELL_EPIC_HERCEMP = 33;//4036; +const int SPELL_EPIC_IMPENET = 34;//4037; +const int SPELL_EPIC_LEECH_F = 35;//4038; +const int SPELL_EPIC_LEG_ART = 1002;//4039; +const int SPELL_EPIC_LIFE_FT = 1003;//4040; +const int SPELL_EPIC_MAGMA_B = 36;//4041; +const int SPELL_EPIC_MASSPEN = 37;//4042; +const int SPELL_EPIC_MORI = 38;//4043; +const int SPELL_EPIC_MUMDUST = 39;//4044; +const int SPELL_EPIC_NAILSKY = 40;//4045; +const int SPELL_EPIC_NIGHTSU = 1004;//4046; +const int SPELL_EPIC_ORDER_R = 41;//4047; +const int SPELL_EPIC_PATHS_B = 42;//4048; +const int SPELL_EPIC_PEERPEN = 43;//4049; +const int SPELL_EPIC_PESTIL = 44;//4050; +const int SPELL_EPIC_PIOUS_P = 45;//4051; +const int SPELL_EPIC_PLANCEL = 46;//4052; +const int SPELL_EPIC_PSION_S = 47;//4053; +const int SPELL_EPIC_RAINFIR = 48;//4054; +//const int SPELL_EPIC_RISEN_R =1005;//4055; +const int SPELL_EPIC_RISEN_R = 49;//4055; +const int SPELL_EPIC_RUINN = 50;//4056; //NON_STANDARD +const int SPELL_EPIC_SINGSUN = 51;//4057; +const int SPELL_EPIC_SP_WORM = 52;//4058; +const int SPELL_EPIC_STORM_M = 53;//4059; +const int SPELL_EPIC_SUMABER = 54;//4060; +const int SPELL_EPIC_SUP_DIS = 55;//4061; +const int SPELL_EPIC_SYMRUST = 1006;//4062; +const int SPELL_EPIC_THEWITH = 56;//4063; +const int SPELL_EPIC_TOLO_KW = 57;//4064; +const int SPELL_EPIC_TRANVIT = 58;//4065; +const int SPELL_EPIC_TWINF = 59;//4066; +const int SPELL_EPIC_UNHOLYD = 60;//4067; +const int SPELL_EPIC_UNIMPIN = 61;//4068; +const int SPELL_EPIC_UNSEENW = 62;//4069; +const int SPELL_EPIC_WHIP_SH = 63;//4070; /* diff --git a/src/include/inc_epicspellfnc.nss b/src/include/inc_epicspellfnc.nss index 59f4dd6..7fb6054 100644 --- a/src/include/inc_epicspellfnc.nss +++ b/src/include/inc_epicspellfnc.nss @@ -246,7 +246,7 @@ int GetSpellFromAbrev(string sAbrev) sAbrev = GetStringLowerCase(sAbrev); if(GetStringLeft(sAbrev, 8) == "epic_sp_") sAbrev = GetStringRight(sAbrev, GetStringLength(sAbrev)-8); - if(DEBUG) DoDebug("sAbrew to check vs: " + sAbrev); + if(DEBUG) DoDebug("sAbrev to check vs: " + sAbrev); int i = 0; string sLabel = GetStringLowerCase(Get2DACache("epicspells", "LABEL", i)); while(sLabel != "") diff --git a/src/include/inc_infusion.nss b/src/include/inc_infusion.nss new file mode 100644 index 0000000..e9c7528 --- /dev/null +++ b/src/include/inc_infusion.nss @@ -0,0 +1,481 @@ +//::////////////////////////////////////////////// +//:: ;-. ,-. ,-. ,-. +//:: | ) | ) / ( ) +//:: |-' |-< | ;-: +//:: | | \ \ ( ) +//:: ' ' ' `-' `-' +//:://///////////////////////////////////////////// +//:: +/* + Script: inc_infusion + Author: Jaysyn + Created: 2025-08-11 17:01:26 + + Description: + Contains most functions related to the Create + Infusion feat. + +*/ +//:: +//::////////////////////////////////////////////// +#include "prc_inc_spells" + +int GetMaxDivineSpellLevel(object oCaster, int nClass); +int GetCastSpellCasterLevelFromItem(object oItem, int nSpellID); +int GetIsClassSpell(object oCaster, int nSpellID, int nClass); +int GetHasSpellOnClassList(object oCaster, int nSpellID); +void InfusionSecondSave(object oUser, int nDC); + +/** + * @brief Finds the class index for which the given spell is available to the specified caster. + * + * This function iterates through all possible classes and returns the first class + * index for which the specified spell is on the caster's spell list. + * + * @param oCaster The creature object to check. + * @param nSpellID The spell ID to find the class for. + * + * @return The class index that has the spell on its class spell list for the caster, + * or -1 if no matching class is found. + */ +int FindSpellCastingClass(object oCaster, int nSpellID) +{ + int i = 0; + int nClassFound = -1; + int nClass; + + // Only loop through caster's classes + for (i = 0; i <= 8; i++) + { + nClass = GetClassByPosition(i, oCaster); + if (nClass == CLASS_TYPE_INVALID) continue; + + if (GetIsClassSpell(oCaster, nSpellID, nClass)) + { + nClassFound = nClass; + break; + } + } + + return nClassFound; +} + + +/** + * @brief Performs validation checks to determine if the caster can use a spell infusion from the specified item. + * + * This function verifies that the item is a valid infused herb, checks the caster's relevant class and ability scores, + * confirms the caster is a divine spellcaster with the necessary caster level, and ensures the spell is on the caster's class spell list. + * + * @param oCaster The creature attempting to use the infusion. + * @param oItem The infused herb item containing the spell. + * @param nSpellID The spell ID of the infusion spell being cast. + * + * @return TRUE if all infusion use checks pass and the caster can use the infusion; FALSE otherwise. + */ + int DoInfusionUseChecks(object oCaster, object oItem, int nSpellID) +{ + int bPnPHerbs = GetPRCSwitch(PRC_CREATE_INFUSION_OPTIONAL_HERBS); + + if(GetBaseItemType(oItem) != BASE_ITEM_INFUSED_HERB) + { + FloatingTextStringOnCreature("Not casting from an Infused Herb", oCaster); + return FALSE; + } + + int nItemSpellLvl = GetCastSpellCasterLevelFromItem(oItem, nSpellID); + if (bPnPHerbs && nItemSpellLvl == -1) + { + FloatingTextStringOnCreature("Item has no spellcaster level.", oCaster); + return FALSE; + } + + // **CRITICAL: Find the correct class that actually has the spell on its list** + int nClassCaster = FindSpellCastingClass(oCaster, nSpellID); + + if(DEBUG) DoDebug("nClassCaster is: " + IntToString(nClassCaster) + "."); + + // Check for valid class + if (nClassCaster == -1) + { + FloatingTextStringOnCreature("No valid class found for this spell.", oCaster); + return FALSE; + } + + if(GetMaxDivineSpellLevel(oCaster, nClassCaster) < 1 ) + { + FloatingTextStringOnCreature("You must be a divine spellcaster to activate an infusion.", oCaster); + return FALSE; + } + + // Must have spell on class list - (This will also double-check via the class) + if (!GetHasSpellOnClassList(oCaster, nSpellID)) + { + FloatingTextStringOnCreature("You must have a spell on one of your class spell lists to cast it from an infusion.", oCaster); + return FALSE; + } + + // Must meet ability requirement: Ability score >= 10 + spell level + int nSpellLevel = PRCGetSpellLevelForClass(nSpellID, nClassCaster); + int nClassAbility = GetAbilityScoreForClass(nClassCaster, oCaster); + + if(DEBUG) DoDebug("inc_infusion >> DoInfusionUseChecks: nClassCaster is "+IntToString(nClassCaster)+"."); + if(DEBUG) DoDebug("inc_infusion >> DoInfusionUseChecks: Class nSpellLevel is "+IntToString(nSpellLevel)+"."); + if(DEBUG) DoDebug("inc_infusion >> DoInfusionUseChecks: nClassAbility is "+IntToString(nClassAbility)+"."); + + if (nClassAbility < 10 + nSpellLevel) + { + FloatingTextStringOnCreature("You must meet ability score requirement to cast spell from infusion.", oCaster); + return FALSE; + } + + // Must have a divine caster level at least equal to infusion's caster level + int nDivineLvl = GetPrCAdjustedCasterLevelByType(TYPE_DIVINE, oCaster); + + if(DEBUG) DoDebug("inc_infusion >> DoInfusionUseChecks: nDivineLvl is "+IntToString(nDivineLvl)+"."); + + if (nDivineLvl < nItemSpellLvl) + { + FloatingTextStringOnCreature("Your divine caster level is too low to cast this spell from an infusion.", oCaster); + return FALSE; + } + + return TRUE; +} + +/* int DoInfusionUseChecks(object oCaster, object oItem, int nSpellID) +{ + int bPnPHerbs = GetPRCSwitch(PRC_CREATE_INFUSION_OPTIONAL_HERBS); + + if(GetBaseItemType(oItem) != BASE_ITEM_INFUSED_HERB) + { + FloatingTextStringOnCreature("Not casting from an Infused Herb", oCaster); + return FALSE; + + } + + int nItemSpellLvl = GetCastSpellCasterLevelFromItem(oItem, nSpellID); + if (bPnPHerbs && nItemSpellLvl == -1) + { + FloatingTextStringOnCreature("Item has no spellcaster level.", oCaster); + return FALSE; + } + + // Find relevant class for the spell + int nClassCaster = FindSpellCastingClass(oCaster, nSpellID); + + if(DEBUG) DoDebug("nClassCaster is: "+IntToString(nClassCaster)+"."); + + if(GetMaxDivineSpellLevel(oCaster, nClassCaster) < 1 ) + { + FloatingTextStringOnCreature("You must be a divine spellcaster to activate an infusion.", oCaster); + return FALSE; + } + + // Must have spell on class list + if (!GetHasSpellOnClassList(oCaster, nSpellID)) + { + FloatingTextStringOnCreature("You must have a spell on one of your class spell lists to cast it from an infusion.", oCaster); + return FALSE; + } + + // Must meet ability requirement: Ability score >= 10 + spell level + int nSpellLevel = PRCGetSpellLevelForClass(nSpellID, nClassCaster); + int nClassAbility = GetAbilityScoreForClass(nClassCaster, oCaster); + + if(DEBUG) DoDebug("inc_infusion >> DoInfusionUseChecks: nClassCaster is "+IntToString(nClassCaster)+"."); + if(DEBUG) DoDebug("inc_infusion >> DoInfusionUseChecks: Class nSpellLevel is "+IntToString(nSpellLevel)+"."); + if(DEBUG) DoDebug("inc_infusion >> DoInfusionUseChecks: nClassAbility is "+IntToString(nClassAbility)+"."); + + if (nClassAbility < 10 + nSpellLevel) + { + FloatingTextStringOnCreature("You must meet ability score requirement to cast spell from infusion.", oCaster); + return FALSE; + } + + // Must have a divine caster level at least equal to infusion's caster level + int nDivineLvl = GetPrCAdjustedCasterLevelByType(TYPE_DIVINE, oCaster); + + if(DEBUG) DoDebug("inc_infusion >> DoInfusionUseChecks: nDivineLvl is "+IntToString(nDivineLvl)+"."); + + if (nDivineLvl < nItemSpellLvl) + { + FloatingTextStringOnCreature("Your divine caster level is too low to cast this spell from an infusion.", oCaster); + return FALSE; + } + + return TRUE; +} + */ +/** + * @brief Retrieves the maximum divine spell level known by the caster for a given class. + * + * This function checks the caster's local integers named "PRC_DivSpell1" through "PRC_DivSpell9" + * in descending order to determine the highest divine spell level available. + * It returns the highest spell level for which the corresponding local int is false (zero). + * + * @param oCaster The creature whose divine spell levels are being checked. + * @param nClass The class index for which to check the divine spell level (currently unused). + * + * @return The highest divine spell level known by the caster (1 to 9). + */ +int GetMaxDivineSpellLevel(object oCaster, int nClass) +{ + int i = 9; + for (i; i > 0; i--) + { + if(!GetLocalInt(oCaster, "PRC_DivSpell"+IntToString(i))) + return i; + } + return 1; +} + +/** + * @brief Retrieves the spell school of an herb based on its resref by looking it up in the craft_infusion.2da file. + * + * This function searches the "craft_infusion" 2DA for a row matching the herb's resref. + * If found, it returns the corresponding spell school as an integer constant. + * If not found or the SpellSchool column is missing/invalid, it returns -1. + * + * @param oHerb The herb object to check. + * + * @return The spell school constant corresponding to the herb's infusion spell school, + * or -1 if the herb is invalid, not found, or the data is missing. + */ +int GetHerbsSpellSchool(object oHerb) +{ + if (!GetIsObjectValid(oHerb)) return -1; + + string sResref = GetResRef(oHerb); + int nRow = 0; + string sRowResref; + + while (nRow < 200) + { + sRowResref = Get2DACache("craft_infusion", "Resref", nRow); + if (sRowResref == "") break; + if (sRowResref == sResref) + { + string sHerbSpellSchool = Get2DAString("craft_infusion", "SpellSchool", nRow); + + if (sHerbSpellSchool == "A") return SPELL_SCHOOL_ABJURATION; + else if (sHerbSpellSchool == "C") return SPELL_SCHOOL_CONJURATION; + else if (sHerbSpellSchool == "D") return SPELL_SCHOOL_DIVINATION; + else if (sHerbSpellSchool == "E") return SPELL_SCHOOL_ENCHANTMENT; + else if (sHerbSpellSchool == "V") return SPELL_SCHOOL_EVOCATION; + else if (sHerbSpellSchool == "I") return SPELL_SCHOOL_ILLUSION; + else if (sHerbSpellSchool == "N") return SPELL_SCHOOL_NECROMANCY; + else if (sHerbSpellSchool == "T") return SPELL_SCHOOL_TRANSMUTATION; + else return SPELL_SCHOOL_GENERAL; + + return -1; + } + nRow++; + } + return -1; // Not found +} + +/** + * @brief Retrieves the infusion spell level of an herb by matching its resref in the craft_infusion.2da file. + * + * This function searches the "craft_infusion" 2DA for a row matching the herb's resref. + * If found, it returns the spell level from the SpellLevel column as an integer. + * If not found or the column is missing, it returns -1. + * + * @param oHerb The herb object whose infusion spell level is to be retrieved. + * + * @return The spell level as an integer if found, or -1 if the herb is invalid, not found, or the column is missing. + */ +int GetHerbsInfusionSpellLevel(object oHerb) +{ + if (!GetIsObjectValid(oHerb)) return -1; + + string sResref = GetResRef(oHerb); + int nRow = 0; + string sRowResref; + + // Brute-force loop — adjust limit if your 2DA has more than 500 rows + while (nRow < 200) + { + sRowResref = Get2DACache("craft_infusion", "Resref", nRow); + if (sRowResref == "") break; // End of valid rows + if (sRowResref == sResref) + { + string sSpellLevelStr = Get2DAString("craft_infusion", "SpellLevel", nRow); + return StringToInt(sSpellLevelStr); + } + nRow++; + } + return -1; // Not found +} + +/** + * @brief Retrieves the caster level of a specific cast-spell item property from an item. + * + * This function iterates through the item properties of the given item, searching for an + * ITEM_PROPERTY_CAST_SPELL_CASTER_LEVEL property that matches the specified spell ID. + * If found, it returns the caster level value stored in the item property. + * + * @param oItem The item object to check. + * @param nSpellID The spell ID to match against the item property. + * + * @return The caster level associated with the matching cast-spell item property, + * or -1 if no matching property is found. + */ +int GetCastSpellCasterLevelFromItem(object oItem, int nSpellID) +{ + int nFoundCL = -1; + + itemproperty ip = GetFirstItemProperty(oItem); + while (GetIsItemPropertyValid(ip)) + { + int nType = GetItemPropertyType(ip); + + // First preference: PRC's CASTER_LEVEL itemprop + if (nType == ITEM_PROPERTY_CAST_SPELL_CASTER_LEVEL) + { + int nSubType = GetItemPropertySubType(ip); + string sSpellIDStr = Get2DAString("iprp_spells", "SpellIndex", nSubType); + int nSubSpellID = StringToInt(sSpellIDStr); + + if (nSubSpellID == nSpellID) + { + return GetItemPropertyCostTableValue(ip); // Found exact CL + } + } + + // Fallback: vanilla CAST_SPELL property + if (nType == ITEM_PROPERTY_CAST_SPELL && nFoundCL == -1) + { + int nSubType = GetItemPropertySubType(ip); + string sSpellIDStr = Get2DAString("iprp_spells", "SpellIndex", nSubType); + int nSubSpellID = StringToInt(sSpellIDStr); + + if (nSubSpellID == nSpellID) + { + // Vanilla uses CostTableValue for *number of uses*, not CL, + // so we’ll assume default caster level = spell level * 2 - 1 + int nSpellLevel = StringToInt(Get2DAString("spells", "Innate", nSubSpellID)); + nFoundCL = nSpellLevel * 2 - 1; // default NWN caster level rule + } + } + + ip = GetNextItemProperty(oItem); + } + + return nFoundCL; // -1 if not found +} + + +/** + * @brief Checks if a given spell ID is present on the specified class's spell list for the caster. + * + * This function determines the spell level of the spell for the given class using PRCGetSpellLevelForClass. + * If the spell level is -1, the spell is not on the class's spell list. + * Otherwise, the spell is considered to be on the class spell list. + * + * @param oCaster The creature object casting or querying the spell. + * @param nSpellID The spell ID to check. + * @param nClass The class index to check the spell list against. + * + * @return TRUE if the spell is on the class's spell list; FALSE otherwise. + */ +int GetIsClassSpell(object oCaster, int nSpellID, int nClass) +{ + if(DEBUG) DoDebug("inc_infusion >> GetIsClassSpell: nSpellID is: "+IntToString(nSpellID)+"."); + if(DEBUG) DoDebug("inc_infusion >> GetIsClassSpell: nClass is: "+IntToString(nClass)+"."); + + int nSpellLevel = PRCGetSpellLevelForClass(nSpellID, nClass); + if (nSpellLevel == -1) + { + if(DEBUG) DoDebug("inc_infusion >> GetIsClassSpell: SpellLevel is "+IntToString(nSpellLevel)+"."); + if(DEBUG) DoDebug("inc_infusion >> GetIsClassSpell: Spell "+IntToString(nSpellID)+" is not in spelllist of "+IntToString(nClass)+"."); + return FALSE; + } + return TRUE; +} + +/** + * @brief Checks if the caster has the specified spell on any of their class spell lists. + * + * This function iterates through all classes the caster has (up to position 8), + * and returns TRUE if the spell is found on any class's spell list. + * + * @param oCaster The creature object to check. + * @param nSpellID The spell ID to search for. + * + * @return TRUE if the spell is present on at least one of the caster's class spell lists; + * FALSE otherwise. + */ +int GetHasSpellOnClassList(object oCaster, int nSpellID) +{ + int i; + for (i = 0; i <= 8; i++) + { + int nClass = GetClassByPosition(i, oCaster); + if (nClass == CLASS_TYPE_INVALID) continue; + + if (GetIsClassSpell(oCaster, nSpellID, nClass)) + { + if(DEBUG) DoDebug("inc_infusion >> GetHasSpellOnClassList: Class spell found."); + return TRUE; + } + } + if(DEBUG) DoDebug("inc_infusion >> GetHasSpellOnClassList: Class spell not found."); + return FALSE; +} + +/** + * @brief Applies a poison nausea effect to the user when infusion use fails. + * + * This function performs an immediate Fortitude saving throw against poison DC based on infusion caster level. + * If the user fails and is not immune to poison, an infusion nausea effect is applied, replacing any existing one. + * A second saving throw is scheduled after 1 minute to attempt to remove the effect. + * + * @param oUser The creature who used the infusion and may be poisoned. + * @param nInfusionCL The caster level of the infusion used, affecting the DC of the saving throw. + */ +void ApplyInfusionPoison(object oUser, int nInfusionCL) +{ + int nDC = 10 + (nInfusionCL / 2); + int bImmune = GetIsImmune(oUser, IMMUNITY_TYPE_POISON); + + // First save immediately + if (!bImmune && !PRCMySavingThrow(SAVING_THROW_FORT, oUser, nDC, SAVING_THROW_TYPE_POISON)) + { + // Remove existing infusion poison nausea effect before applying new + effect eOld = GetFirstEffect(oUser); + while (GetIsEffectValid(eOld)) + { + if (GetEffectTag(eOld) == "INFUSION_POISON_TAG") + { + RemoveEffect(oUser, eOld); + break; // Assuming only one effect with this tag + } + eOld = GetNextEffect(oUser); + } + + effect eNausea = EffectNausea(oUser, 60.0f); + + TagEffect(eNausea, "INFUSION_POISON_TAG"); + FloatingTextStringOnCreature("The infusion has made you nauseous.", oUser); + ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eNausea, oUser, RoundsToSeconds(10)); + } + + // Second save 1 minute later + if (!bImmune) + { + DelayCommand(60.0, InfusionSecondSave(oUser, nDC)); + } +} + +void InfusionSecondSave(object oUser, int nDC) +{ + if (!PRCMySavingThrow(SAVING_THROW_FORT, oUser, nDC, SAVING_THROW_TYPE_POISON)) + { + FloatingTextStringOnCreature("The infusion has made you nauseous.", oUser); + ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectNausea(oUser, 60.0f), oUser, RoundsToSeconds(10)); + } +} + +//:: void main (){} \ No newline at end of file diff --git a/src/include/inc_lookups.nss b/src/include/inc_lookups.nss index e5a5124..fa68b5c 100644 --- a/src/include/inc_lookups.nss +++ b/src/include/inc_lookups.nss @@ -242,25 +242,27 @@ void SetupLookupStage(object oMod, int n) case 11: SetLkupStage(n, oMod, CLASS_TYPE_DRAGON_SHAMAN, "cls_inv_drgshm"); break; case 12: SetLkupStage(n, oMod, CLASS_TYPE_WARLOCK, "cls_inv_warlok"); break; case 13: SetLkupStage(n, oMod, CLASS_TYPE_ARCHIVIST, "cls_spell_archv"); break; - case 14: SetLkupStage(n, oMod, CLASS_TYPE_ASSASSIN, "cls_spell_asasin"); break; - case 15: SetLkupStage(n, oMod, CLASS_TYPE_BARD, "cls_spell_bard"); break; - case 16: SetLkupStage(n, oMod, CLASS_TYPE_BEGUILER, "cls_spell_beguil"); break; - case 17: SetLkupStage(n, oMod, CLASS_TYPE_DREAD_NECROMANCER, "cls_spell_dnecro"); break; - case 18: SetLkupStage(n, oMod, CLASS_TYPE_DUSKBLADE, "cls_spell_duskbl"); break; - case 19: SetLkupStage(n, oMod, CLASS_TYPE_FAVOURED_SOUL, "cls_spell_favsol"); break; - case 20: SetLkupStage(n, oMod, CLASS_TYPE_HARPER, "cls_spell_harper"); break; - case 21: SetLkupStage(n, oMod, CLASS_TYPE_HEXBLADE, "cls_spell_hexbl"); break; - case 22: SetLkupStage(n, oMod, CLASS_TYPE_JUSTICEWW, "cls_spell_justww"); break; - case 23: SetLkupStage(n, oMod, CLASS_TYPE_SORCERER, "cls_spell_sorc"); break; - case 24: SetLkupStage(n, oMod, CLASS_TYPE_SUBLIME_CHORD, "cls_spell_schord"); break; - case 25: SetLkupStage(n, oMod, CLASS_TYPE_SUEL_ARCHANAMACH, "cls_spell_suel"); break; - case 26: SetLkupStage(n, oMod, CLASS_TYPE_VIGILANT, "cls_spell_vigil"); break; - case 27: SetLkupStage(n, oMod, CLASS_TYPE_WARMAGE, "cls_spell_wrmage"); break; - case 28: SetLkupStage(n, oMod, CLASS_TYPE_KNIGHT_WEAVE, "cls_spell_kngtwv"); break; - case 29: SetLkupStage(n, oMod, CLASS_TYPE_PSYCHIC_ROGUE, "cls_psipw_psyrog"); break; - case 30: SetLkupStage(n, oMod, CLASS_TYPE_SHADOWCASTER, "cls_myst_shdcst"); break; - case 31: SetLkupStage(n, oMod, CLASS_TYPE_SHADOWSMITH, "cls_myst_shdsmt"); break; - case 32: SetLkupStage(n, oMod, CLASS_TYPE_CELEBRANT_SHARESS, "cls_spell_sharss"); break; + case 14: SetLkupStage(n, oMod, CLASS_TYPE_BARD, "cls_spell_bard"); break; + case 15: SetLkupStage(n, oMod, CLASS_TYPE_BEGUILER, "cls_spell_beguil"); break; + case 16: SetLkupStage(n, oMod, CLASS_TYPE_DREAD_NECROMANCER, "cls_spell_dnecro"); break; + case 17: SetLkupStage(n, oMod, CLASS_TYPE_DUSKBLADE, "cls_spell_duskbl"); break; + case 18: SetLkupStage(n, oMod, CLASS_TYPE_FAVOURED_SOUL, "cls_spell_favsol"); break; + case 19: SetLkupStage(n, oMod, CLASS_TYPE_HARPER, "cls_spell_harper"); break; + case 20: SetLkupStage(n, oMod, CLASS_TYPE_HEXBLADE, "cls_spell_hexbl"); break; + case 21: SetLkupStage(n, oMod, CLASS_TYPE_JUSTICEWW, "cls_spell_justww"); break; + case 22: SetLkupStage(n, oMod, CLASS_TYPE_SORCERER, "cls_spell_sorc"); break; + case 23: SetLkupStage(n, oMod, CLASS_TYPE_SUBLIME_CHORD, "cls_spell_schord"); break; + case 24: SetLkupStage(n, oMod, CLASS_TYPE_SUEL_ARCHANAMACH, "cls_spell_suel"); break; + case 25: SetLkupStage(n, oMod, CLASS_TYPE_VIGILANT, "cls_spell_vigil"); break; + case 26: SetLkupStage(n, oMod, CLASS_TYPE_WARMAGE, "cls_spell_wrmage"); break; + case 27: SetLkupStage(n, oMod, CLASS_TYPE_KNIGHT_WEAVE, "cls_spell_kngtwv"); break; + case 28: SetLkupStage(n, oMod, CLASS_TYPE_PSYCHIC_ROGUE, "cls_psipw_psyrog"); break; + case 29: SetLkupStage(n, oMod, CLASS_TYPE_SHADOWCASTER, "cls_myst_shdcst"); break; + case 30: SetLkupStage(n, oMod, CLASS_TYPE_SHADOWSMITH, "cls_myst_shdsmt"); break; + case 31: SetLkupStage(n, oMod, CLASS_TYPE_CELEBRANT_SHARESS, "cls_spell_sharss"); break; + + //:: These were all moved to the Bioware spellbooks -Jaysyn + //case 14: SetLkupStage(n, oMod, CLASS_TYPE_ASSASSIN, "cls_spell_asasin"); break; //case 46: SetLkupStage(n, oMod, CLASS_TYPE_CULTIST_SHATTERED_PEAK, "cls_spell_cultst"); break; //case 40: SetLkupStage(n, oMod, CLASS_TYPE_NENTYAR_HUNTER, "cls_spell_hunter"); break; //case 28: SetLkupStage(n, oMod, CLASS_TYPE_SHADOWLORD, "cls_spell_tfshad"); break; @@ -528,7 +530,7 @@ int SpellToSpellbookID(int nSpell) int nOutSpellID = GetLocalInt(oWP, /*"PRC_GetRowFromSpellID_" + */IntToString(nSpell)); if(nOutSpellID == 0) nOutSpellID = -1; - //if(DEBUG) DoDebug("SpellToSpellbookID(" + IntToString(nSpell) + ", " + sFile + ") = " + IntToString(nOutSpellID)); + if(DEBUG) DoDebug("inc_lookup >> SpellToSpellbookID: (nSpell: " + IntToString(nSpell) + ") = nOutSpellID: " + IntToString(nOutSpellID)); return nOutSpellID; } diff --git a/src/include/inc_newspellbook.nss b/src/include/inc_newspellbook.nss index 909b93d..542e2d5 100644 --- a/src/include/inc_newspellbook.nss +++ b/src/include/inc_newspellbook.nss @@ -8,7 +8,7 @@ Make cls_spcr_*.2da Make blank cls_spell_*.2da Add cls_spgn_*.2da to classes.2da Add class entry in prc_classes.2da -Add the spellbook feat (#1999) to cls_feat_*.2da at the appropriate level +Add the spellbook feat (#1999) to cls_feat_*.2da at the appropriate level (not needed for NWN:EE) Add class to PRCGetSpellSaveDC() in prc_add_spell_dc Add class to GetSpellbookTypeForClass() below Add class to GetAbilityScoreForClass() below @@ -119,6 +119,7 @@ int GetSpellbookTypeForClass(int nClass) switch(nClass) { case CLASS_TYPE_ARCHIVIST: + case CLASS_TYPE_ASSASSIN: case CLASS_TYPE_BLACKGUARD: case CLASS_TYPE_BLIGHTER: case CLASS_TYPE_CLERIC: @@ -141,7 +142,6 @@ int GetSpellbookTypeForClass(int nClass) case CLASS_TYPE_VIGILANT: case CLASS_TYPE_WIZARD: return SPELLBOOK_TYPE_PREPARED; - case CLASS_TYPE_ASSASSIN: case CLASS_TYPE_BARD: case CLASS_TYPE_BEGUILER: case CLASS_TYPE_CELEBRANT_SHARESS: @@ -559,7 +559,7 @@ int bKnowsAllClassSpells(int nClass) { //case CLASS_TYPE_WIZARD: case CLASS_TYPE_ARCHIVIST: - case CLASS_TYPE_ASSASSIN: + //case CLASS_TYPE_ASSASSIN: case CLASS_TYPE_BARD: case CLASS_TYPE_CELEBRANT_SHARESS: case CLASS_TYPE_CULTIST_SHATTERED_PEAK: @@ -580,7 +580,79 @@ int bKnowsAllClassSpells(int nClass) return TRUE; } + int GetSpellKnownMaxCount(int nLevel, int nSpellLevel, int nClass, object oPC) +{ + // If the character doesn't have any spell slots available on for this level, it can't know any spells of that level either + if(!GetSlotCount(nLevel, nSpellLevel, GetAbilityScoreForClass(nClass, oPC), nClass)) + { + if(DEBUG) DoDebug("GetSpellKnownMaxCount: No slots available for " + IntToString(nClass) + " level " + IntToString(nLevel) + " circle " + IntToString(nSpellLevel)); + return 0; + } + + int nKnown; + string sFile = Get2DACache("classes", "SpellKnownTable", nClass); + string sKnown = Get2DACache(sFile, "SpellLevel" + IntToString(nSpellLevel), nLevel - 1); + + if(DEBUG) + { + DoDebug("GetSpellKnownMaxCount Details:"); + DoDebug("- Class: " + IntToString(nClass)); + DoDebug("- Passed Level: " + IntToString(nLevel)); + DoDebug("- Base Class Level: " + IntToString(GetLevelByClass(nClass, oPC))); + DoDebug("- Effective Level: " + IntToString(GetSpellslotLevel(nClass, oPC))); + DoDebug("- Spell Level: " + IntToString(nSpellLevel)); + DoDebug("- SpellKnownTable: " + sFile); + DoDebug("- MaxKnown from 2DA: " + sKnown); + } + + if(sKnown == "") + { + nKnown = -1; + if(DEBUG) DoDebug("GetSpellKnownMaxCount: Problem getting known numbers"); + } + else + nKnown = StringToInt(sKnown); + + if(nKnown == -1) + return 0; + + // COMPLETELY REWROTE THIS SECTION + // Bard and Sorcerer logic for prestige class advancement + if(nClass == CLASS_TYPE_SORCERER || nClass == CLASS_TYPE_BARD) + { + int baseClassLevel = GetLevelByClass(nClass, oPC); + int effectiveLevel = GetSpellslotLevel(nClass, oPC); + + // Debug the values we're checking + if(DEBUG) + { + DoDebug("Spont caster check - Base level: " + IntToString(baseClassLevel) + + ", Effective level: " + IntToString(effectiveLevel)); + } + + // If they have prestige class advancement OR special feats, they should get spells + if(effectiveLevel > baseClassLevel || + GetHasFeat(FEAT_DRACONIC_GRACE, oPC) || + GetHasFeat(FEAT_DRACONIC_BREATH, oPC)) + { + // Allow them to get spells - do nothing here, return nKnown at the end + if(DEBUG) DoDebug("Spontaneous caster eligible for new spells"); + } + else + { + // No advancement, no special feats - no new spells + if(DEBUG) DoDebug("Spontaneous caster NOT eligible for new spells"); + return 0; + } + } + + if(DEBUG) DoDebug("Final spell known count: " + IntToString(nKnown)); + return nKnown; +} + + +/* int GetSpellKnownMaxCount(int nLevel, int nSpellLevel, int nClass, object oPC) { // If the character doesn't have any spell slots available on for this level, it can't know any spells of that level either // @todo Check rules. There might be cases where this doesn't hold @@ -588,22 +660,9 @@ int GetSpellKnownMaxCount(int nLevel, int nSpellLevel, int nClass, object oPC) return 0; int nKnown; string sFile; - // Bioware casters use their classes.2da-specified tables - /*if( nClass == CLASS_TYPE_WIZARD - || nClass == CLASS_TYPE_SORCERER - || nClass == CLASS_TYPE_BARD - || nClass == CLASS_TYPE_CLERIC - || nClass == CLASS_TYPE_DRUID - || nClass == CLASS_TYPE_PALADIN - || nClass == CLASS_TYPE_RANGER) - {*/ - sFile = Get2DACache("classes", "SpellKnownTable", nClass); - /*} - else - { - sFile = Get2DACache("classes", "FeatsTable", nClass); - sFile = "cls_spkn" + GetStringRight(sFile, GetStringLength(sFile) - 8); // Hardcoded the cls_ part. It's not as if any class uses some other prefix - Ornedan, 20061231 - }*/ + + sFile = Get2DACache("classes", "SpellKnownTable", nClass); + string sKnown = Get2DACache(sFile, "SpellLevel" + IntToString(nSpellLevel), nLevel - 1); if(DEBUG) DoDebug("GetSpellKnownMaxCount(" + IntToString(nLevel) + ", " + IntToString(nSpellLevel) + ", " + IntToString(nClass) + ", " + GetName(oPC) + ") = " + sKnown); @@ -626,6 +685,7 @@ int GetSpellKnownMaxCount(int nLevel, int nSpellLevel, int nClass, object oPC) } return nKnown; } + */ int GetSpellKnownCurrentCount(object oPC, int nSpellLevel, int nClass) { @@ -693,6 +753,44 @@ int GetSpellKnownCurrentCount(object oPC, int nSpellLevel, int nClass) } int GetSpellUnknownCurrentCount(object oPC, int nSpellLevel, int nClass) +{ + // Get the lookup token created by MakeSpellbookLevelLoop() + string sTag = "SpellLvl_" + IntToString(nClass) + "_Level_" + IntToString(nSpellLevel); + object oCache = GetObjectByTag(sTag); + if(!GetIsObjectValid(oCache)) + { + if(DEBUG) DoDebug("GetSpellUnknownCurrentCount: " + sTag + " is not valid"); + + // Add code to create the missing lookup object + if(DEBUG) DoDebug("Attempting to create missing spell lookup token"); + ExecuteScript("prc_create_spellb", oPC); + + // Try again after creating it + oCache = GetObjectByTag(sTag); + if(!GetIsObjectValid(oCache)) + { + if(DEBUG) DoDebug("Still couldn't create spell lookup token"); + return 0; + } + else + { + if(DEBUG) DoDebug("Successfully created spell lookup token"); + } + } + + // Read the total number of spells on the given level and determine how many are already known + int nTotal = array_get_size(oCache, "Lkup"); + int nKnown = GetSpellKnownCurrentCount(oPC, nSpellLevel, nClass); + int nUnknown = nTotal - nKnown; + + if(DEBUG) DoDebug("GetSpellUnknownCurrentCount(" + GetName(oPC) + ", " + IntToString(nSpellLevel) + ", " + IntToString(nClass) + ") = " + IntToString(nUnknown)); + if(DEBUG) DoDebug(" Total spells in lookup: " + IntToString(nTotal) + ", Known spells: " + IntToString(nKnown)); + + return nUnknown; +} + + +/* int GetSpellUnknownCurrentCount(object oPC, int nSpellLevel, int nClass) { // Get the lookup token created by MakeSpellbookLevelLoop() string sTag = "SpellLvl_" + IntToString(nClass) + "_Level_" + IntToString(nSpellLevel); @@ -709,7 +807,7 @@ int GetSpellUnknownCurrentCount(object oPC, int nSpellLevel, int nClass) if(DEBUG) DoDebug("GetSpellUnknownCurrentCount(" + GetName(oPC) + ", " + IntToString(nSpellLevel) + ", " + IntToString(nClass) + ") = " + IntToString(nUnknown)); return nUnknown; -} +} */ void AddSpellUse(object oPC, int nSpellbookID, int nClass, string sFile, string sArrayName, int nSpellbookType, object oSkin, int nFeatID, int nIPFeatID, string sIDX = "") { @@ -850,7 +948,7 @@ void SetupSpells(object oPC, int nClass) int nAbility = GetAbilityScoreForClass(nClass, oPC); int nSpellbookType = GetSpellbookTypeForClass(nClass); - if(DEBUG) DoDebug("SetupSpells\n" + if(DEBUG) DoDebug("SetupSpells()\n" + "nClass = " + IntToString(nClass) + "\n" + "nSpellslotLevel = " + IntToString(nLevel) + "\n" + "nAbility = " + IntToString(nAbility) + "\n" @@ -1178,7 +1276,7 @@ void CastSpontaneousSpell(int nClass, int bInstantSpell = FALSE) else if(GetLocalInt(OBJECT_SELF, "PRC_metamagic_state") == 1) SetLocalInt(OBJECT_SELF, "MetamagicFeatAdjust", 0); } - + if (DEBUG) DoDebug("CastSpontaneousSpell(): nSpellLevel is: "+IntToString(nSpellLevel)+"."); CheckSpontSlots(nClass, nSpellID, nSpellLevel); if(GetLocalInt(OBJECT_SELF, "NSB_Cast")) ActionDoCommand(CheckSpontSlots(nClass, nSpellID, nSpellLevel, TRUE)); @@ -1330,6 +1428,8 @@ void NewSpellbookSpell(int nClass, int nSpellbookType, int nMetamagic = METAMAGI string sFile = GetFileForClass(nClass); int nSpellLevel = StringToInt(Get2DACache(sFile, "Level", nSpellbookID)); + + if (DEBUG) DoDebug("inc_newspellbook >> NewSpellbookSpell(): nSpellbookType is: "+IntToString(nSpellbookType)+"."); // Make sure the caster has uses of this spell remaining // 2009-9-20: Add metamagic feat abilities. -N-S @@ -1371,13 +1471,14 @@ void NewSpellbookSpell(int nClass, int nSpellbookType, int nMetamagic = METAMAGI else if(nSpellLevel > 9)//now test the spell level { nMetamagic = METAMAGIC_NONE; - ActionDoCommand(SendMessageToPC(oPC, "Modified spell level is to high! Casting spell without metamagic")); + ActionDoCommand(SendMessageToPC(oPC, "Modified spell level is too high! Casting spell without metamagic")); nSpellLevel = nSpellSlotLevel; } else if(GetLocalInt(oPC, "PRC_metamagic_state") == 1) SetLocalInt(oPC, "MetamagicFeatAdjust", 0); } - + + if (DEBUG) DoDebug("inc_newspellbook >> NewSpellbookSpell(): nSpellLevel is: "+IntToString(nSpellLevel)+"."); CheckSpontSlots(nClass, nSpellID, nSpellLevel); if(GetLocalInt(oPC, "NSB_Cast")) ActionDoCommand(CheckSpontSlots(nClass, nSpellID, nSpellLevel, TRUE)); @@ -1460,7 +1561,7 @@ void CheckPrepSlots(int nClass, int nSpellID, int nSpellbookID, int bIsAction = { DeleteLocalInt(OBJECT_SELF, "NSB_Cast"); int nCount = persistant_array_get_int(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass), nSpellbookID); - if(DEBUG) DoDebug("NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + "[" + IntToString(nSpellbookID) + "] = " + IntToString(nCount)); + if(DEBUG) DoDebug("NewSpellbookSpell >> CheckPrepSlots: NewSpellbookMem_" + IntToString(nClass) + "[SpellbookID: " + IntToString(nSpellbookID) + "] = " + IntToString(nCount)); if(nCount < 1) { string sSpellName = GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", nSpellID))); @@ -1486,7 +1587,7 @@ void CheckSpontSlots(int nClass, int nSpellID, int nSpellSlotLevel, int bIsActio { DeleteLocalInt(OBJECT_SELF, "NSB_Cast"); int nCount = persistant_array_get_int(OBJECT_SELF, "NewSpellbookMem_" + IntToString(nClass), nSpellSlotLevel); - if(DEBUG) DoDebug("NewSpellbookSpell: NewSpellbookMem_" + IntToString(nClass) + "[" + IntToString(nSpellSlotLevel) + "] = " + IntToString(nCount)); + if(DEBUG) DoDebug("NewSpellbookSpell >> CheckSpontSlots: NewSpellbookMem_" + IntToString(nClass) + "[SpellSlotLevel: " + IntToString(nSpellSlotLevel) + "] = " + IntToString(nCount)); if(nCount < 1) { // "You have no castings of spells of level " + IntToString(nSpellLevel) + " remaining" diff --git a/src/include/inc_switch_setup.nss b/src/include/inc_switch_setup.nss index afffaae..d21b16c 100644 --- a/src/include/inc_switch_setup.nss +++ b/src/include/inc_switch_setup.nss @@ -720,7 +720,7 @@ void SetDefaultFileEnds() SetPRCSwitch("PRC_FILE_END_polymorph", 155); SetPRCSwitch("PRC_FILE_END_portraits", 1300); SetPRCSwitch("PRC_FILE_END_prc_craft_alchem", 37); - SetPRCSwitch("PRC_FILE_END_prc_craft_gen_it", 204); + SetPRCSwitch("PRC_FILE_END_prc_craft_gen_it", 253); SetPRCSwitch("PRC_FILE_END_prc_craft_poison", 62); SetPRCSwitch("PRC_FILE_END_prc_domains", 59); SetPRCSwitch("PRC_FILE_END_prc_familiar", 10); @@ -767,7 +767,7 @@ void SetDefaultFileEnds() SetPRCSwitch("PRC_FILE_END_soundset", 453); SetPRCSwitch("PRC_FILE_END_soundsettype", 4); SetPRCSwitch("PRC_FILE_END_soundtypes", 1); - SetPRCSwitch("PRC_FILE_END_spells", 19348); + SetPRCSwitch("PRC_FILE_END_spells", 19400); //SetPRCSwitch("PRC_FILE_END_spellschools", 9); SetPRCSwitch("PRC_FILE_END_statescripts", 35); SetPRCSwitch("PRC_FILE_END_stringtokens", 92); @@ -876,6 +876,8 @@ void CreateSwitchNameArray() array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_BIOWARE_HARM); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_BIOWARE_NEUTRALIZE_POISON); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_BIOWARE_REMOVE_DISEASE); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_BIO_UNLEARN); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_UNLEARN_SPELL_MAXNR); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_TIMESTOP_BIOWARE_DURATION); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_TIMESTOP_LOCAL); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_TIMESTOP_NO_HOSTILE); @@ -1067,14 +1069,16 @@ void CreateSwitchNameArray() array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_ROD_CASTER_LEVEL); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_STAFF_CASTER_LEVEL); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFTING_BASE_ITEMS); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), X2_CI_BREWPOTION_MAXLEVEL); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), X2_CI_BREWPOTION_COSTMODIFIER); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), X2_CI_SCRIBESCROLL_COSTMODIFIER); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), X2_CI_CRAFTWAND_MAXLEVEL); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), X2_CI_CRAFTWAND_COSTMODIFIER); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_X2_BREWPOTION_MAXLEVEL); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_X2_BREWPOTION_COSTMODIFIER); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_X2_SCRIBESCROLL_COSTMODIFIER); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_X2_CRAFTWAND_MAXLEVEL); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_X2_CRAFTWAND_COSTMODIFIER); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_X2_CREATEINFUSION_COSTMODIFIER); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFTING_ARBITRARY); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFTING_COST_SCALE); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFTING_TIME_SCALE); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CREATE_INFUSION_CASTER_LEVEL); //spells diff --git a/src/include/moi_inc_moifunc.nss b/src/include/moi_inc_moifunc.nss index d48a441..a2b0a3b 100644 --- a/src/include/moi_inc_moifunc.nss +++ b/src/include/moi_inc_moifunc.nss @@ -1182,7 +1182,7 @@ int GetMaxEssentiaCapacityFeat(object oMeldshaper) // Don't allow more than they have if (nMax > GetTotalUsableEssentia(oMeldshaper)) nMax = GetTotalUsableEssentia(oMeldshaper); - //if (DEBUG) DoDebug("GetMaxEssentiaCapacityFeat: nHD "+IntToString(nHD)+" nMax "+IntToString(nMax)); + if(DEBUG) DoDebug("GetMaxEssentiaCapacityFeat: nHD "+IntToString(nHD)+" nMax "+IntToString(nMax)); return nMax; } diff --git a/src/include/nw_inc_gff.nss b/src/include/nw_inc_gff.nss new file mode 100644 index 0000000..533cf21 --- /dev/null +++ b/src/include/nw_inc_gff.nss @@ -0,0 +1,623 @@ +// This is a helper library for advanced use: It allows constructing arbitrary gff data. +// You can then spawn your object via JsonToObject(). +// +// The data format is the same as https://github.com/niv/neverwinter.nim@1.4.3+. +// +// Example: +// +// json j = GffCreateObject(OBJECT_TYPE_ITEM); +// j = GffAddInt(j, "BaseItem", BASE_ITEM_BELT); +// j = GffAddInt(j, "ModelPart1", 12); +// j = GffAddLocString(j, "LocalizedName", "hi!"); +// object belt = JsonToObject(j, GetLocation(OBJECT_SELF)); + + +const string GFF_FIELD_TYPE_STRUCT = "struct"; +const string GFF_FIELD_TYPE_LIST = "list"; +const string GFF_FIELD_TYPE_BYTE = "byte"; +const string GFF_FIELD_TYPE_CHAR = "char"; +const string GFF_FIELD_TYPE_WORD = "word"; +const string GFF_FIELD_TYPE_SHORT = "short"; +const string GFF_FIELD_TYPE_DWORD = "dword"; +const string GFF_FIELD_TYPE_INT = "int"; +const string GFF_FIELD_TYPE_DWORD64 = "dword64"; +const string GFF_FIELD_TYPE_INT64 = "int64"; +const string GFF_FIELD_TYPE_FLOAT = "float"; +const string GFF_FIELD_TYPE_DOUBLE = "double"; +const string GFF_FIELD_TYPE_RESREF = "resref"; +const string GFF_FIELD_TYPE_STRING = "cexostring"; +const string GFF_FIELD_TYPE_LOC_STRING = "cexolocstring"; + + +// Create a empty object of the given type. You need to manually fill in all +// GFF data with GffAddXXX. This will require understanding of the GFF file format +// and what data fields each object type requires. +json GffCreateObject(int nObjectType); +// Create a combined area format(CAF) object. You need to manually create the ARE and GIT objects with their required data fields. +json GffCreateArea(json jARE, json jGIT); + +// Returns the OBJECT_TYPE_* of jGff. +// Note: Will return 0 for invalid object types, including areas. +int GffGetObjectType(json jGff); +// Returns TRUE if jGff is a combined area format(CAF) object. +int GffGetIsArea(json jGff); + +// Returns TRUE if a field named sLabel of sType exists in jGff. +// * sLabel: Can be a json pointer(path) without the starting /, see the documentation of JsonPointer() for details. +// * sType: An optional GFF_FIELD_TYPE_*, leave empty to check if sLabel exists regardless of type. +int GffGetFieldExists(json jGff, string sLabel, string sType = ""); + + +// Add a new field, will overwrite any existing fields with the same label even if the type is different. +// Returns a json null value on error with GetJsonError() filled in. +// +// sLabel can be a json pointer(path) without the starting /, see the documentation of JsonPointer() for details. +// For example, to add the tag of an area to an empty combined area format(CAF) object you can do the following: +// json jArea = GffCreateArea(JsonObject(), JsonObject()); +// jArea = GffAddString(jArea, "ARE/value/Tag", "AREA_TAG"); + +json GffAddStruct(json jGff, string sLabel, json jStruct, int nType = -1); +json GffAddList(json jGff, string sLabel, json jList); +json GffAddByte(json jGff, string sLabel, int v); +json GffAddChar(json jGff, string sLabel, int v); +json GffAddWord(json jGff, string sLabel, int v); +json GffAddShort(json jGff, string sLabel, int v); +// Note: Only data of type int32 will fit, because that's all that NWScript supports. +json GffAddDword(json jGff, string sLabel, int v); +json GffAddInt(json jGff, string sLabel, int v); +// Note: Only data of type int32 will fit, because that's all that NWScript supports. +json GffAddDword64(json jGff, string sLabel, int v); +// Note: Only data of type int32 will fit, because that's all that NWScript supports. +json GffAddInt64(json jGff, string sLabel, int v); +json GffAddFloat(json jGff, string sLabel, float v); +// Note: Only data of type float will fit, because that's all that NWScript supports. +json GffAddDouble(json jGff, string sLabel, float v); +json GffAddResRef(json jGff, string sLabel, string v); +json GffAddString(json jGff, string sLabel, string v); +json GffAddLocString(json jGff, string sLabel, string v, int nStrRef = -1); + + +// Replace a field, the type must match and the field must exist. +// Returns a json null value on error with GetJsonError() filled in. +// +// sLabel can be a json pointer(path) without the starting /, see the documentation of JsonPointer() for details. +// For example, to replace the name of an area in a combined area format(CAF) object you can do the following: +// json jArea = ObjectToStruct(GetFirstArea()); +// jArea = GffReplaceLocString(jArea, "ARE/value/Name", "New Area Name"); + +json GffReplaceStruct(json jGff, string sLabel, json jStruct); +json GffReplaceList(json jGff, string sLabel, json jList); +json GffReplaceByte(json jGff, string sLabel, int v); +json GffReplaceChar(json jGff, string sLabel, int v); +json GffReplaceWord(json jGff, string sLabel, int v); +json GffReplaceShort(json jGff, string sLabel, int v); +// Note: Only data of type int32 will fit, because that's all that NWScript supports. +json GffReplaceDword(json jGff, string sLabel, int v); +json GffReplaceInt(json jGff, string sLabel, int v); +// Note: Only data of type int32 will fit, because that's all that NWScript supports. +json GffReplaceDword64(json jGff, string sLabel, int v); +// Note: Only data of type int32 will fit, because that's all that NWScript supports. +json GffReplaceInt64(json jGff, string sLabel, int v); +json GffReplaceFloat(json jGff, string sLabel, float v); +// Note: Only data of type float will fit, because that's all that NWScript supports. +json GffReplaceDouble(json jGff, string sLabel, float v); +json GffReplaceResRef(json jGff, string sLabel, string v); +json GffReplaceString(json jGff, string sLabel, string v); +json GffReplaceLocString(json jGff, string sLabel, string v, int nStrRef = -1); + + +// Remove a field, the type must match and the field must exist. +// Returns a json null value on error with GetJsonError() filled in. +// +// sLabel can be a json pointer(path) without the starting /, see the documentation of JsonPointer() for details. +// For example, to remove all placeables from an area in a combined area format(CAF) object you can do the following: +// json jArea = ObjectToStruct(GetFirstArea()); +// jArea = GffRemoveList(jArea, "GIT/value/Placeable List"); + +json GffRemoveStruct(json jGff, string sLabel); +json GffRemoveList(json jGff, string sLabel); +json GffRemoveByte(json jGff, string sLabel); +json GffRemoveChar(json jGff, string sLabel); +json GffRemoveWord(json jGff, string sLabel); +json GffRemoveShort(json jGff, string sLabel); +json GffRemoveDword(json jGff, string sLabel); +json GffRemoveInt(json jGff, string sLabel); +json GffRemoveDword64(json jGff, string sLabel); +json GffRemoveInt64(json jGff, string sLabel); +json GffRemoveFloat(json jGff, string sLabel); +json GffRemoveDouble(json jGff, string sLabel); +json GffRemoveResRef(json jGff, string sLabel); +json GffRemoveString(json jGff, string sLabel); +json GffRemoveLocString(json jGff, string sLabel); + + +// Get a field's value as json object. +// Returns a json null value on error with GetJsonError() filled in. +// +// Note: Json types do not implicitly convert between types, this means you cannot convert a JsonInt to a string with JsonGetString(), etc. +// You may need to check the type with JsonGetType() and then do the appropriate cast yourself. +// For GffGet*() functions the json type returned is noted in the function description. +// +// Example: +// INCORRECT: string s = JsonGetString(GffGetInt()); +// CORRECT: string s = IntToString(JsonGetInt(GffGetInt())); +// +// sLabel can be a json pointer(path) without the starting /, see the documentation of JsonPointer() for details. +// For example, to get the resref of an area in a combined area format(CAF) object you can do the following: +// json jResRef = GffGetResRef(ObjectToStruct(GetFirstArea()), "ARE/value/ResRef"); +// if (jResRef != JsonNull()) +// { +// string sResRef = JsonGetString(jResRef); +// } +// else +// WriteTimestampedLogEntry("Failed to get area ResRef: " + JsonGetError(jResRef)); + +// Returns the struct as JsonObject() on success. +json GffGetStruct(json jGff, string sLabel); +// Returns a JsonArray() with all the list elements on success. +json GffGetList(json jGff, string sLabel); +// Returns a JsonInt() on success. +json GffGetByte(json jGff, string sLabel); +// Returns a JsonInt() on success. +json GffGetChar(json jGff, string sLabel); +// Returns a JsonInt() on success. +json GffGetWord(json jGff, string sLabel); +// Returns a JsonInt() on success. +json GffGetShort(json jGff, string sLabel); +// Returns a JsonInt() on success. +json GffGetDword(json jGff, string sLabel); +// Returns a JsonInt() on success. +json GffGetInt(json jGff, string sLabel); +// Returns a JsonInt() on success. +json GffGetDword64(json jGff, string sLabel); +// Returns a JsonInt() on success. +json GffGetInt64(json jGff, string sLabel); +// Returns a JsonFloat() on success. +json GffGetFloat(json jGff, string sLabel); +// Returns a JsonFloat() on success. +json GffGetDouble(json jGff, string sLabel); +// Returns a JsonString() on success. +json GffGetResRef(json jGff, string sLabel); +// Returns a JsonString() on success. +json GffGetString(json jGff, string sLabel); +// Returns a JsonObject() on success. +// Key "0" will have a JsonString() with the string, if set. +// Key "id" will have a JsonInt() with the strref, if set. +json GffGetLocString(json jGff, string sLabel); + + +// *** Internal Helper Functions +json AddPatchOperation(json jPatchArray, string sOp, string sPath, json jValue) +{ + json jOperation = JsonObject(); + jOperation = JsonObjectSet(jOperation, "op", JsonString(sOp)); + jOperation = JsonObjectSet(jOperation, "path", JsonString(sPath)); + jOperation = JsonObjectSet(jOperation, "value", jValue); + return JsonArrayInsert(jPatchArray, jOperation); +} + +json GffAddField(json jGff, string sLabel, string sType, json jValue, int nType = -1) +{ + json jField = JsonObject(); + jField = JsonObjectSet(jField, "type", JsonString(sType)); + jField = JsonObjectSet(jField, "value", jValue); + if (sType == GFF_FIELD_TYPE_STRUCT && nType != -1) + jField = JsonObjectSet(jField, "__struct_id", JsonInt(nType)); + + return JsonPatch(jGff, AddPatchOperation(JsonArray(), "add", "/" + sLabel, jField)); +} + +json GffReplaceField(json jGff, string sLabel, string sType, json jValue) +{ + json jPatch = JsonArray(); + jPatch = AddPatchOperation(jPatch, "test", "/" + sLabel + "/type", JsonString(sType)); + jPatch = AddPatchOperation(jPatch, "replace", "/" + sLabel + "/value", jValue); + return JsonPatch(jGff, jPatch); +} + +json GffRemoveField(json jGff, string sLabel, string sType) +{ + json jPatch = JsonArray(); + jPatch = AddPatchOperation(jPatch, "test", "/" + sLabel + "/type", JsonString(sType)); + jPatch = AddPatchOperation(jPatch, "remove", "/" + sLabel, JsonNull()); + return JsonPatch(jGff, jPatch); +} + +json GffGetFieldType(json jGff, string sLabel) +{ + return JsonPointer(jGff, "/" + sLabel + "/type"); +} + +json GffGetFieldValue(json jGff, string sLabel) +{ + return JsonPointer(jGff, "/" + sLabel + "/value"); +} + +json GffGetField(json jGff, string sLabel, string sType) +{ + json jType = GffGetFieldType(jGff, sLabel); + if (jType == JsonNull()) + return jType; + else if (jType != JsonString(sType)) + return JsonNull("field type does not match"); + else + return GffGetFieldValue(jGff, sLabel); +} + +json GffLocString(string v, int nStrRef = -1) +{ + json jLocString = JsonObject(); + if (v != "") + jLocString = JsonObjectSet(jLocString, "0", JsonString(v)); // english/any + if (nStrRef != -1) + jLocString = JsonObjectSet(jLocString, "id", JsonInt(nStrRef)); + + return jLocString; +} +//*** + +json GffCreateObject(int nObjectType) +{ + string ot; + if (nObjectType == OBJECT_TYPE_CREATURE) ot = "UTC "; + else if (nObjectType == OBJECT_TYPE_ITEM) ot = "UTI "; + else if (nObjectType == OBJECT_TYPE_TRIGGER) ot = "UTT "; + else if (nObjectType == OBJECT_TYPE_DOOR) ot = "UTD "; + else if (nObjectType == OBJECT_TYPE_WAYPOINT) ot = "UTW "; + else if (nObjectType == OBJECT_TYPE_PLACEABLE) ot = "UTP "; + else if (nObjectType == OBJECT_TYPE_STORE) ot = "UTM "; + else if (nObjectType == OBJECT_TYPE_ENCOUNTER) ot = "UTE "; + + if (ot == "") return JsonNull("invalid object type"); + + json ret = JsonObject(); + ret = JsonObjectSet(ret, "__data_type", JsonString(ot)); + return ret; +} + +json GffCreateArea(json jARE, json jGIT) +{ + json jCAF = JsonObject(); + jCAF = JsonObjectSet(jCAF, "__data_type", JsonString("CAF ")); + jCAF = GffAddStruct(jCAF, "ARE", jARE, 0); + jCAF = GffAddStruct(jCAF, "GIT", jGIT, 1); + return jCAF; +} + + +int GffGetObjectType(json jGff) +{ + json jDataType = JsonObjectGet(jGff, "__data_type"); + if (jDataType == JsonNull()) + return 0; + else + { + string sObjectType = JsonGetString(jDataType); + + if (sObjectType == "UTC ") return OBJECT_TYPE_CREATURE; + else if (sObjectType == "UTI ") return OBJECT_TYPE_ITEM; + else if (sObjectType == "UTT ") return OBJECT_TYPE_TRIGGER; + else if (sObjectType == "UTD ") return OBJECT_TYPE_DOOR; + else if (sObjectType == "UTW ") return OBJECT_TYPE_WAYPOINT; + else if (sObjectType == "UTP ") return OBJECT_TYPE_PLACEABLE; + else if (sObjectType == "UTM ") return OBJECT_TYPE_STORE; + else if (sObjectType == "UTE ") return OBJECT_TYPE_ENCOUNTER; + } + + return 0; +} + +int GffGetIsArea(json jGff) +{ + return JsonObjectGet(jGff, "__data_type") == JsonString("CAF "); +} + +int GffGetFieldExists(json jGff, string sLabel, string sType = "") +{ + json jFieldType = GffGetFieldType(jGff, sLabel); + return sType == "" ? jFieldType != JsonNull() : jFieldType == JsonString(sType); +} + + +json GffAddStruct(json jGff, string sLabel, json jStruct, int nType = -1) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_STRUCT, jStruct, nType); +} + +json GffAddList(json jGff, string sLabel, json jList) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_LIST, jList); +} + +json GffAddByte(json jGff, string sLabel, int v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_BYTE, JsonInt(v)); +} + +json GffAddChar(json jGff, string sLabel, int v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_CHAR, JsonInt(v)); +} + +json GffAddWord(json jGff, string sLabel, int v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_WORD, JsonInt(v)); +} + +json GffAddShort(json jGff, string sLabel, int v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_SHORT, JsonInt(v)); +} + +json GffAddDword(json jGff, string sLabel, int v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_DWORD, JsonInt(v)); +} + +json GffAddInt(json jGff, string sLabel, int v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_INT, JsonInt(v)); +} + +json GffAddDword64(json jGff, string sLabel, int v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_DWORD64, JsonInt(v)); +} + +json GffAddInt64(json jGff, string sLabel, int v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_INT64, JsonInt(v)); +} + +json GffAddFloat(json jGff, string sLabel, float v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_FLOAT, JsonFloat(v)); +} + +json GffAddDouble(json jGff, string sLabel, float v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_DOUBLE, JsonFloat(v)); +} + +json GffAddResRef(json jGff, string sLabel, string v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_RESREF, JsonString(v)); +} + +json GffAddString(json jGff, string sLabel, string v) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_STRING, JsonString(v)); +} + +json GffAddLocString(json jGff, string sLabel, string v, int nStrRef = -1) +{ + return GffAddField(jGff, sLabel, GFF_FIELD_TYPE_LOC_STRING, GffLocString(v, nStrRef)); +} + + +json GffReplaceStruct(json jGff, string sLabel, json jStruct) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_STRUCT, jStruct); +} + +json GffReplaceList(json jGff, string sLabel, json jList) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_LIST, jList); +} + +json GffReplaceByte(json jGff, string sLabel, int v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_BYTE, JsonInt(v)); +} + +json GffReplaceChar(json jGff, string sLabel, int v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_CHAR, JsonInt(v)); +} + +json GffReplaceWord(json jGff, string sLabel, int v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_WORD, JsonInt(v)); +} + +json GffReplaceShort(json jGff, string sLabel, int v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_SHORT, JsonInt(v)); +} + +json GffReplaceDword(json jGff, string sLabel, int v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_DWORD, JsonInt(v)); +} + +json GffReplaceInt(json jGff, string sLabel, int v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_INT, JsonInt(v)); +} + +json GffReplaceDword64(json jGff, string sLabel, int v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_DWORD64, JsonInt(v)); +} + +json GffReplaceInt64(json jGff, string sLabel, int v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_INT64, JsonInt(v)); +} + +json GffReplaceFloat(json jGff, string sLabel, float v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_FLOAT, JsonFloat(v)); +} + +json GffReplaceDouble(json jGff, string sLabel, float v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_DOUBLE, JsonFloat(v)); +} + +json GffReplaceResRef(json jGff, string sLabel, string v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_RESREF, JsonString(v)); +} + +json GffReplaceString(json jGff, string sLabel, string v) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_STRING, JsonString(v)); +} + +json GffReplaceLocString(json jGff, string sLabel, string v, int nStrRef = -1) +{ + return GffReplaceField(jGff, sLabel, GFF_FIELD_TYPE_LOC_STRING, GffLocString(v, nStrRef)); +} + + +json GffRemoveStruct(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_STRUCT); +} + +json GffRemoveList(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_LIST); +} + +json GffRemoveByte(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_BYTE); +} + +json GffRemoveChar(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_CHAR); +} + +json GffRemoveWord(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_WORD); +} + +json GffRemoveShort(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_SHORT); +} + +json GffRemoveDword(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_DWORD); +} + +json GffRemoveInt(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_INT); +} + +json GffRemoveDword64(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_DWORD64); +} + +json GffRemoveInt64(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_INT64); +} + +json GffRemoveFloat(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_FLOAT); +} + +json GffRemoveDouble(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_DOUBLE); +} + +json GffRemoveResRef(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_RESREF); +} + +json GffRemoveString(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_STRING); +} + +json GffRemoveLocString(json jGff, string sLabel) +{ + return GffRemoveField(jGff, sLabel, GFF_FIELD_TYPE_LOC_STRING); +} + + +json GffGetStruct(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_STRUCT); +} + +json GffGetList(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_LIST); +} + +json GffGetByte(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_BYTE); +} + +json GffGetChar(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_CHAR); +} + +json GffGetWord(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_WORD); +} + +json GffGetShort(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_SHORT); +} + +json GffGetDword(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_DWORD); +} + +json GffGetInt(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_INT); +} + +json GffGetDword64(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_DWORD64); +} + +json GffGetInt64(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_INT64); +} + +json GffGetFloat(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_FLOAT); +} + +json GffGetDouble(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_DOUBLE); +} + +json GffGetResRef(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_RESREF); +} + +json GffGetString(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_STRING); +} + +json GffGetLocString(json jGff, string sLabel) +{ + return GffGetField(jGff, sLabel, GFF_FIELD_TYPE_LOC_STRING); +} diff --git a/src/include/nw_inc_nui.nss b/src/include/nw_inc_nui.nss index 96fc3da..a37f13f 100644 --- a/src/include/nw_inc_nui.nss +++ b/src/include/nw_inc_nui.nss @@ -40,6 +40,24 @@ const float NUI_STYLE_TERTIARY_HEIGHT = 30.0; const float NUI_STYLE_ROW_HEIGHT = 25.0; +// ----------------------- +// Bind params + +// These currently only affect presentation and serve as a +// optimisation to let the client do the heavy lifting on this. +// In particular, this enables you to bind an array of values and +// transform them all at once on the client, instead of having to +// have the server transform them before sending. + +// NB: These must be OR-ed together into a bitmask. + +const int NUI_NUMBER_FLAG_HEX = 0x001; + +// NB: These must be OR-ed together into a bitmask. + +const int NUI_TEXT_FLAG_LOWERCASE = 0x001; +const int NUI_TEXT_FLAG_UPPERCASE = 0x002; + // ----------------------- // Window @@ -47,24 +65,41 @@ const float NUI_STYLE_ROW_HEIGHT = 25.0; // * Set the window title to JsonBool(FALSE), Collapse to JsonBool(FALSE) and bClosable to FALSE // to hide the title bar. // Note: You MUST provide a way to close the window some other way, or the user will be stuck with it. -json // Window -NuiWindow( - json jRoot, // Layout-ish (NuiRow, NuiCol, NuiGroup) - json jTitle, // Bind:String - json jGeometry, // Bind:Rect Set x and/or y to -1.0 to center the window on that axis - // Set x and/or y to -2.0 to position the window's top left at the mouse cursor's position of that axis - // Set x and/or y to -3.0 to center the window on the mouse cursor's position of that axis - json jResizable, // Bind:Bool Set to JsonBool(TRUE) or JsonNull() to let user resize without binding. - json jCollapsed, // Bind:Bool Set to a static value JsonBool(FALSE) to disable collapsing. - // Set to JsonNull() to let user collapse without binding. - // For better UX, leave collapsing on. - json jClosable, // Bind:Bool You must provide a way to close the window if you set this to FALSE. - // For better UX, handle the window "closed" event. - json jTransparent, // Bind:Bool Do not render background - json jBorder, // Bind:Bool Do not render border - json jAcceptsInput = // Bind:Bool Set JsonBool(FALSE) to disable all input. - JSON_TRUE // All hover, clicks and keypresses will fall through. -); +// * Set a minimum size constraint equal to the maximmum size constraint in the same dimension to prevent +// a window from being resized in that dimension. +// - jRoot: Layout-ish (NuiRow, NuiCol, NuiGroup) +// - jTitle: Bind:String +// - jGeometry: Bind:Rect +// Set x and/or y to -1.0 to center the window on that axis. +// Set x and/or y to -2.0 to position the window's top left at the mouse cursor's position of that axis. +// Set x and/or y to -3.0 to center the window on the mouse cursor's position of that axis. +// - jResizable: Bind:Bool +// Set to JsonBool(TRUE) or JsonNull() to let user resize without binding. +// - jCollapsed: Bind:Bool +// Set to a static value JsonBool(FALSE) to disable collapsing. +// Set to JsonNull() to let user collapse without binding. +// For better UX, leave collapsing on. +// - jCloseable: Bind:Bool +// You provide a way to close the window if you set this to FALSE. +// For better UX, handle the window "closed" event. +// - jTransparent: Bind:Bool +// Do not render background +// - jBorder: Bind:Bool +// Do not render border +// - jAcceptsInput: Bind:Bool +// Set JsonBool(FALSE) to disable all input. +// All hover, clicks and keypresses will fall through. +// - jSizeConstraint: Bind:Rect +// Constrains minimum and maximum size of window. +// Set x to minimum width, y to minimum height, w to maximum width, h to maximum height. +// Set any individual constraint to 0.0 to ignore that constraint. +// - jEdgeConstraint: Bind:Rect +// Prevents a window from being rendered within the specified margins. +// Set x to left margin, y to top margin, w to right margin, h to bottom margin. +// Set any individual constraint to 0.0 to ignore that constraint +// - jFont: Bind:String +// Override font used on window, including decorations. See NuiStyleFont() for details. +json NuiWindow(json jRoot, json jTitle, json jGeometry, json jResizable,json jCollapsed,json jClosable, json jTransparent, json jBorder, json jAcceptsInput = JSON_TRUE, json jSizeConstraint = JSON_NULL, json jEdgeConstraint = JSON_NULL, json jFont = JSON_STRING); // ----------------------- // Values @@ -74,144 +109,111 @@ NuiWindow( // NuiSetBind(.., "mybindlabel", JsonString("hi")); // To create static values, just use the json types directly: // JsonString("hi"); -json // Bind -NuiBind( - string sId -); +// +// You can parametrise this particular bind with the given flags. +// These flags only apply to that particular usage of this bind value. +// +// - sId: string +// - nNumberFlags: bitmask of NUI_NUMBER_FLAG_* +// - nNumberPrecision: Precision to print number with (int or float) +// - nTextFlags: bitmask of NUI_TEXT_FLAG_* +json NuiBind(string sId, int nNumberFlags = 0, int nNumberPrecision = 0, int nTextFlags = 0); // Tag the given element with a id. // Only tagged elements will send events to the server. -json // Element -NuiId( - json jElem, // Element - string sId // String -); +json NuiId(json jElem, string sId); // A shim/helper that can be used to render or bind a strref where otherwise // a string value would go. -json -NuiStrRef( - int nStrRef // STRREF -); +json NuiStrRef(int nStrRef); // ----------------------- // Layout // A column will auto-space all elements inside of it and advise the parent // about it's desired size. -json // Layout -NuiCol( - json jList // Layout[] or Element[] -); +// - jList: Layout[] or Element[] +json NuiCol(json jList); // A row will auto-space all elements inside of it and advise the parent // about it's desired size. -json // Layout -NuiRow( - json jList // Layout[] or Element[] -); +// - jList: Layout[] or Element[] +json NuiRow(json jList); // A group, usually with a border and some padding, holding a single element. Can scroll. // Will not advise parent of size, so you need to let it fill a span (col/row) as if it was // a element. -json // Layout -NuiGroup( - json jChild, // Layout or Element - int bBorder = TRUE, - int nScroll = NUI_SCROLLBARS_AUTO -); +// - jChild: Layout or Element +json NuiGroup(json jChild, int bBorder = TRUE, int nScroll = NUI_SCROLLBARS_AUTO); // Modifiers/Attributes: These are all static and cannot be bound, since the UI system // cannot easily reflow once the layout is set up. You need to swap the layout if you // want to change element geometry. -json // Element -NuiWidth( - json jElem, // Element - float fWidth // Float: Element width in pixels (strength=required). -); +// - jElem: Element +// - fWidth: Float: Element width in pixels (strength=required). +json NuiWidth(json jElem, float fWidth); -json // Element -NuiHeight( - json jElem, // Element - float fHeight // Float: Height in pixels (strength=required). -); +// - jElem: Element +// - fHeight: Float: Height in pixels (strength=required). +json NuiHeight(json jElem, float fHeight); -json // Element -NuiAspect( - json jElem, // Element - float fAspect // Float: Ratio of x/y. -); +// - jElem: Element +// - fAspect: Float: Ratio of x/y +json NuiAspect(json jElem, float fAspect); // Set a margin on the widget. The margin is the spacing outside of the widget. -json // Element -NuiMargin( - json jElem, // Element - float fMargin // Float -); +json NuiMargin(json jElem, float fMargin); // Set padding on the widget. The margin is the spacing inside of the widget. -json // Element -NuiPadding( - json jElem, // Element - float fPadding // Float -); +json NuiPadding(json jElem, float fPadding); // Disabled elements are non-interactive and greyed out. -json // Element -NuiEnabled( - json jElem, // Element - json jEnabler // Bind:Bool -); +// - jElem: Element +// - jEnabled: Bind:Bool +json NuiEnabled(json jElem, json jEnabler); // Invisible elements do not render at all, but still take up layout space. -json // Element -NuiVisible( - json jElem, // Element - json jVisible // Bind:Bool -); +// - jElem: Element +// - jVisible: Bind:Bool +json NuiVisible(json jElem, json jVisible); // Tooltips show on mouse hover. -json // Element -NuiTooltip( - json jElem, // Element - json jTooltip // Bind:String -); +// - jElem: Element +// - jTooltip: Bind:String +json NuiTooltip(json jElem, json jTooltip); // Tooltips for disabled elements show on mouse hover. -json // Element -NuiDisabledTooltip( - json jElem, // Element - json jTooltip // Bind:String -); +// - jElem: Element +// - jTooltip: Bind:String +json NuiDisabledTooltip(json jElem, json jTooltip); // Encouraged elements have a breathing animated glow inside of it. -json // Element -NuiEncouraged( - json jElem, // Element - json jEncouraged // Bind:Bool -); +// - jElem: Element +// - jEncouraged: Bind:Bool +json NuiEncouraged(json jElem, json jEncouraged); // ----------------------- // Props & Style -json // Vec2 -NuiVec(float x, float y); +json NuiVec(float x, float y); -json // Rect -NuiRect(float x, float y, float w, float h); +json NuiRect(float x, float y, float w, float h); -json // Color -NuiColor(int r, int g, int b, int a = 255); +json NuiColor(int r, int g, int b, int a = 255); -// Style the foreground color of the widget. This is dependent on the widget +// Style the foreground color of a widget or window title. This is dependent on the widget // in question and only supports solid/full colors right now (no texture skinning). // For example, labels would style their text color; progress bars would style the bar. -json // Element -NuiStyleForegroundColor( - json jElem, // Element - json jColor // Bind:Color -); +// - jElem: Element +// - jColor: Bind:Color +json NuiStyleForegroundColor(json jElem, json jColor); + +// Override the font used for this element. The font and it's properties needs to be listed in +// nui_skin.tml, as all fonts are pre-baked into a texture atlas at content load. +// - jElem: Element +// - jColor: Bind:String ([[fonts]].name in nui_skin.tml) +json NuiStyleFont(json jElem, json jFont); // ----------------------- // Widgets @@ -219,119 +221,87 @@ NuiStyleForegroundColor( // A special widget that just takes up layout space. // If you add multiple spacers to a span, they will try to size equally. // e.g.: [ ] will try to center the button. -json // Element -NuiSpacer(); +json NuiSpacer(); // Create a label field. Labels are single-line stylable non-editable text fields. -json // Element -NuiLabel( - json jValue, // Bind:String - json jHAlign, // Bind:Int:NUI_HALIGN_* - json jVAlign // Bind:Int:NUI_VALIGN_* -); +// - jValue: Bind:String +// - jHAlign: Bind:Int:NUI_HALIGN_* +// - jVAlign: Bind:Int:NUI_VALIGN_* +json NuiLabel(json jValue, json jHAlign, json jVAlign); // Create a non-editable text field. Note: This text field internally implies a NuiGroup wrapped // around it, which is providing the optional border and scrollbars. -json // Element -NuiText( - json jValue, // Bind:String - int bBorder = TRUE, // Bool - int nScroll = NUI_SCROLLBARS_AUTO // Int:NUI_SCROLLBARS_* -); +// - jValue: Bind:String +// - bBorder: Bool +// - nScroll: Int:NUI_SCROLLBARS_* +json NuiText(json jValue, int bBorder = TRUE, int nScroll = NUI_SCROLLBARS_AUTO); // A clickable button with text as the label. // Sends "click" events on click. -json // Element -NuiButton( - json jLabel // Bind:String -); +// - jLabel: Bind:String +json NuiButton(json jLabel); // A clickable button with an image as the label. // Sends "click" events on click. -json // Element -NuiButtonImage( - json jResRef // Bind:ResRef -); +// - jResRef: Bind:String +json NuiButtonImage(json jResRef); // A clickable button with text as the label. // Same as the normal button, but this one is a toggle. // Sends "click" events on click. -json // Element -NuiButtonSelect( - json jLabel, // Bind:String - json jValue // Bind:Bool -); +// - jLabel: Bind:String +// - jValue: Bind:Bool +json NuiButtonSelect(json jLabel, json jValue); // A checkbox with a label to the right of it. -json // Element -NuiCheck( - json jLabel, // Bind:String - json jBool // Bind:Bool -); +// - jLabel: Bind:String +// - jBool: Bind:Bool +json NuiCheck(json jLabel, json jBool); // A image, with no border or padding. -json // Element -NuiImage( - json jResRef, // Bind:ResRef - json jAspect, // Bind:Int:NUI_ASPECT_* - json jHAlign, // Bind:Int:NUI_HALIGN_* - json jVAlign // Bind:Int:NUI_VALIGN_* -); +// - jResRef: Bind:String +// - jAspect: Bind:Int:NUI_ASPECT_* +// - jHAlign: Bind:Int:NUI_HALIGN_* +// - jVAlign: Bind:Int:NUI_VALIGN_* +json NuiImage(json jResRef, json jAspect, json jHAlign, json jVAlign); -// Optionally render only subregion of jImage. +// Optionally render only subregion of jImage. This property can be set on +// NuiImage and NuiButtonImage widgets. // jRegion is a NuiRect (x, y, w, h) to indicate the render region inside the image. -json // NuiImage -NuiImageRegion( - json jImage, // NuiImage - json jRegion // Bind:NuiRect -); +json NuiImageRegion(json jImage, json jRegion); // A combobox/dropdown. -json // Element -NuiCombo( - json jElements, // Bind:ComboEntry[] - json jSelected // Bind:Int (index into jElements) -); +// - jElements: Bind:ComboEntry[] +// - jSelected: Bind:Int (index into jElements) +json NuiCombo(json jElements, json jSelected); -json // ComboEntry -NuiComboEntry( - string sLabel, - int nValue -); +json NuiComboEntry(string sLabel, int nValue); // A floating-point slider. A good step size for normal-sized sliders is 0.01. -json // Element -NuiSliderFloat( - json jValue, // Bind:Float - json jMin, // Bind:Float - json jMax, // Bind:Float - json jStepSize // Bind:Float -); +// - jValue: Bind:Float +// - jMin: Bind:Float +// - jMax: Bind:Float +// - jStepSize: Bind:Float +json NuiSliderFloat(json jValue, json jMin, json jMax, json jStepSize); // A integer/discrete slider. -json // Element -NuiSlider( - json jValue, // Bind:Int - json jMin, // Bind:Int - json jMax, // Bind:Int - json jStepSize // Bind:Int -); +// - jValue: Bind:Int +// - jMin: Bind:Int +// - jMax: Bind:Int +// - jStepSize: Bind:Int +json NuiSlider(json jValue, json jMin, json jMax, json jStepSize); // A progress bar. Progress is always from 0.0 to 1.0. -json // Element -NuiProgress( - json jValue // Bind:Float (0.0->1.0) -); +// - jValue: Bind:Float (0.0->1.0 +json NuiProgress(json jValue); // A editable text field. -json // Element -NuiTextEdit( - json jPlaceholder, // Bind:String - json jValue, // Bind:String - int nMaxLength, // UInt >= 1, <= 65535 - int bMultiline, // Bool - int bWordWrap = TRUE // Bool -); +// - jPlaceholder: Bind:String +// - jValue: Bind:String +// - nMaxLength: UInt >= 1, <= 65535 +// - bMultiLine: Bool +// - bWordWrap: Bool +json NuiTextEdit(json jPlaceholder, json jValue, int nMaxLength, int bMultiline, int bWordWrap = TRUE); // Creates a list view of elements. // jTemplate needs to be an array of NuiListTemplateCell instances. @@ -339,67 +309,52 @@ NuiTextEdit( // e.g. when rendering a NuiLabel(), the bound label String should be an array of strings. // You can pass in one of the template jRowCount into jSize as a convenience. The array // size will be uses as the Int bind. -// jRowHeight defines the height of the rendered rows. -json // Element -NuiList( - json jTemplate, // NuiListTemplateCell[] (max: 16) - json jRowCount, // Bind:Int - float fRowHeight = NUI_STYLE_ROW_HEIGHT, - int bBorder = TRUE, - int nScroll = NUI_SCROLLBARS_Y // Note: Cannot be AUTO. -); +// fRowHeight defines the height of the rendered rows. +// - jTemplate: NuiListTemplateCell[] (max: 16) +// - jRowCount: Bind:Int +// - bBorder: Bool +// - nScroll: Int:NUI_SCROLLBARS_*, Note: Cannot be AUTO +json NuiList(json jTemplate, json jRowCount, float fRowHeight = NUI_STYLE_ROW_HEIGHT, int bBorder = TRUE, int nScroll = NUI_SCROLLBARS_Y); -json // NuiListTemplateCell -NuiListTemplateCell( - json jElem, // Element - float fWidth, // Float:0 = auto, >1 = pixel width - int bVariable // Bool:Cell can grow if space is available; otherwise static -); +// - jElem: Element +// - fWidth: Float:0 = auto, >1 = pixel width +// - bVariable: Bool:Cell can grow if space is available; otherwise static +json NuiListTemplateCell(json jElem, float fWidth, int bVariable); // A simple color picker, with no border or spacing. -json // Element -NuiColorPicker( - json jColor // Bind:Color -); +// - jColor: Bind:Color +json NuiColorPicker(json jColor); // A list of options (radio buttons). Only one can be selected // at a time. jValue is updated every time a different element is // selected. The special value -1 means "nothing". -json // Element -NuiOptions( - int nDirection, // NUI_DIRECTION_* - json jElements, // JsonArray of string labels - json jValue // Bind:Int -); +// - nDirection: NUI_DIRECTION_* +// - jElements: JsonArray of string labels +// - jValue: Bind:UInt +json NuiOptions(int nDirection, json jElements, json jValue); -// A group of buttons. Only one can be selected at a time. jValue -// is updated every time a different button is selected. The special +// A group of buttons. Only one can be selected at a time. jValue +// is updated every time a different button is selected. The special // value -1 means "nothing". -json // Element -NuiToggles( - int nDirection, // NUI_DIRECTION_* - json jElements, // JsonArray of string labels - json jValue // Bind:Int -); +// - nDirection: NUI_DIRECTION_* +// - jElements: JsonArray of string labels +// - jValue: Bind:Int +json NuiToggles(int nDirection, json jElements, json jValue); const int NUI_CHART_TYPE_LINES = 0; const int NUI_CHART_TYPE_COLUMN = 1; -json // NuiChartSlot -NuiChartSlot( - int nType, // Int:NUI_CHART_TYPE_* - json jLegend, // Bind:String - json jColor, // Bind:NuiColor - json jData // Bind:Float[] -); +// - nType: Int:NUI_CHART_TYPE_* +// - jLegend: Bind:String +// - jColor: Bind:NuiColor +// - jData: Bind:Float[] +json NuiChartSlot(int nType, json jLegend, json jColor, json jData); // Renders a chart. // Currently, min and max values are determined automatically and // cannot be influenced. -json // Element -NuiChart( - json jSlots // NuiChartSlot[] -); +// - jSlots: NuiChartSlot[] +json NuiChart( json jSlots); // ----------------------- // Draw Lists @@ -417,6 +372,7 @@ const int NUI_DRAW_LIST_ITEM_TYPE_ARC = 3; const int NUI_DRAW_LIST_ITEM_TYPE_TEXT = 4; const int NUI_DRAW_LIST_ITEM_TYPE_IMAGE = 5; const int NUI_DRAW_LIST_ITEM_TYPE_LINE = 6; +const int NUI_DRAW_LIST_ITEM_TYPE_RECT = 7; // You can order draw list items to be painted either before, or after the // builtin render of the widget in question. This enables you to paint "behind" @@ -438,100 +394,100 @@ const int NUI_DRAW_LIST_ITEM_RENDER_MOUSE_RIGHT = 4; // Only render while MMB is held down. const int NUI_DRAW_LIST_ITEM_RENDER_MOUSE_MIDDLE = 5; -json // DrawListItem -NuiDrawListPolyLine( - json jEnabled, // Bind:Bool - json jColor, // Bind:Color - json jFill, // Bind:Bool - json jLineThickness, // Bind:Float - json jPoints, // Bind:Float[] Always provide points in pairs - int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, // Int:NUI_DRAW_LIST_ITEM_ORDER_* - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS // Int:NUI_DRAW_LIST_ITEM_RENDER_* -); +// - jEnabled: Bind:Bool +// - jColor: Bind:Color +// - jFill: Bind:Bool +// - jLineThickness: Bind:Float +// - jPoints: Bind:Float[] Always provide points in pairs +// - nOrder: Int:NUI_DRAW_LIST_ITEM_ORDER_* +// - nRender: Int:NUI_DRAW_LIST_ITEM_RENDER_* +// - nBindArrays: Values in binds are considered arrays-of-values +json NuiDrawListPolyLine(json jEnabled, json jColor, json jFill, json jLineThickness, json jPoints, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, int nBindArrays = FALSE); -json // DrawListItem -NuiDrawListCurve( - json jEnabled, // Bind:Bool - json jColor, // Bind:Color - json jLineThickness, // Bind:Float - json jA, // Bind:Vec2 - json jB, // Bind:Vec2 - json jCtrl0, // Bind:Vec2 - json jCtrl1, // Bind:Vec2 - int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, // Int:NUI_DRAW_LIST_ITEM_ORDER_* - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS // Int:NUI_DRAW_LIST_ITEM_RENDER_* -); +// - jEnabled: Bind:Bool +// - jColor: Bind:Color +// - jLineThickness: Bind:Float +// - jA: Bind:Vec2 +// - jB: Bind:Vec2 +// - jCtrl0: Bind:Vec2 +// - jCtrl1: Bind:Vec2 +// - nOrder: Int:NUI_DRAW_LIST_ITEM_ORDER_* +// - nRender: Int:NUI_DRAW_LIST_ITEM_RENDER_* +// - nBindArrays: Values in binds are considered arrays-of-values +json NuiDrawListCurve(json jEnabled, json jColor, json jLineThickness, json jA, json jB, json jCtrl0, json jCtrl1, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, int nBindArrays = FALSE); -json // DrawListItem -NuiDrawListCircle( - json jEnabled, // Bind:Bool - json jColor, // Bind:Color - json jFill, // Bind:Bool - json jLineThickness, // Bind:Float - json jRect, // Bind:Rect - int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, // Int:NUI_DRAW_LIST_ITEM_ORDER_* - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS // Int:NUI_DRAW_LIST_ITEM_RENDER_* -); +// - jEnabled: Bind:Bool +// - jColor: Bind:Color +// - jFill: Bind:Bool +// - jLineThickness: Bind:Float +// - jRect: Bind:Rect +// - nOrder: Int:NUI_DRAW_LIST_ITEM_ORDER_* +// - nRender: Int:NUI_DRAW_LIST_ITEM_RENDER_* +// - nBindArrays: Values in binds are considered arrays-of-values +json NuiDrawListCircle(json jEnabled, json jColor, json jFill, json jLineThickness, json jRect, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, int nBindArrays = FALSE); -json // DrawListItem -NuiDrawListArc( - json jEnabled, // Bind:Bool - json jColor, // Bind:Color - json jFill, // Bind:Bool - json jLineThickness, // Bind:Float - json jCenter, // Bind:Vec2 - json jRadius, // Bind:Float - json jAMin, // Bind:Float - json jAMax, // Bind:Float - int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, // Int:NUI_DRAW_LIST_ITEM_ORDER_* - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS // Int:NUI_DRAW_LIST_ITEM_RENDER_* -); +// - jEnabled: Bind:Bool +// - jColor: Bind:Color +// - jFill: Bind:Bool +// - jLineThickness: Bind:Float +// - jCenter: Bind:Rect +// - jRadius: Bind:Float +// - jAMin: Bind:Float +// - jAMax: Bind:Float +// - nOrder: Int:NUI_DRAW_LIST_ITEM_ORDER_* +// - nRender: Int:NUI_DRAW_LIST_ITEM_RENDER_* +// - nBindArrays: Values in binds are considered arrays-of-values +json NuiDrawListArc(json jEnabled, json jColor, json jFill, json jLineThickness, json jCenter, json jRadius, json jAMin, json jAMax, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, int nBindArrays = FALSE); -json // DrawListItem -NuiDrawListText( - json jEnabled, // Bind:Bool - json jColor, // Bind:Color - json jRect, // Bind:Rect - json jText, // Bind:String - int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, // Int:NUI_DRAW_LIST_ITEM_ORDER_* - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS // Int:NUI_DRAW_LIST_ITEM_RENDER_* -); +// - jEnabled: Bind:Bool +// - jColor: Bind:Color +// - jRect: Bind:Rect +// - jText: Bind:String +// - nOrder: Int:NUI_DRAW_LIST_ITEM_ORDER_* +// - nRender: Int:NUI_DRAW_LIST_ITEM_RENDER_* +// - nBindArrays: Values in binds are considered arrays-of-values +// - jFont: Bind:String +json NuiDrawListText(json jEnabled, json jColor, json jRect, json jText, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, int nBindArrays = FALSE, json jFont = JSON_STRING); -json // DrawListItem -NuiDrawListImage( - json jEnabled, // Bind:Bool - json jResRef, // Bind:ResRef - json jPos, // Bind:Rect - json jAspect, // Bind:Int:NUI_ASPECT_* - json jHAlign, // Bind:Int:NUI_HALIGN_* - json jVAlign, // Bind:Int:NUI_VALIGN_* - int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, // Int:NUI_DRAW_LIST_ITEM_ORDER_* - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS // Int:NUI_DRAW_LIST_ITEM_RENDER_* -); +// - jEnabled: Bind:Bool +// - jResRef: Bind:String +// - jPos: Bind:Rect +// - jAspect: Bind:Int:NUI_ASPECT_* +// - jHAlign: Bind:Int:NUI_HALIGN_* +// - jVAlign: Bind:Int:NUI_VALIGN_* +// - nOrder: Int:NUI_DRAW_LIST_ITEM_ORDER_* +// - nRender: Int:NUI_DRAW_LIST_ITEM_RENDER_* +// - nBindArrays: Values in binds are considered arrays-of-values +json NuiDrawListImage(json jEnabled, json jResRef, json jPos, json jAspect, json jHAlign, json jVAlign, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, int nBindArrays = FALSE); -json // DrawListItemImage -NuiDrawListImageRegion( - json jDrawListImage, // DrawListItemImage - json jRegion // Bind:NuiRect -); +// - jDrawListImage: DrawListItemImage +// - jRegion: Bind:NuiRect +json NuiDrawListImageRegion(json jDrawListImage, json jRegion); -json // DrawListItem -NuiDrawListLine( - json jEnabled, // Bind:Bool - json jColor, // Bind:Color - json jLineThickness, // Bind:Float - json jA, // Bind:Vec2 - json jB, // Bind:Vec2 - int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, // Int:NUI_DRAW_LIST_ITEM_ORDER_* - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS // Int:NUI_DRAW_LIST_ITEM_RENDER_* -); +// - jEnabled: Bind:Bool +// - jColor: Bind:Color +// - jLineThickness: Bind:Float +// - jA: Bind:Vec2 +// - jB: Bind:Vec2 +// - nOrder: Int:NUI_DRAW_LIST_ITEM_ORDER_* +// - nRender: Int:NUI_DRAW_LIST_ITEM_RENDER_* +// - nBindArrays: Values in binds are considered arrays-of-values +json NuiDrawListLine(json jEnabled, json jColor, json jLineThickness, json jA, json jB, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, int nBindArrays = FALSE); -json // Element -NuiDrawList( - json jElem, // Element - json jScissor, // Bind:Bool Constrain painted elements to widget bounds. - json jList // DrawListItem[] -); +// - jEnabled: Bind:Bool +// - jColor: Bind:Color +// - jFill: Bind:Bool +// - jLineThickness: Bind:Float +// - jRext: Bind:Rect +// - nOrder: Int:NUI_DRAW_LIST_ITEM_ORDER_* +// - nRender: Int:NUI_DRAW_LIST_ITEM_RENDER_* +// - nBindArrays: Values in binds are considered arrays-of-values +json NuiDrawListRect(json jEnabled, json jColor, json jFill, json jLineThickness, json jRect, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, int nBindArrays = FALSE); + +// - jElem: Element +// - jScissor: Bind:Bool, Constrain painted elements to widget bounds. +// - jList: DrawListItem[] +json NuiDrawList(json jElem, json jScissor, json jList); // ----------------------- // Implementation @@ -546,21 +502,27 @@ NuiWindow( json jClosable, json jTransparent, json jBorder, - json jAcceptsInput + json jAcceptsInput = JSON_TRUE, + json jWindowConstraint = JSON_NULL, + json jEdgeConstraint = JSON_NULL, + json jFont = JSON_STRING ) { json ret = JsonObject(); // Currently hardcoded and here to catch backwards-incompatible data in the future. - ret = JsonObjectSet(ret, "version", JsonInt(1)); - ret = JsonObjectSet(ret, "title", jTitle); - ret = JsonObjectSet(ret, "root", jRoot); - ret = JsonObjectSet(ret, "geometry", jGeometry); - ret = JsonObjectSet(ret, "resizable", jResizable); - ret = JsonObjectSet(ret, "collapsed", jCollapsed); - ret = JsonObjectSet(ret, "closable", jClosable); - ret = JsonObjectSet(ret, "transparent", jTransparent); - ret = JsonObjectSet(ret, "border", jBorder); - ret = JsonObjectSet(ret, "accepts_input", jAcceptsInput); + JsonObjectSetInplace(ret, "version", JsonInt(1)); + JsonObjectSetInplace(ret, "title", jTitle); + JsonObjectSetInplace(ret, "root", jRoot); + JsonObjectSetInplace(ret, "geometry", jGeometry); + JsonObjectSetInplace(ret, "resizable", jResizable); + JsonObjectSetInplace(ret, "collapsed", jCollapsed); + JsonObjectSetInplace(ret, "closable", jClosable); + JsonObjectSetInplace(ret, "transparent", jTransparent); + JsonObjectSetInplace(ret, "border", jBorder); + JsonObjectSetInplace(ret, "accepts_input", jAcceptsInput); + JsonObjectSetInplace(ret, "size_constraint", jWindowConstraint); + JsonObjectSetInplace(ret, "edge_constraint", jEdgeConstraint); + JsonObjectSetInplace(ret, "font", jFont); return ret; } @@ -572,18 +534,26 @@ NuiElement( ) { json ret = JsonObject(); - ret = JsonObjectSet(ret, "type", JsonString(sType)); - ret = JsonObjectSet(ret, "label", jLabel); - ret = JsonObjectSet(ret, "value", jValue); + JsonObjectSetInplace(ret, "type", JsonString(sType)); + JsonObjectSetInplace(ret, "label", jLabel); + JsonObjectSetInplace(ret, "value", jValue); return ret; } json NuiBind( - string sId + string sId, + int nNumberFlags = 0, + int nNumberPrecision = 0, + int nTextFlags = 0 ) { - return JsonObjectSet(JsonObject(), "bind", JsonString(sId)); + json ret = JsonObject(); + JsonObjectSetInplace(ret, "bind", JsonString(sId)); + JsonObjectSetInplace(ret, "number_flags", JsonInt(nNumberFlags)); + JsonObjectSetInplace(ret, "number_precision", JsonInt(nNumberPrecision)); + JsonObjectSetInplace(ret, "text_flags", JsonInt(nTextFlags)); + return ret; } json @@ -601,7 +571,7 @@ NuiStrRef( ) { json ret = JsonObject(); - ret = JsonObjectSet(ret, "strref", JsonInt(nStrRef)); + JsonObjectSetInplace(ret, "strref", JsonInt(nStrRef)); return ret; } @@ -629,9 +599,9 @@ NuiGroup( ) { json ret = NuiElement("group", JsonNull(), JsonNull()); - ret = JsonObjectSet(ret, "children", JsonArrayInsert(JsonArray(), jChild)); - ret = JsonObjectSet(ret, "border", JsonBool(bBorder)); - ret = JsonObjectSet(ret, "scrollbars", JsonInt(nScroll)); + JsonObjectSetInplace(ret, "children", JsonArrayInsert(JsonArray(), jChild)); + JsonObjectSetInplace(ret, "border", JsonBool(bBorder)); + JsonObjectSetInplace(ret, "scrollbars", JsonInt(nScroll)); return ret; } @@ -720,8 +690,8 @@ json NuiVec(float x, float y) { json ret = JsonObject(); - ret = JsonObjectSet(ret, "x", JsonFloat(x)); - ret = JsonObjectSet(ret, "y", JsonFloat(y)); + JsonObjectSetInplace(ret, "x", JsonFloat(x)); + JsonObjectSetInplace(ret, "y", JsonFloat(y)); return ret; } @@ -729,10 +699,10 @@ json NuiRect(float x, float y, float w, float h) { json ret = JsonObject(); - ret = JsonObjectSet(ret, "x", JsonFloat(x)); - ret = JsonObjectSet(ret, "y", JsonFloat(y)); - ret = JsonObjectSet(ret, "w", JsonFloat(w)); - ret = JsonObjectSet(ret, "h", JsonFloat(h)); + JsonObjectSetInplace(ret, "x", JsonFloat(x)); + JsonObjectSetInplace(ret, "y", JsonFloat(y)); + JsonObjectSetInplace(ret, "w", JsonFloat(w)); + JsonObjectSetInplace(ret, "h", JsonFloat(h)); return ret; } @@ -740,10 +710,10 @@ json NuiColor(int r, int g, int b, int a = 255) { json ret = JsonObject(); - ret = JsonObjectSet(ret, "r", JsonInt(r)); - ret = JsonObjectSet(ret, "g", JsonInt(g)); - ret = JsonObjectSet(ret, "b", JsonInt(b)); - ret = JsonObjectSet(ret, "a", JsonInt(a)); + JsonObjectSetInplace(ret, "r", JsonInt(r)); + JsonObjectSetInplace(ret, "g", JsonInt(g)); + JsonObjectSetInplace(ret, "b", JsonInt(b)); + JsonObjectSetInplace(ret, "a", JsonInt(a)); return ret; } @@ -756,6 +726,15 @@ NuiStyleForegroundColor( return JsonObjectSet(jElem, "foreground_color", jColor); } +json +NuiStyleFont( + json jElem, + json jFont +) +{ + return JsonObjectSet(jElem, "font", jFont); +} + json NuiSpacer() { @@ -770,8 +749,8 @@ NuiLabel( ) { json ret = NuiElement("label", JsonNull(), jValue); - ret = JsonObjectSet(ret, "text_halign", jHAlign); - ret = JsonObjectSet(ret, "text_valign", jVAlign); + JsonObjectSetInplace(ret, "text_halign", jHAlign); + JsonObjectSetInplace(ret, "text_valign", jVAlign); return ret; } @@ -783,8 +762,8 @@ NuiText( ) { json ret = NuiElement("text", JsonNull(), jValue); - ret = JsonObjectSet(ret, "border", JsonBool(bBorder)); - ret = JsonObjectSet(ret, "scrollbars", JsonInt(nScroll)); + JsonObjectSetInplace(ret, "border", JsonBool(bBorder)); + JsonObjectSetInplace(ret, "scrollbars", JsonInt(nScroll)); return ret; } @@ -831,9 +810,9 @@ NuiImage( ) { json img = NuiElement("image", JsonNull(), jResRef); - img = JsonObjectSet(img, "image_aspect", jAspect); - img = JsonObjectSet(img, "image_halign", jHAlign); - img = JsonObjectSet(img, "image_valign", jVAlign); + JsonObjectSetInplace(img, "image_aspect", jAspect); + JsonObjectSetInplace(img, "image_halign", jHAlign); + JsonObjectSetInplace(img, "image_valign", jVAlign); return img; } @@ -873,9 +852,9 @@ NuiSliderFloat( ) { json ret = NuiElement("sliderf", JsonNull(), jValue); - ret = JsonObjectSet(ret, "min", jMin); - ret = JsonObjectSet(ret, "max", jMax); - ret = JsonObjectSet(ret, "step", jStepSize); + JsonObjectSetInplace(ret, "min", jMin); + JsonObjectSetInplace(ret, "max", jMax); + JsonObjectSetInplace(ret, "step", jStepSize); return ret; } @@ -888,9 +867,9 @@ NuiSlider( ) { json ret = NuiElement("slider", JsonNull(), jValue); - ret = JsonObjectSet(ret, "min", jMin); - ret = JsonObjectSet(ret, "max", jMax); - ret = JsonObjectSet(ret, "step", jStepSize); + JsonObjectSetInplace(ret, "min", jMin); + JsonObjectSetInplace(ret, "max", jMax); + JsonObjectSetInplace(ret, "step", jStepSize); return ret; } @@ -912,9 +891,9 @@ NuiTextEdit( ) { json ret = NuiElement("textedit", jPlaceholder, jValue); - ret = JsonObjectSet(ret, "max", JsonInt(nMaxLength)); - ret = JsonObjectSet(ret, "multiline", JsonBool(bMultiline)); - ret = JsonObjectSet(ret, "wordwrap", JsonBool(bWordWrap)); + JsonObjectSetInplace(ret, "max", JsonInt(nMaxLength)); + JsonObjectSetInplace(ret, "multiline", JsonBool(bMultiline)); + JsonObjectSetInplace(ret, "wordwrap", JsonBool(bWordWrap)); return ret; } @@ -928,11 +907,11 @@ NuiList( ) { json ret = NuiElement("list", JsonNull(), JsonNull()); - ret = JsonObjectSet(ret, "row_template", jTemplate); - ret = JsonObjectSet(ret, "row_count", jRowCount); - ret = JsonObjectSet(ret, "row_height", JsonFloat(fRowHeight)); - ret = JsonObjectSet(ret, "border", JsonBool(bBorder)); - ret = JsonObjectSet(ret, "scrollbars", JsonInt(nScroll)); + JsonObjectSetInplace(ret, "row_template", jTemplate); + JsonObjectSetInplace(ret, "row_count", jRowCount); + JsonObjectSetInplace(ret, "row_height", JsonFloat(fRowHeight)); + JsonObjectSetInplace(ret, "border", JsonBool(bBorder)); + JsonObjectSetInplace(ret, "scrollbars", JsonInt(nScroll)); return ret; } @@ -944,9 +923,9 @@ NuiListTemplateCell( ) { json ret = JsonArray(); - ret = JsonArrayInsert(ret, jElem); - ret = JsonArrayInsert(ret, JsonFloat(fWidth)); - ret = JsonArrayInsert(ret, JsonBool(bVariable)); + JsonArrayInsertInplace(ret, jElem); + JsonArrayInsertInplace(ret, JsonFloat(fWidth)); + JsonArrayInsertInplace(ret, JsonBool(bVariable)); return ret; } @@ -967,8 +946,8 @@ NuiOptions( ) { json ret = NuiElement("options", JsonNull(), jValue); - ret = JsonObjectSet(ret, "direction", JsonInt(nDirection)); - ret = JsonObjectSet(ret, "elements", jElements); + JsonObjectSetInplace(ret, "direction", JsonInt(nDirection)); + JsonObjectSetInplace(ret, "elements", jElements); return ret; } @@ -980,8 +959,8 @@ NuiToggles( ) { json ret = NuiElement("tabbar", JsonNull(), jValue); - ret = JsonObjectSet(ret, "direction", JsonInt(nDirection)); - ret = JsonObjectSet(ret, "elements", jElements); + JsonObjectSetInplace(ret, "direction", JsonInt(nDirection)); + JsonObjectSetInplace(ret, "elements", jElements); return ret; } @@ -994,10 +973,10 @@ NuiChartSlot( ) { json ret = JsonObject(); - ret = JsonObjectSet(ret, "type", JsonInt(nType)); - ret = JsonObjectSet(ret, "legend", jLegend); - ret = JsonObjectSet(ret, "color", jColor); - ret = JsonObjectSet(ret, "data", jData); + JsonObjectSetInplace(ret, "type", JsonInt(nType)); + JsonObjectSetInplace(ret, "legend", jLegend); + JsonObjectSetInplace(ret, "color", jColor); + JsonObjectSetInplace(ret, "data", jData); return ret; } @@ -1018,17 +997,19 @@ NuiDrawListItem( json jFill, json jLineThickness, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS + int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, + int nBindArrays = FALSE ) { json ret = JsonObject(); - ret = JsonObjectSet(ret, "type", JsonInt(nType)); - ret = JsonObjectSet(ret, "enabled", jEnabled); - ret = JsonObjectSet(ret, "color", jColor); - ret = JsonObjectSet(ret, "fill", jFill); - ret = JsonObjectSet(ret, "line_thickness", jLineThickness); - ret = JsonObjectSet(ret, "order", JsonInt(nOrder)); - ret = JsonObjectSet(ret, "render", JsonInt(nRender)); + JsonObjectSetInplace(ret, "type", JsonInt(nType)); + JsonObjectSetInplace(ret, "enabled", jEnabled); + JsonObjectSetInplace(ret, "color", jColor); + JsonObjectSetInplace(ret, "fill", jFill); + JsonObjectSetInplace(ret, "line_thickness", jLineThickness); + JsonObjectSetInplace(ret, "order", JsonInt(nOrder)); + JsonObjectSetInplace(ret, "render", JsonInt(nRender)); + JsonObjectSetInplace(ret, "arrayBinds", JsonBool(nBindArrays)); return ret; } @@ -1040,11 +1021,12 @@ NuiDrawListPolyLine( json jLineThickness, json jPoints, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS + int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, + int nBindArrays = FALSE ) { - json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_POLYLINE, jEnabled, jColor, jFill, jLineThickness, nOrder, nRender); - ret = JsonObjectSet(ret, "points", jPoints); + json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_POLYLINE, jEnabled, jColor, jFill, jLineThickness, nOrder, nRender, nBindArrays); + JsonObjectSetInplace(ret, "points", jPoints); return ret; } @@ -1058,14 +1040,15 @@ NuiDrawListCurve( json jCtrl0, json jCtrl1, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS + int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, + int nBindArrays = FALSE ) { - json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_CURVE, jEnabled, jColor, JsonBool(0), jLineThickness, nOrder, nRender); - ret = JsonObjectSet(ret, "a", jA); - ret = JsonObjectSet(ret, "b", jB); - ret = JsonObjectSet(ret, "ctrl0", jCtrl0); - ret = JsonObjectSet(ret, "ctrl1", jCtrl1); + json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_CURVE, jEnabled, jColor, JsonBool(0), jLineThickness, nOrder, nRender, nBindArrays); + JsonObjectSetInplace(ret, "a", jA); + JsonObjectSetInplace(ret, "b", jB); + JsonObjectSetInplace(ret, "ctrl0", jCtrl0); + JsonObjectSetInplace(ret, "ctrl1", jCtrl1); return ret; } @@ -1077,11 +1060,12 @@ NuiDrawListCircle( json jLineThickness, json jRect, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS + int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, + int nBindArrays = FALSE ) { - json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_CIRCLE, jEnabled, jColor, jFill, jLineThickness, nOrder, nRender); - ret = JsonObjectSet(ret, "rect", jRect); + json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_CIRCLE, jEnabled, jColor, jFill, jLineThickness, nOrder, nRender, nBindArrays); + JsonObjectSetInplace(ret, "rect", jRect); return ret; } @@ -1096,14 +1080,15 @@ NuiDrawListArc( json jAMin, json jAMax, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS + int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, + int nBindArrays = FALSE ) { - json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_ARC, jEnabled, jColor, jFill, jLineThickness, nOrder, nRender); - ret = JsonObjectSet(ret, "c", jCenter); - ret = JsonObjectSet(ret, "radius", jRadius); - ret = JsonObjectSet(ret, "amin", jAMin); - ret = JsonObjectSet(ret, "amax", jAMax); + json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_ARC, jEnabled, jColor, jFill, jLineThickness, nOrder, nRender, nBindArrays); + JsonObjectSetInplace(ret, "c", jCenter); + JsonObjectSetInplace(ret, "radius", jRadius); + JsonObjectSetInplace(ret, "amin", jAMin); + JsonObjectSetInplace(ret, "amax", jAMax); return ret; } @@ -1114,12 +1099,15 @@ NuiDrawListText( json jRect, json jText, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS + int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, + int nBindArrays = FALSE, + json jFont = JSON_STRING ) { - json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_TEXT, jEnabled, jColor, JsonNull(), JsonNull(), nOrder, nRender); - ret = JsonObjectSet(ret, "rect", jRect); - ret = JsonObjectSet(ret, "text", jText); + json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_TEXT, jEnabled, jColor, JsonNull(), JsonNull(), nOrder, nRender, nBindArrays); + JsonObjectSetInplace(ret, "rect", jRect); + JsonObjectSetInplace(ret, "text", jText); + ret = NuiStyleFont(ret, jFont); return ret; } @@ -1132,15 +1120,16 @@ NuiDrawListImage( json jHAlign, json jVAlign, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS + int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, + int nBindArrays = FALSE ) { - json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_IMAGE, jEnabled, JsonNull(), JsonNull(), JsonNull(), nOrder, nRender); - ret = JsonObjectSet(ret, "image", jResRef); - ret = JsonObjectSet(ret, "rect", jRect); - ret = JsonObjectSet(ret, "image_aspect", jAspect); - ret = JsonObjectSet(ret, "image_halign", jHAlign); - ret = JsonObjectSet(ret, "image_valign", jVAlign); + json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_IMAGE, jEnabled, JsonNull(), JsonNull(), JsonNull(), nOrder, nRender, nBindArrays); + JsonObjectSetInplace(ret, "image", jResRef); + JsonObjectSetInplace(ret, "rect", jRect); + JsonObjectSetInplace(ret, "image_aspect", jAspect); + JsonObjectSetInplace(ret, "image_halign", jHAlign); + JsonObjectSetInplace(ret, "image_valign", jVAlign); return ret; } @@ -1161,12 +1150,30 @@ NuiDrawListLine( json jA, json jB, int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, - int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS + int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, + int nBindArrays = FALSE ) { - json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_LINE, jEnabled, jColor, JsonNull(), jLineThickness, nOrder, nRender); - ret = JsonObjectSet(ret, "a", jA); - ret = JsonObjectSet(ret, "b", jB); + json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_LINE, jEnabled, jColor, JsonNull(), jLineThickness, nOrder, nRender, nBindArrays); + JsonObjectSetInplace(ret, "a", jA); + JsonObjectSetInplace(ret, "b", jB); + return ret; +} + +json +NuiDrawListRect( + json jEnabled, + json jColor, + json jFill, + json jLineThickness, + json jRect, + int nOrder = NUI_DRAW_LIST_ITEM_ORDER_AFTER, + int nRender = NUI_DRAW_LIST_ITEM_RENDER_ALWAYS, + int nBindArrays = FALSE +) +{ + json ret = NuiDrawListItem(NUI_DRAW_LIST_ITEM_TYPE_RECT, jEnabled, jColor, jFill, jLineThickness, nOrder, nRender, nBindArrays); + JsonObjectSetInplace(ret, "rect", jRect); return ret; } @@ -1178,7 +1185,7 @@ NuiDrawList( ) { json ret = JsonObjectSet(jElem, "draw_list", jList); - ret = JsonObjectSet(ret, "draw_list_scissor", jScissor); + JsonObjectSetInplace(ret, "draw_list_scissor", jScissor); return ret; } @@ -1190,4 +1197,3 @@ NuiDrawList( // json ret = NuiElement("canvas", JsonNull(), jList); // return ret; // } - diff --git a/src/include/prc_add_spell_dc.nss b/src/include/prc_add_spell_dc.nss index 4cde6de..f2a0e2f 100644 --- a/src/include/prc_add_spell_dc.nss +++ b/src/include/prc_add_spell_dc.nss @@ -513,6 +513,8 @@ int PRCGetSpellSaveDC(int nSpellID = -1, int nSchool = -1, object oCaster = OBJE if(nClass == CLASS_TYPE_BARD) nDC += StringToInt(Get2DACache("Spells", "Bard", nSpellID)); + else if(nClass == CLASS_TYPE_ASSASSIN) + nDC += StringToInt(Get2DACache("Spells", "Assassin", nSpellID)); else if(nClass == CLASS_TYPE_CLERIC || nClass == CLASS_TYPE_UR_PRIEST || nClass == CLASS_TYPE_OCULAR) nDC += StringToInt(Get2DACache("Spells", "Cleric", nSpellID)); else if(nClass == CLASS_TYPE_DRUID) diff --git a/src/include/prc_class_const.nss b/src/include/prc_class_const.nss index 286b574..04e96f1 100644 --- a/src/include/prc_class_const.nss +++ b/src/include/prc_class_const.nss @@ -143,7 +143,7 @@ const int CLASS_TYPE_MASTER_HARPER = 176; const int CLASS_TYPE_FRE_BERSERKER = 177; const int CLASS_TYPE_TEMPEST = 178; const int CLASS_TYPE_FOE_HUNTER = 179; -//:: Free = 180 +const int CLASS_TYPE_VERDANT_LORD = 180; const int CLASS_TYPE_ORC_WARLORD = 181; const int CLASS_TYPE_THRALL_OF_GRAZZT_A = 182; const int CLASS_TYPE_NECROCARNATE = 183; @@ -162,7 +162,7 @@ const int CLASS_TYPE_MASTER_OF_NINE = 195; const int CLASS_TYPE_ETERNAL_BLADE = 196; const int CLASS_TYPE_SHADOW_SUN_NINJA = 197; const int CLASS_TYPE_WITCHBORN_BINDER = 198; -const int CLASS_TYPE_BAELNORN = 199; +const int CLASS_TYPE_LION_OF_TALISID = 199; const int CLASS_TYPE_DISCIPLE_OF_MEPH = 200; const int CLASS_TYPE_SOUL_EATER = 201; const int CLASS_TYPE_HENSHIN_MYSTIC = 202; @@ -236,6 +236,7 @@ const int CLASS_TYPE_WITCH = -1; const int CLASS_TYPE_TEMPLAR = -1; const int CLASS_TYPE_MYSTIC = -1; const int CLASS_TYPE_NOBLE = -1; +const int CLASS_TYPE_BAELNORN = -2; //void main (){} \ No newline at end of file diff --git a/src/include/prc_effect_inc.nss b/src/include/prc_effect_inc.nss index 6186f71..4552481 100644 --- a/src/include/prc_effect_inc.nss +++ b/src/include/prc_effect_inc.nss @@ -75,6 +75,13 @@ void DeathlessFrenzyCheck(object oTarget); // * PRC Version of a Bioware function to disable include loops void PRCRemoveSpellEffects(int nSpell_ID, object oCaster, object oTarget); +/** + * Target is immune to gaze attacks + * + * @return the Dazzle effect + */ +effect EffectGazeImmune(); + /** * Dazzles the target: -1 Attack, Search, Spot, and VFX * @@ -583,7 +590,8 @@ effect PRCEffectHeal(int nHP, object oTarget) return EffectHeal(nHP); } -effect EffectAbilityBasedSkillIncrease(int iAbility, int iIncrease = 1){ +effect EffectAbilityBasedSkillIncrease(int iAbility, int iIncrease = 1) +{ effect eReturn; switch(iAbility) { @@ -639,7 +647,8 @@ effect EffectAbilityBasedSkillIncrease(int iAbility, int iIncrease = 1){ return eReturn; } -effect EffectAbilityBasedSkillDecrease(int iAbility, int iDecrease = 1){ +effect EffectAbilityBasedSkillDecrease(int iAbility, int iDecrease = 1) +{ effect eReturn; switch(iAbility) { @@ -695,7 +704,8 @@ effect EffectAbilityBasedSkillDecrease(int iAbility, int iDecrease = 1){ return eReturn; } -effect EffectDamageImmunityAll(){ +effect EffectDamageImmunityAll() +{ effect eReturn = EffectDamageImmunityIncrease(DAMAGE_TYPE_ACID, 100); eReturn = EffectLinkEffects(eReturn, EffectDamageImmunityIncrease(DAMAGE_TYPE_BLUDGEONING, 100)); eReturn = EffectLinkEffects(eReturn, EffectDamageImmunityIncrease(DAMAGE_TYPE_COLD, 100)); @@ -712,7 +722,8 @@ effect EffectDamageImmunityAll(){ return eReturn; } -effect EffectImmunityMiscAll(){ +effect EffectImmunityMiscAll() +{ effect eReturn = EffectImmunity(IMMUNITY_TYPE_ABILITY_DECREASE); eReturn = EffectLinkEffects(eReturn, EffectImmunity(IMMUNITY_TYPE_BLINDNESS)); eReturn = EffectLinkEffects(eReturn, EffectImmunity(IMMUNITY_TYPE_DEAFNESS)); @@ -732,6 +743,31 @@ effect EffectImmunityMiscAll(){ return eReturn; } +//:: Immunity to all gaze attacks +effect EffectGazeImmune() +{ + effect eBlank; + + effect eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_CHARM); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_CONFUSION); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_DAZE); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_DEATH); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_DESTROY_CHAOS); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_DESTROY_EVIL); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_DESTROY_GOOD); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_DESTROY_LAW); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_DOMINATE); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_DOOM); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_FEAR); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_PARALYSIS); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_PETRIFY); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_STUNNED); + + eReturn = TagEffect(eReturn, "PRCGazeImmune"); + + return eReturn; +} + int GetIsShaken(object oTarget) { effect eEffect = GetFirstEffect(oTarget); @@ -747,4 +783,7 @@ int GetIsShaken(object oTarget) eEffect = GetNextEffect(oTarget); } return FALSE; -} \ No newline at end of file +} + +//:: Test void +//:: void main() {} \ No newline at end of file diff --git a/src/include/prc_feat_const.nss b/src/include/prc_feat_const.nss index c961dab..8035960 100644 --- a/src/include/prc_feat_const.nss +++ b/src/include/prc_feat_const.nss @@ -152,6 +152,9 @@ const int FEAT_EPIC_DIAMOND_DRAGON = 25115; const int FEAT_EPIC_CRUSADER = 25116; const int FEAT_EPIC_SWORDSAGE = 25117; const int FEAT_EPIC_WARBLADE = 25118; +const int FEAT_EPIC_LION_OF_TALISID = 25600; +const int FEAT_EPIC_VERDANT_LORD = 25618; + //:: Vile Martial Strike Expansion const int FEAT_VILE_MARTIAL_EAGLE_CLAW = 24800; @@ -195,6 +198,28 @@ const int FEAT_CHARMING_THE_ARROW = 25998; //:: Skill Based Feats const int FEAT_JUMP = 2884; +//:: Lion of Talisid +const int FEAT_LOT_LIONS_COURAGE = 25614; +const int FEAT_LOT_LIONS_POUNCE = 25615; +const int FEAT_LOT_LIONS_SWIFTNESS = 25616; +const int FEAT_LOT_LEONALS_ROAR = 25617; + +//::: Verdant Lord +const int FEAT_VL_EXPERT_INFUSION = 25634; +const int FEAT_VL_SUN_SUSTENANCE = 25635; +const int FEAT_VL_SPONTANEITY = 25636; +const int FEAT_VL_PLANT_FACILITY = 25637; +const int FEAT_VL_WILD_SHAPE_TREANT = 25638; +const int FEAT_VL_ANIMATE_TREE = 25639; +const int FEAT_VL_GAEAS_EMBRACE = 25640; + +//:: Masters of the Wild feats +const int FEAT_CREATE_INFUSION = 25960; +const int FEAT_MAGICAL_ARTISAN_CREATE_INFUSION = 25961; +const int FEAT_PLANT_DEFIANCE = 25992; +const int FEAT_PLANT_CONTROL = 25993; + + //:: Racial Feats const int FEAT_WEMIC_JUMP_8 = 4518; const int FEAT_URDINNIR_STONESKIN = 4644; @@ -782,6 +807,9 @@ const int FEAT_SUEL_IGNORE_SPELL_FAILURE = 2398; const int FEAT_SUEL_EXTENDED_SPELL = 2399; const int FEAT_SUEL_DISPELLING_STRIKE = 2400; +//:: Druid +const int FEAT_SPONT_SUMMON = 2372; + //Passive Feats const int FEAT_ETERNAL_FREEDOM = 4298; const int FEAT_INTUITIVE_ATTACK = 3166; @@ -1538,18 +1566,19 @@ const int FEAT_SELVETARMS_BLESSING = 2447; const int FEAT_RANGER_DUAL = 374; const int FEAT_CAMOUFLAGE = 4486; -//Exalted Feat -const int FEAT_SAC_VOW = 3388; -const int FEAT_VOW_OBED = 3389; -const int FEAT_EXALTED_TURNING = 3168; -const int FEAT_HAND_HEALER = 3167; -const int FEAT_NIMBUSLIGHT = 3165; -const int FEAT_HOLYRADIANCE = 3164; -const int FEAT_STIGMATA = 3163; -const int FEAT_SERVHEAVEN = 3355; -const int FEAT_RANGED_SMITE = 3356; -const int FEAT_VOW_PURITY = 5360; -const int FEAT_VOWOFPOVERTY = 26001; +//:: Exalted Feats +const int FEAT_SAC_VOW = 3388; +const int FEAT_VOW_OBED = 3389; +const int FEAT_EXALTED_TURNING = 3168; +const int FEAT_HAND_HEALER = 3167; +const int FEAT_NIMBUSLIGHT = 3165; +const int FEAT_HOLYRADIANCE = 3164; +const int FEAT_STIGMATA = 3163; +const int FEAT_SERVHEAVEN = 3355; +const int FEAT_RANGED_SMITE = 3356; +const int FEAT_VOW_PURITY = 5360; +const int FEAT_VOWOFPOVERTY = 26002; +const int FEAT_FAV_COMPANIONS = 25994; //Vile Feat const int FEAT_LICHLOVED = 3395; @@ -1868,12 +1897,12 @@ const int FEAT_SANCTIFY_MARTIAL_SICKLE = 3169; const int FEAT_SANCTIFY_MARTIAL_MINDBLADE = 3623; const int FEAT_SANCTIFY_MARTIAL_WHIP = 3596; const int FEAT_SANCTIFY_MARTIAL_TRIDENT = 3597; -const int FEAT_SANCTIFYKISTRIKE = 26002; -const int FEAT_HOLYKISTRIKE = 26003; -const int FEAT_FISTOFHEAVENS = 26004; -const int FEAT_VOWABSTINENCE = 26005; -const int FEAT_VOWCHASTITY = 26006; -const int FEAT_GIFTOFFAITH = 26007; +const int FEAT_SANCTIFYKISTRIKE = 26003; +const int FEAT_HOLYKISTRIKE = 26004; +const int FEAT_FISTOFHEAVENS = 26005; +const int FEAT_VOWABSTINENCE = 26006; +const int FEAT_VOWCHASTITY = 26007; +const int FEAT_GIFTOFFAITH = 26008; //heartwarder const int FEAT_CHARISMA_INC1 = 3230; @@ -3189,6 +3218,8 @@ const int FEAT_ETHEREAL = 4167; const int FEAT_TEMPLATE_ARCHLICH_MARKER = 22700; const int FEAT_TEMPLATE_ARCHLICH_TURN_UNDEAD = 22701; +const int FEAT_TEMPLATE_BAELNORN_MARKER = 22708; + const int FEAT_TEMPLATE_CELESTIAL_SMITE_EVIL = 22601; const int FEAT_TEMPLATE_CELESTIAL_MARKER = 22602; const int FEAT_TEMPLATE_FIENDISH_SMITE_GOOD = 22603; @@ -3933,6 +3964,8 @@ const int FEAT_OPPORTUNISTIC_PIETY_HEAL = 5358; const int FEAT_OPPORTUNISTIC_PIETY_TURN = 5359; // Combat Maneuver Feats +const int FEAT_CM_CHARGE = 2823; +const int FEAT_CM_GRAPPLE = 3414; const int FEAT_CURLING_WAVE_STRIKE = 2809; const int FEAT_SIDESTEP_CHARGE = 3505; const int FEAT_POWERFUL_CHARGE = 3506; @@ -6203,6 +6236,38 @@ const int FEAT_SHINING_BLADE_SPELLCASTING_VASSAL = 19587; const int FEAT_SWIFT_WING_SPELLCASTING_VASSAL = 19588; const int FEAT_WARPRIEST_SPELLCASTING_VASSAL = 19589; +//:: Lion of Talisid marker feats +const int FEAT_LION_OF_TALISID_SPELLCASTING_ARCHIVIST = 25601; +const int FEAT_LION_OF_TALISID_SPELLCASTING_CLERIC = 25602; +const int FEAT_LION_OF_TALISID_SPELLCASTING_DRUID = 25603; +const int FEAT_LION_OF_TALISID_SPELLCASTING_FAVOURED_SOUL = 25604; +const int FEAT_LION_OF_TALISID_SPELLCASTING_HEALER = 25605; +const int FEAT_LION_OF_TALISID_SPELLCASTING_JOWAW = 25606; +const int FEAT_LION_OF_TALISID_SPELLCASTING_KOTMC = 25607; +const int FEAT_LION_OF_TALISID_SPELLCASTING_NENTYAR_HUNTER = 25608; +const int FEAT_LION_OF_TALISID_SPELLCASTING_RANGER = 25609; +const int FEAT_LION_OF_TALISID_SPELLCASTING_OASHAMAN = 25610; +const int FEAT_LION_OF_TALISID_SPELLCASTING_SOHEI = 25611; +const int FEAT_LION_OF_TALISID_SPELLCASTING_SOL = 25612; +const int FEAT_LION_OF_TALISID_SPELLCASTING_SPSHAMAN = 25613; + +//:: Verdant Lord marker feats +const int FEAT_VERDANT_LORD_SPELLCASTING_ARCHIVIST = 25619; +const int FEAT_VERDANT_LORD_SPELLCASTING_CLERIC = 25620; +const int FEAT_VERDANT_LORD_SPELLCASTING_DRUID = 25621; +const int FEAT_VERDANT_LORD_SPELLCASTING_FAVOURED_SOUL = 25622; +const int FEAT_VERDANT_LORD_SPELLCASTING_HEALER = 25623; +const int FEAT_VERDANT_LORD_SPELLCASTING_JOWAW = 25624; +const int FEAT_VERDANT_LORD_SPELLCASTING_KOTC = 25625; +const int FEAT_VERDANT_LORD_SPELLCASTING_KOTMC = 25626; +const int FEAT_VERDANT_LORD_SPELLCASTING_NENTYAR_HUNTER = 25627; +const int FEAT_VERDANT_LORD_SPELLCASTING_PALADIN = 25628; +const int FEAT_VERDANT_LORD_SPELLCASTING_RANGER = 25629; +const int FEAT_VERDANT_LORD_SPELLCASTING_OASHAMAN = 25630; +const int FEAT_VERDANT_LORD_SPELLCASTING_SOHEI = 25631; +const int FEAT_VERDANT_LORD_SPELLCASTING_SOL = 25632; +const int FEAT_VERDANT_LORD_SPELLCASTING_SPSHAMAN = 25633; + //:: No spellcasting or invoking marker feats const int FEAT_ASMODEUS_SPELLCASTING_NONE = 19590; const int FEAT_TIAMAT_SPELLCASTING_NONE = 19591; diff --git a/src/include/prc_inc_castlvl.nss b/src/include/prc_inc_castlvl.nss index 876ac84..8336a62 100644 --- a/src/include/prc_inc_castlvl.nss +++ b/src/include/prc_inc_castlvl.nss @@ -575,8 +575,8 @@ int PRCGetCasterLevel(object oCaster = OBJECT_SELF) iReturnLevel = GetLevelByClass(CLASS_TYPE_SHAPECHANGER); } - // Casting as a bard but don't have any levels in the class - if(iCastingClass == CLASS_TYPE_BARD && !GetLevelByClass(CLASS_TYPE_BARD, oCaster)) + // Casting as a bard but don't have any levels in the class //:: Double-dipping? +/* if(iCastingClass == CLASS_TYPE_BARD && !GetLevelByClass(CLASS_TYPE_BARD, oCaster)) { int nRace = GetRacialType(oCaster); @@ -584,7 +584,7 @@ int PRCGetCasterLevel(object oCaster = OBJECT_SELF) //otherwise use RHD instead of bard levels if(nRace == RACIAL_TYPE_GLOURA) iReturnLevel = GetLevelByClass(CLASS_TYPE_FEY); - } + } */ //Spell Rage ability if(GetHasSpellEffect(SPELL_SPELL_RAGE, oCaster) @@ -960,8 +960,10 @@ int GetArcanePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) } //:: End Bard Arcane PrC casting calculations - if(nCastingClass == CLASS_TYPE_BARD && nRace == RACIAL_TYPE_GLOURA && !GetLevelByClass(CLASS_TYPE_BARD, oCaster)) + if(nCastingClass == CLASS_TYPE_BARD || nCastingClass == CLASS_TYPE_BARD && nRace == RACIAL_TYPE_GLOURA && !GetLevelByClass(CLASS_TYPE_BARD, oCaster)) { + if(DEBUG) DoDebug("prc_inc_castlvl >> Found Fey RHD caster (not bard)"); + if(GetHasFeat(FEAT_ABCHAMP_SPELLCASTING_FEY, oCaster)) nArcane += GetLevelByClass(CLASS_TYPE_ABJURANT_CHAMPION, oCaster); @@ -1065,7 +1067,10 @@ int GetArcanePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nArcane += GetLevelByClass(CLASS_TYPE_UNSEEN_SEER, oCaster); if(GetHasFeat(FEAT_VIRTUOSO_SPELLCASTING_FEY, oCaster)) + { nArcane += GetLevelByClass(CLASS_TYPE_VIRTUOSO, oCaster); + if(DEBUG) DoDebug("prc_inc_castlvl >> Found Fey + Virtuoso PrC. Arcane caster level is "+IntToString(nArcane)+"."); + } if(GetHasFeat(FEAT_WWOC_SPELLCASTING_FEY, oCaster)) nArcane += GetLevelByClass(CLASS_TYPE_WAR_WIZARD_OF_CORMYR, oCaster); @@ -1143,8 +1148,8 @@ int GetArcanePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_DIABOLIST_SPELLCASTING_ASSASSIN, oCaster)) nArcane += GetLevelByClass(CLASS_TYPE_DIABOLIST, oCaster); - if(GetHasFeat(FEAT_DHEART_SPELLCASTING_ASSASSIN, oCaster)) - nArcane += GetLevelByClass(CLASS_TYPE_DRAGONHEART_MAGE, oCaster); + //if(GetHasFeat(FEAT_DHEART_SPELLCASTING_ASSASSIN, oCaster)) + //nArcane += GetLevelByClass(CLASS_TYPE_DRAGONHEART_MAGE, oCaster); if(GetHasFeat(FEAT_EKNIGHT_SPELLCASTING_ASSASSIN, oCaster)) nArcane += GetLevelByClass(CLASS_TYPE_ELDRITCH_KNIGHT, oCaster); @@ -3817,6 +3822,9 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_ARCHIVIST, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_ARCHIVIST, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_LION_OF_TALISID, oCaster); + /* if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_ARCHIVIST, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); */ @@ -4143,7 +4151,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_HIEROPHANT, oCaster); if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_CLERIC, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_CLERIC, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_LION_OF_TALISID, oCaster); if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_CLERIC, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); @@ -4253,7 +4264,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_HIEROPHANT, oCaster); if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_DRUID, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_DRUID, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_LION_OF_TALISID, oCaster); // if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_DRUID, oCaster)) // nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); @@ -4365,10 +4379,13 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_HIEROPHANT, oCaster); if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_FAVOURED_SOUL, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_FAVOURED_SOUL, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_LION_OF_TALISID, oCaster); - // if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_FAVOURED_SOUL, oCaster)) - // nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); + if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_FAVOURED_SOUL, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); if(GetHasFeat(FEAT_MORNINGLORD_SPELLCASTING_FAVOURED_SOUL, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_MORNINGLORD, oCaster); @@ -4474,6 +4491,9 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_HEALER, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_HEALER, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_LION_OF_TALISID, oCaster); + /* if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_HEALER, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); */ @@ -4581,6 +4601,9 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_JUSTICEWW, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_JOWAW, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_LION_OF_TALISID, oCaster); + // if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_JUSTICEWW, oCaster)) // nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); @@ -4791,6 +4814,9 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_KNIGHT_MIDDLECIRCLE, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_KOTMC, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_LION_OF_TALISID, oCaster); + /* if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_KNIGHT_MIDDLECIRCLE, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); */ @@ -4896,7 +4922,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_HIEROPHANT, oCaster); */ if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_NENTYAR_HUNTER, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_NENTYAR_HUNTER, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_LION_OF_TALISID, oCaster); /* if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_NENTYAR_HUNTER, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); */ @@ -5207,7 +5236,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_HIEROPHANT, oCaster); */ if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_RANGER, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_RANGER, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_LION_OF_TALISID, oCaster); if(GetHasFeat(FEAT_MORNINGLORD_SPELLCASTING_RANGER, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_MORNINGLORD, oCaster); @@ -5311,7 +5343,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_HIEROPHANT, oCaster); if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_OASHAMAN, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_OASHAMAN, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_SHAMAN, oCaster); if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_OASHAMAN, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); @@ -5524,6 +5559,9 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_SOHEI, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_SOHEI, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_LION_OF_TALISID, oCaster); + // if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_SOHEI, oCaster)) // nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); @@ -5631,6 +5669,9 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_HOSPITALER_SPELLCASTING_SOL, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_HOSPITALER, oCaster); + if(GetHasFeat(FEAT_LION_OF_TALISID_SPELLCASTING_SOL, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_LION_OF_TALISID, oCaster); + /* if(GetHasFeat(FEAT_MASTER_OF_SHROUDS_SPELLCASTING_SOL, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_MASTER_OF_SHROUDS, oCaster); */ diff --git a/src/include/prc_inc_clsfunc.nss b/src/include/prc_inc_clsfunc.nss index 3a6c251..4d1ffcb 100644 --- a/src/include/prc_inc_clsfunc.nss +++ b/src/include/prc_inc_clsfunc.nss @@ -402,6 +402,7 @@ int Vile_Feat(int iTypeWeap) case BASE_ITEM_LONGSWORD: return GetHasFeat(FEAT_VILE_MARTIAL_LONGSWORD); case BASE_ITEM_MORNINGSTAR: return GetHasFeat(FEAT_VILE_MARTIAL_MORNINGSTAR); case BASE_ITEM_QUARTERSTAFF: return GetHasFeat(FEAT_VILE_MARTIAL_QUARTERSTAFF); + case BASE_ITEM_MAGICSTAFF: return GetHasFeat(FEAT_VILE_MARTIAL_QUARTERSTAFF); case BASE_ITEM_RAPIER: return GetHasFeat(FEAT_VILE_MARTIAL_RAPIER); case BASE_ITEM_SCIMITAR: return GetHasFeat(FEAT_VILE_MARTIAL_SCIMITAR); case BASE_ITEM_SCYTHE: return GetHasFeat(FEAT_VILE_MARTIAL_SCYTHE); @@ -482,6 +483,7 @@ int GetSanctifedMartialFeat(int iTypeWeap) case BASE_ITEM_LONGSWORD: return FEAT_SANCTIFY_MARTIAL_LONGSWORD; case BASE_ITEM_MORNINGSTAR: return FEAT_SANCTIFY_MARTIAL_MORNINGSTAR; case BASE_ITEM_QUARTERSTAFF: return FEAT_SANCTIFY_MARTIAL_QUARTERSTAFF; + case BASE_ITEM_MAGICSTAFF: return FEAT_SANCTIFY_MARTIAL_QUARTERSTAFF; case BASE_ITEM_RAPIER: return FEAT_SANCTIFY_MARTIAL_RAPIER; case BASE_ITEM_SCIMITAR: return FEAT_SANCTIFY_MARTIAL_SCIMITAR; case BASE_ITEM_SCYTHE: return FEAT_SANCTIFY_MARTIAL_SCYTHE; @@ -555,6 +557,7 @@ int Sanctify_Feat(int iTypeWeap) case BASE_ITEM_LONGSWORD: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_LONGSWORD); case BASE_ITEM_MORNINGSTAR: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_MORNINGSTAR); case BASE_ITEM_QUARTERSTAFF: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_QUARTERSTAFF); + case BASE_ITEM_MAGICSTAFF: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_QUARTERSTAFF); case BASE_ITEM_RAPIER: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_RAPIER); case BASE_ITEM_SCIMITAR: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_SCIMITAR); case BASE_ITEM_SCYTHE: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_SCYTHE); diff --git a/src/include/prc_inc_combat.nss b/src/include/prc_inc_combat.nss index f5a2411..df4a5b7 100644 --- a/src/include/prc_inc_combat.nss +++ b/src/include/prc_inc_combat.nss @@ -1082,6 +1082,7 @@ int GetIsTwoHandedMeleeWeaponType(int iWeaponType) case BASE_ITEM_HEAVYFLAIL: return TRUE; case BASE_ITEM_SCYTHE: return TRUE; case BASE_ITEM_QUARTERSTAFF: return TRUE; + case BASE_ITEM_MAGICSTAFF: return TRUE; case BASE_ITEM_ELVEN_COURTBLADE: return TRUE; case BASE_ITEM_MAUL: return TRUE; case BASE_ITEM_FALCHION: return TRUE; @@ -1093,7 +1094,7 @@ int GetIsTwoHandedMeleeWeapon(object oWeap) { return GetIsTwoHandedMeleeWeaponType(GetBaseItemType(oWeap)); } - + int GetIsCreatureWeaponType(int iWeaponType) { // any of the three creature weapon types that produce bludgeoning, piercing or slashing damage @@ -1130,6 +1131,7 @@ int GetIsSimpleWeaponType(int iWeaponType) { case BASE_ITEM_MORNINGSTAR: return 1; case BASE_ITEM_QUARTERSTAFF: return 1; + case BASE_ITEM_MAGICSTAFF: return 1; case BASE_ITEM_SHORTSPEAR: return 1; case BASE_ITEM_HEAVYCROSSBOW: return 1; case BASE_ITEM_INVALID: return 1; @@ -1204,6 +1206,7 @@ int GetIsDoubleSidedWeaponType(int iWeaponType) return ( iWeaponType == BASE_ITEM_DIREMACE || iWeaponType == BASE_ITEM_DOUBLEAXE || iWeaponType == BASE_ITEM_TWOBLADEDSWORD + || iWeaponType == BASE_ITEM_DOUBLE_SCIMITAR ); } @@ -1562,6 +1565,19 @@ struct WeaponFeat GetAllFeatsOfWeaponType(int iWeaponType) sFeat.VileMartialStrike = FEAT_VILE_MARTIAL_QUARTERSTAFF; break; } + case BASE_ITEM_MAGICSTAFF: { + sFeat.Focus = FEAT_WEAPON_FOCUS_STAFF; + sFeat.Specialization = FEAT_WEAPON_SPECIALIZATION_STAFF; + sFeat.EpicFocus = FEAT_EPIC_WEAPON_FOCUS_QUARTERSTAFF; + sFeat.EpicSpecialization = FEAT_EPIC_WEAPON_SPECIALIZATION_QUARTERSTAFF; + sFeat.ImprovedCritical = FEAT_IMPROVED_CRITICAL_STAFF; + sFeat.OverwhelmingCritical = FEAT_EPIC_OVERWHELMING_CRITICAL_QUARTERSTAFF; + sFeat.DevastatingCritical = FEAT_EPIC_DEVASTATING_CRITICAL_QUARTERSTAFF; + sFeat.WeaponOfChoice = FEAT_WEAPON_OF_CHOICE_QUARTERSTAFF; + sFeat.SanctifyMartialStrike = FEAT_SANCTIFY_MARTIAL_QUARTERSTAFF; + sFeat.VileMartialStrike = FEAT_VILE_MARTIAL_QUARTERSTAFF; + break; + } case BASE_ITEM_RAPIER: { sFeat.Focus = FEAT_WEAPON_FOCUS_RAPIER; sFeat.Specialization = FEAT_WEAPON_SPECIALIZATION_RAPIER; diff --git a/src/include/prc_inc_combmove.nss b/src/include/prc_inc_combmove.nss index b81aab4..d280dd1 100644 --- a/src/include/prc_inc_combmove.nss +++ b/src/include/prc_inc_combmove.nss @@ -1140,6 +1140,9 @@ void DoCharge(object oPC, object oTarget, int nDoAttack = TRUE, int nGenerateAoO nPounce = TRUE; if (GetHasSpellEffect(VESTIGE_CHUPOCLOPS, oPC) && GetLocalInt(oPC, "ExploitVestige") != VESTIGE_CHUPOCLOPS_POUNCE) nPounce = TRUE; + //:: Lion of Talisid + if(GetHasFeat(FEAT_LOT_LIONS_POUNCE, oPC)) + nPounce = TRUE; // Checks for a White Raven Stance // If it exists, +1 damage/initiator level @@ -2312,7 +2315,10 @@ void DoShieldCharge(object oPC, object oTarget, int nSlam = FALSE) if(GetLevelByClass(CLASS_TYPE_CELEBRANT_SHARESS, oPC) >= 5) nPounce = TRUE; if(GetRacialType(oPC) == RACIAL_TYPE_MARRUSAULT) - nPounce = TRUE; + nPounce = TRUE; + //:: Lion of Talisid + if(GetHasFeat(FEAT_LOT_LIONS_POUNCE, oPC)) + nPounce = TRUE; // Checks for a White Raven Stance // If it exists, +1 damage/initiator level diff --git a/src/include/prc_inc_core.nss b/src/include/prc_inc_core.nss index 30af518..099c3ac 100644 --- a/src/include/prc_inc_core.nss +++ b/src/include/prc_inc_core.nss @@ -462,7 +462,7 @@ int PRCGetSpellLevelForClass(int nSpell, int nClass) return nSpellLevel; } -// returns the spelllevel of nSpell as it can be cast by oCreature +// returns the spell circle level of nSpell as it can be cast by oCreature int PRCGetSpellLevel(object oCreature, int nSpell) { /*if (!PRCGetHasSpell(nSpell, oCreature)) @@ -605,7 +605,7 @@ int PRCGetHasSpell(int nRealSpellID, object oCreature = OBJECT_SELF) if(nSpellbookType == SPELLBOOK_TYPE_PREPARED) { nCount = persistant_array_get_int(oCreature, "NewSpellbookMem_" + IntToString(nClass), j); - if(DEBUG) DoDebug("PRCGetHasSpell: NewSpellbookMem_" + IntToString(nClass) + "[" + IntToString(j) + "] = " + IntToString(nCount)); + if(DEBUG) DoDebug("prc_inc_core >> PRCGetHasSpell: NewSpellbookMem_" + IntToString(nClass) + "[" + IntToString(j) + "] = " + IntToString(nCount)); if(nCount > 0) { nUses += nCount; @@ -615,7 +615,7 @@ int PRCGetHasSpell(int nRealSpellID, object oCreature = OBJECT_SELF) { nSpellLevel = StringToInt(Get2DACache(sFile, "Level", j)); nCount = persistant_array_get_int(oCreature, "NewSpellbookMem_" + IntToString(nClass), nSpellLevel); - if(DEBUG) DoDebug("PRCGetHasSpell: NewSpellbookMem_" + IntToString(nClass) + "[" + IntToString(j) + "] = " + IntToString(nCount)); + if(DEBUG) DoDebug("prc_inc_core >> PRCGetHasSpell: NewSpellbookMem_" + IntToString(nClass) + "[" + IntToString(j) + "] = " + IntToString(nCount)); if(nCount > 0) { nUses += nCount; diff --git a/src/include/prc_inc_fork.nss b/src/include/prc_inc_fork.nss index 3e46b83..2b19f97 100644 --- a/src/include/prc_inc_fork.nss +++ b/src/include/prc_inc_fork.nss @@ -248,6 +248,7 @@ int GetFocusFeatOfWeaponType(int iWeaponType) case BASE_ITEM_LONGSWORD: return FEAT_WEAPON_FOCUS_LONG_SWORD; case BASE_ITEM_MORNINGSTAR: return FEAT_WEAPON_FOCUS_MORNING_STAR; case BASE_ITEM_QUARTERSTAFF: return FEAT_WEAPON_FOCUS_STAFF; + case BASE_ITEM_MAGICSTAFF: return FEAT_WEAPON_FOCUS_STAFF; case BASE_ITEM_RAPIER: return FEAT_WEAPON_FOCUS_RAPIER; case BASE_ITEM_SCIMITAR: return FEAT_WEAPON_FOCUS_SCIMITAR; case BASE_ITEM_SCYTHE: return FEAT_WEAPON_FOCUS_SCYTHE; @@ -318,6 +319,7 @@ int GetSpecializationFeatOfWeaponType(int iWeaponType) case BASE_ITEM_LONGSWORD: return FEAT_WEAPON_SPECIALIZATION_LONG_SWORD; case BASE_ITEM_MORNINGSTAR: return FEAT_WEAPON_SPECIALIZATION_MORNING_STAR; case BASE_ITEM_QUARTERSTAFF: return FEAT_WEAPON_SPECIALIZATION_STAFF; + case BASE_ITEM_MAGICSTAFF: return FEAT_WEAPON_SPECIALIZATION_STAFF; case BASE_ITEM_RAPIER: return FEAT_WEAPON_SPECIALIZATION_RAPIER; case BASE_ITEM_SCIMITAR: return FEAT_WEAPON_SPECIALIZATION_SCIMITAR; case BASE_ITEM_SCYTHE: return FEAT_WEAPON_SPECIALIZATION_SCYTHE; @@ -388,6 +390,7 @@ int GetEpicFocusFeatOfWeaponType(int iWeaponType) case BASE_ITEM_LONGSWORD: return FEAT_EPIC_WEAPON_FOCUS_LONGSWORD; case BASE_ITEM_MORNINGSTAR: return FEAT_EPIC_WEAPON_FOCUS_MORNINGSTAR; case BASE_ITEM_QUARTERSTAFF: return FEAT_EPIC_WEAPON_FOCUS_QUARTERSTAFF; + case BASE_ITEM_MAGICSTAFF: return FEAT_EPIC_WEAPON_FOCUS_QUARTERSTAFF; case BASE_ITEM_RAPIER: return FEAT_EPIC_WEAPON_FOCUS_RAPIER; case BASE_ITEM_SCIMITAR: return FEAT_EPIC_WEAPON_FOCUS_SCIMITAR; case BASE_ITEM_SCYTHE: return FEAT_EPIC_WEAPON_FOCUS_SCYTHE; @@ -458,7 +461,8 @@ int GetEpicSpecializationFeatOfWeaponType(int iWeaponType) case BASE_ITEM_LONGSWORD: return FEAT_EPIC_WEAPON_SPECIALIZATION_LONGSWORD; case BASE_ITEM_MORNINGSTAR: return FEAT_EPIC_WEAPON_SPECIALIZATION_MORNINGSTAR; case BASE_ITEM_QUARTERSTAFF: return FEAT_EPIC_WEAPON_SPECIALIZATION_QUARTERSTAFF; - case BASE_ITEM_RAPIER: return FEAT_EPIC_WEAPON_SPECIALIZATION_RAPIER; + case BASE_ITEM_MAGICSTAFF: return FEAT_EPIC_WEAPON_SPECIALIZATION_QUARTERSTAFF; + case BASE_ITEM_RAPIER: return FEAT_EPIC_WEAPON_SPECIALIZATION_RAPIER; case BASE_ITEM_SCIMITAR: return FEAT_EPIC_WEAPON_SPECIALIZATION_SCIMITAR; case BASE_ITEM_SCYTHE: return FEAT_EPIC_WEAPON_SPECIALIZATION_SCYTHE; case BASE_ITEM_SHORTBOW: return FEAT_EPIC_WEAPON_SPECIALIZATION_SHORTBOW; @@ -528,6 +532,7 @@ int GetImprovedCriticalFeatOfWeaponType(int iWeaponType) case BASE_ITEM_LONGSWORD: return FEAT_IMPROVED_CRITICAL_LONG_SWORD; case BASE_ITEM_MORNINGSTAR: return FEAT_IMPROVED_CRITICAL_MORNING_STAR; case BASE_ITEM_QUARTERSTAFF: return FEAT_IMPROVED_CRITICAL_STAFF; + case BASE_ITEM_MAGICSTAFF: return FEAT_IMPROVED_CRITICAL_STAFF; case BASE_ITEM_RAPIER: return FEAT_IMPROVED_CRITICAL_RAPIER; case BASE_ITEM_SCIMITAR: return FEAT_IMPROVED_CRITICAL_SCIMITAR; case BASE_ITEM_SCYTHE: return FEAT_IMPROVED_CRITICAL_SCYTHE; @@ -598,6 +603,7 @@ int GetOverwhelmingCriticalFeatOfWeaponType(int iWeaponType) case BASE_ITEM_LONGSWORD: return FEAT_EPIC_OVERWHELMING_CRITICAL_LONGSWORD; case BASE_ITEM_MORNINGSTAR: return FEAT_EPIC_OVERWHELMING_CRITICAL_MORNINGSTAR; case BASE_ITEM_QUARTERSTAFF: return FEAT_EPIC_OVERWHELMING_CRITICAL_QUARTERSTAFF; + case BASE_ITEM_MAGICSTAFF: return FEAT_EPIC_OVERWHELMING_CRITICAL_QUARTERSTAFF; case BASE_ITEM_RAPIER: return FEAT_EPIC_OVERWHELMING_CRITICAL_RAPIER; case BASE_ITEM_SCIMITAR: return FEAT_EPIC_OVERWHELMING_CRITICAL_SCIMITAR; case BASE_ITEM_SCYTHE: return FEAT_EPIC_OVERWHELMING_CRITICAL_SCYTHE; @@ -668,6 +674,7 @@ int GetDevastatingCriticalFeatOfWeaponType(int iWeaponType) case BASE_ITEM_LONGSWORD: return FEAT_EPIC_DEVASTATING_CRITICAL_LONGSWORD; case BASE_ITEM_MORNINGSTAR: return FEAT_EPIC_DEVASTATING_CRITICAL_MORNINGSTAR; case BASE_ITEM_QUARTERSTAFF: return FEAT_EPIC_DEVASTATING_CRITICAL_QUARTERSTAFF; + case BASE_ITEM_MAGICSTAFF: return FEAT_EPIC_DEVASTATING_CRITICAL_QUARTERSTAFF; case BASE_ITEM_RAPIER: return FEAT_EPIC_DEVASTATING_CRITICAL_RAPIER; case BASE_ITEM_SCIMITAR: return FEAT_EPIC_DEVASTATING_CRITICAL_SCIMITAR; case BASE_ITEM_SCYTHE: return FEAT_EPIC_DEVASTATING_CRITICAL_SCYTHE; @@ -729,6 +736,7 @@ int GetWeaponOfChoiceFeatOfWeaponType(int iWeaponType) case BASE_ITEM_LONGSWORD: return FEAT_WEAPON_OF_CHOICE_LONGSWORD; case BASE_ITEM_MORNINGSTAR: return FEAT_WEAPON_OF_CHOICE_MORNINGSTAR; case BASE_ITEM_QUARTERSTAFF: return FEAT_WEAPON_OF_CHOICE_QUARTERSTAFF; + case BASE_ITEM_MAGICSTAFF: return FEAT_WEAPON_OF_CHOICE_QUARTERSTAFF; case BASE_ITEM_RAPIER: return FEAT_WEAPON_OF_CHOICE_RAPIER; case BASE_ITEM_SCIMITAR: return FEAT_WEAPON_OF_CHOICE_SCIMITAR; case BASE_ITEM_SCYTHE: return FEAT_WEAPON_OF_CHOICE_SCYTHE; @@ -787,6 +795,7 @@ int GetWeaponSize(object oWeapon) case BASE_ITEM_GREATAXE: case BASE_ITEM_HEAVYFLAIL: case BASE_ITEM_QUARTERSTAFF: + //case BASE_ITEM_MAGICSTAFF: case BASE_ITEM_SCYTHE: case BASE_ITEM_SHORTSPEAR: case BASE_ITEM_ELVEN_COURTBLADE: @@ -823,6 +832,7 @@ int PRCLargeWeaponCheck(int iBaseType, int nSize) case BASE_ITEM_GREATAXE: case BASE_ITEM_HEAVYFLAIL: case BASE_ITEM_QUARTERSTAFF: + //case BASE_ITEM_MAGICSTAFF: case BASE_ITEM_SCYTHE: case BASE_ITEM_SHORTSPEAR: case BASE_ITEM_ELVEN_COURTBLADE: diff --git a/src/include/prc_inc_function.nss b/src/include/prc_inc_function.nss index 93f7aa1..aa47614 100644 --- a/src/include/prc_inc_function.nss +++ b/src/include/prc_inc_function.nss @@ -108,8 +108,8 @@ void SetupCharacterData(object oPC) case CLASS_TYPE_ALIENIST: sScript = "prc_alienist"; break; case CLASS_TYPE_ARCANE_DUELIST: sScript = "prc_arcduel"; break; case CLASS_TYPE_ARCHIVIST: sScript = "prc_archivist"; iData |= 0x01; break; - case CLASS_TYPE_ASSASSIN: iData |= 0x03; break; - case CLASS_TYPE_BAELNORN: sScript = "prc_baelnorn"; break; + case CLASS_TYPE_ASSASSIN: break; + //case CLASS_TYPE_BAELNORN: sScript = "prc_baelnorn"; break; case CLASS_TYPE_BARD: iData |= 0x07; break; case CLASS_TYPE_BATTLESMITH: sScript = "prc_battlesmith"; break; case CLASS_TYPE_BEGUILER: iData |= 0x03; break; @@ -121,7 +121,7 @@ void SetupCharacterData(object oPC) case CLASS_TYPE_BLIGHTLORD: sScript = "prc_blightlord"; break; case CLASS_TYPE_BLOODCLAW_MASTER: sScript = "tob_bloodclaw"; break; case CLASS_TYPE_BONDED_SUMMONNER: sScript = "prc_bondedsumm"; break; - case CLASS_TYPE_CELEBRANT_SHARESS: iData |= 0x03; break; + case CLASS_TYPE_CELEBRANT_SHARESS: iData |= 0x07; break; case CLASS_TYPE_CHILD_OF_NIGHT: sScript = "shd_childnight"; break; case CLASS_TYPE_COC: sScript = "prc_coc"; break; case CLASS_TYPE_COMBAT_MEDIC: sScript = "prc_cbtmed"; break; @@ -180,6 +180,7 @@ void SetupCharacterData(object oPC) case CLASS_TYPE_LASHER: sScript = "prc_lasher"; break; case CLASS_TYPE_LEGENDARY_DREADNOUGHT: sScript = "prc_legendread"; break; case CLASS_TYPE_LICH: sScript = "pnp_lich_level"; break; + case CLASS_TYPE_LION_OF_TALISID: sScript = "prc_lot"; break; case CLASS_TYPE_MAGEKILLER: sScript = "prc_magekill"; break; case CLASS_TYPE_MASTER_HARPER: sScript = "prc_masterh"; break; case CLASS_TYPE_MASTER_OF_NINE: sScript = "tob_masterofnine"; break; @@ -245,6 +246,7 @@ void SetupCharacterData(object oPC) case CLASS_TYPE_TOTEM_RAGER: sScript = "moi_totemrager"; break; case CLASS_TYPE_TRUENAMER: sScript = "true_truenamer"; iData |= 0x01; break; case CLASS_TYPE_VASSAL: sScript = "prc_vassal"; break; + case CLASS_TYPE_VERDANT_LORD: sScript = "prc_verdantlord"; break; case CLASS_TYPE_VIGILANT: sScript = "prc_vigilant"; break; case CLASS_TYPE_WARBLADE: sScript = "tob_warblade"; iData |= 0x01; break; case CLASS_TYPE_WARCHIEF: sScript = "prc_warchief"; break; @@ -2264,6 +2266,8 @@ void FeatSpecialUsePerDay(object oPC) FeatUsePerDay(oPC, FEAT_FM_FOREST_DOMINION, ABILITY_CHARISMA, 3); FeatUsePerDay(oPC, FEAT_SOD_DEATH_TOUCH, -1, (GetLevelByClass(CLASS_TYPE_SLAYER_OF_DOMIEL, oPC)+4)/4); FeatUsePerDay(oPC, FEAT_SUEL_DISPELLING_STRIKE, -1, (GetLevelByClass(CLASS_TYPE_SUEL_ARCHANAMACH, oPC) + 2) / 4); + FeatUsePerDay(oPC, FEAT_PLANT_CONTROL, ABILITY_CHARISMA, 3); + FeatUsePerDay(oPC, FEAT_PLANT_DEFIANCE, ABILITY_CHARISMA, 3); FeatDiabolist(oPC); FeatAlaghar(oPC); ShadowShieldUses(oPC); diff --git a/src/include/prc_inc_json.nss b/src/include/prc_inc_json.nss new file mode 100644 index 0000000..749c551 --- /dev/null +++ b/src/include/prc_inc_json.nss @@ -0,0 +1,750 @@ +//::////////////////////////////////////////////// +//:: ;-. ,-. ,-. ,-. +//:: | ) | ) / ( ) +//:: |-' |-< | ;-: +//:: | | \ \ ( ) +//:: ' ' ' `-' `-' +//::////////////////////////////////////////////// +//:: +/* + Library for json related functions. + +*/ +//:: +//::////////////////////////////////////////////// +//:: Script: prc_inc_json.nss +//:: Author: Jaysyn +//:: Created: 2025-08-14 12:52:32 +//::////////////////////////////////////////////// +#include "nw_inc_gff" +#include "inc_debug" + + +//::---------------------------------------------| +//:: Helper functions | +//::---------------------------------------------| + +//:: Function to calculate the maximum possible hitpoints for oCreature +int GetMaxPossibleHP(object oCreature) +{ + int nMaxHP = 0; // Stores the total maximum hitpoints + int i = 1; // Initialize position for class index + int nConb = GetAbilityModifier(ABILITY_CONSTITUTION, oCreature); + + // Loop through each class position the creature may have, checking each class in turn + while (TRUE) + { + // Get the class ID at position i + int nClassID = GetClassByPosition(i, oCreature); + + // If class is invalid (no more classes to check), break out of loop + if (nClassID == CLASS_TYPE_INVALID) + break; + + // Get the number of levels in this class + int nClassLevels = GetLevelByClass(nClassID, oCreature); + + // Get the row index of the class in classes.2da by using class ID as the row index + int nHitDie = StringToInt(Get2DAString("classes", "HitDie", nClassID)); + + // Add maximum HP for this class (Hit Die * number of levels in this class) + nMaxHP += nClassLevels * nHitDie; + + // Move to the next class position + i++; + } + + nMaxHP += nConb * GetHitDice(oCreature); + + return nMaxHP; +} + +// Returns how many feats a creature should gain when its HD increases +int CalculateFeatsFromHD(int nOriginalHD, int nNewHD) +{ + // HD increase + int nHDIncrease = nNewHD - nOriginalHD; + + if (nHDIncrease <= 0) + return 0; // No new feats if HD did not increase + + // D&D 3E: 1 feat per 3 HD + int nBonusFeats = nHDIncrease / 3; + + return nBonusFeats; +} + +// Returns how many stat boosts a creature needs based on its HD +int GetStatBoostsFromHD(int nCreatureHD, int nModiferCap) +{ + // Make sure we don't get negative boosts + int nBoosts = (40 - nCreatureHD) / 4; + if (nBoosts < 0) + { + nBoosts = 0; + } + return nBoosts; +} + +// Struct to hold size modifiers +struct SizeModifiers +{ + int strMod; + int dexMod; + int conMod; + int naturalAC; + int attackBonus; + int dexSkillMod; +}; + +//::---------------------------------------------| +//:: JSON functions | +//::---------------------------------------------| + +//:: Returns the integer value of a VarTable entry named sVarName, or 0 if not found. +int json_GetLocalIntFromVarTable(json jCreature, string sVarName) +{ + json jVarTable = GffGetList(jCreature, "VarTable"); + if (jVarTable == JsonNull()) + return 0; + + int nCount = JsonGetLength(jVarTable); + int i; + for (i = 0; i < nCount; i++) + { + json jEntry = JsonArrayGet(jVarTable, i); + if (jEntry == JsonNull()) continue; + + // Get the Name field using GFF functions + json jName = GffGetString(jEntry, "Name"); + if (jName == JsonNull()) continue; + string sName = JsonGetString(jName); + + if (sName == sVarName) + { + // Get the Type field to verify it's an integer + json jType = GffGetDword(jEntry, "Type"); + if (jType != JsonNull()) + { + int nType = JsonGetInt(jType); + if (nType == 1) // Type 1 = integer + { + // Get the Value field using GFF functions + json jValue = GffGetInt(jEntry, "Value"); + if (jValue == JsonNull()) return 0; + return JsonGetInt(jValue); + } + } + } + } + + return 0; +} + +//:: Returns the total Hit Dice from a JSON creature GFF. +int json_GetCreatureHD(json jGff) +{ + int nHD = 0; + + json jClasses = GffGetList(jGff, "ClassList"); + if (jClasses == JsonNull()) + return 0; + + int nCount = JsonGetLength(jClasses); + int i; + for (i = 0; i < nCount; i = i + 1) + { + json jClass = JsonArrayGet(jClasses, i); + if (jClass == JsonNull()) + continue; + + json jLevel = GffGetShort(jClass, "ClassLevel"); // Use GffGetShort, not GffGetField + if (jLevel != JsonNull()) + { + int nLevel = JsonGetInt(jLevel); + nHD += nLevel; + } + } + + if (nHD <= 0) nHD = 1; + return nHD; +} + +//:: Reads ABILITY_TO_INCREASE from creature's VarTable and applies stat boosts based on increased HD +json json_ApplyAbilityBoostFromHD(json jCreature, int nOriginalHD, int nModifierCap) +{ + if (jCreature == JsonNull()) + return jCreature; + + // Get the ability to increase from VarTable + int nAbilityToIncrease = json_GetLocalIntFromVarTable(jCreature, "ABILITY_TO_INCREASE"); + if (nAbilityToIncrease < 0 || nAbilityToIncrease > 5) + { + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Invalid ABILITY_TO_INCREASE value: " + IntToString(nAbilityToIncrease)); + return jCreature; // Invalid ability index + } + + // Calculate total current HD from ClassList + json jClassList = GffGetList(jCreature, "ClassList"); + if (jClassList == JsonNull()) + { + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Failed to get ClassList"); + return jCreature; + } + + int nCurrentTotalHD = 0; + int nClassCount = JsonGetLength(jClassList); + int i; + + for (i = 0; i < nClassCount; i++) + { + json jClass = JsonArrayGet(jClassList, i); + if (jClass != JsonNull()) + { + json jClassLevel = GffGetShort(jClass, "ClassLevel"); + if (jClassLevel != JsonNull()) + { + nCurrentTotalHD += JsonGetInt(jClassLevel); + } + } + } + + if (nCurrentTotalHD <= 0) + { + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: No valid Hit Dice found"); + return jCreature; + } + + // Calculate stat boosts based on crossing level thresholds + // Characters get stat boosts at levels 4, 8, 12, 16, 20, etc. + int nOriginalBoosts = nOriginalHD / 4; // How many boosts they already had + int nCurrentBoosts = nCurrentTotalHD / 4; // How many they should have now + int nBoosts = nCurrentBoosts - nOriginalBoosts; // Additional boosts to apply + + if (nBoosts <= 0) + { + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: No boosts needed (Original boosts: " + IntToString(nOriginalBoosts) + ", Current boosts: " + IntToString(nCurrentBoosts) + ")"); + return jCreature; + } + + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Applying " + IntToString(nBoosts) + " boosts to ability " + IntToString(nAbilityToIncrease) + " for HD increase from " + IntToString(nOriginalHD) + " to " + IntToString(nCurrentTotalHD)); + + // Determine which ability to boost and apply the increases + string sAbilityField; + switch (nAbilityToIncrease) + { + case 0: sAbilityField = "Str"; break; + case 1: sAbilityField = "Dex"; break; + case 2: sAbilityField = "Con"; break; + case 3: sAbilityField = "Int"; break; + case 4: sAbilityField = "Wis"; break; + case 5: sAbilityField = "Cha"; break; + default: + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Unknown ability index: " + IntToString(nAbilityToIncrease)); + return jCreature; + } + + // Get current ability score + json jCurrentAbility = GffGetByte(jCreature, sAbilityField); + if (jCurrentAbility == JsonNull()) + { + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Failed to get " + sAbilityField + " score"); + return jCreature; + } + + int nCurrentScore = JsonGetInt(jCurrentAbility); + int nNewScore = nCurrentScore + nBoosts; + + // Clamp to valid byte range + if (nNewScore < 1) nNewScore = 1; + if (nNewScore > 255) nNewScore = 255; + + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Increasing " + sAbilityField + " from " + IntToString(nCurrentScore) + " to " + IntToString(nNewScore)); + + // Apply the ability score increase + jCreature = GffReplaceByte(jCreature, sAbilityField, nNewScore); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Failed to update " + sAbilityField); + return JsonNull(); + } + + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Successfully applied ability boosts"); + return jCreature; +} + +//:: Adjust a skill by its ID (more efficient than name lookup) +json json_AdjustCreatureSkillByID(json jCreature, int nSkillID, int nMod) +{ + // Get the SkillList + json jSkillList = GffGetList(jCreature, "SkillList"); + if (jSkillList == JsonNull()) + { + if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to get SkillList"); + return jCreature; + } + + // Check if we have enough skills in the list + int nSkillCount = JsonGetLength(jSkillList); + if (nSkillID >= nSkillCount) + { + if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Skill ID " + IntToString(nSkillID) + " exceeds skill list length " + IntToString(nSkillCount)); + return jCreature; + } + + // Get the skill struct at the correct index + json jSkill = JsonArrayGet(jSkillList, nSkillID); + if (jSkill == JsonNull()) + { + if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to get skill at index " + IntToString(nSkillID)); + return jCreature; + } + + // Get current rank + json jRank = GffGetByte(jSkill, "Rank"); + if (jRank == JsonNull()) + { + if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to get Rank for skill ID " + IntToString(nSkillID)); + return jCreature; + } + + int nCurrentRank = JsonGetInt(jRank); + int nNewRank = nCurrentRank + nMod; + + // Clamp to valid range + if (nNewRank < 0) nNewRank = 0; + if (nNewRank > 255) nNewRank = 255; + + // Update the rank in the skill struct + jSkill = GffReplaceByte(jSkill, "Rank", nNewRank); + if (jSkill == JsonNull()) + { + if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to replace Rank for skill ID " + IntToString(nSkillID)); + return JsonNull(); + } + + // Replace the skill in the array + jSkillList = JsonArraySet(jSkillList, nSkillID, jSkill); + + // Replace the SkillList in the creature + jCreature = GffReplaceList(jCreature, "SkillList", jSkillList); + + return jCreature; +} + +//:: Reads FutureFeat1..FutureFeatN from the template's VarTable and appends them to FeatList if missing. +json json_AddFeatsFromCreatureVars(json jCreature, int nOriginalHD) +{ + if (jCreature == JsonNull()) + return jCreature; + + // Calculate current total HD + int nCurrentHD = json_GetCreatureHD(jCreature); + int nAddedHD = nCurrentHD - nOriginalHD; + + if (nAddedHD <= 0) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: No additional HD to process (Current: " + IntToString(nCurrentHD) + ", Original: " + IntToString(nOriginalHD) + ")"); + return jCreature; + } + + // Calculate how many feats the creature should get based on added HD + // Characters get a feat at levels 1, 3, 6, 9, 12, 15, 18, etc. + // For added levels, we need to check what feat levels they cross + int nOriginalFeats = (nOriginalHD + 2) / 3; // Feats from original HD + int nCurrentFeats = (nCurrentHD + 2) / 3; // Feats from current HD + int nNumFeats = nCurrentFeats - nOriginalFeats; // Additional feats earned + + if (nNumFeats <= 0) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: No additional feats earned from " + IntToString(nAddedHD) + " added HD"); + return jCreature; + } + + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Processing " + IntToString(nNumFeats) + " feats for " + IntToString(nAddedHD) + " added HD (Original: " + IntToString(nOriginalHD) + ", Current: " + IntToString(nCurrentHD) + ")"); + + // Get or create FeatList + json jFeatArray = GffGetList(jCreature, "FeatList"); + if (jFeatArray == JsonNull()) + jFeatArray = JsonArray(); + + int nOriginalFeatCount = JsonGetLength(jFeatArray); + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Original feat count: " + IntToString(nOriginalFeatCount)); + + int nAdded = 0; + int i = 1; + int nMaxIterations = 100; // Safety valve + int nIterations = 0; + + while (nAdded < nNumFeats && nIterations < nMaxIterations) + { + nIterations++; + string sVarName = "FutureFeat" + IntToString(i); + + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Checking " + sVarName); + + int nFeat = json_GetLocalIntFromVarTable(jCreature, sVarName); + + if (nFeat <= 0) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: " + sVarName + " not found or invalid"); + i++; + continue; + } + + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Found " + sVarName + " = " + IntToString(nFeat)); + + // Check if feat already exists + int bHasFeat = FALSE; + int nFeatCount = JsonGetLength(jFeatArray); + int j; + + for (j = 0; j < nFeatCount; j++) + { + json jFeatStruct = JsonArrayGet(jFeatArray, j); + if (jFeatStruct != JsonNull()) + { + json jFeatValue = GffGetWord(jFeatStruct, "Feat"); + if (jFeatValue != JsonNull() && JsonGetInt(jFeatValue) == nFeat) + { + bHasFeat = TRUE; + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Feat " + IntToString(nFeat) + " already exists"); + break; + } + } + } + + // Insert if missing + if (!bHasFeat) + { + json jNewFeat = JsonObject(); + jNewFeat = JsonObjectSet(jNewFeat, "__struct_id", JsonInt(1)); + jNewFeat = GffAddWord(jNewFeat, "Feat", nFeat); + + if (jNewFeat == JsonNull()) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Failed to create feat struct for feat " + IntToString(nFeat)); + break; + } + + jFeatArray = JsonArrayInsert(jFeatArray, jNewFeat); + nAdded++; + + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Added feat " + IntToString(nFeat) + " (" + IntToString(nAdded) + "/" + IntToString(nNumFeats) + ")"); + } + + i++; + + // Safety break if we've checked too many variables + if (i > 100) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Safety break - checked too many FutureFeat variables"); + break; + } + } + + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Completed. Added " + IntToString(nAdded) + " feats in " + IntToString(nIterations) + " iterations"); + + // Save back the modified FeatList only if we added something + if (nAdded > 0) + { + jCreature = GffReplaceList(jCreature, "FeatList", jFeatArray); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Failed to replace FeatList"); + return JsonNull(); + } + } + + return jCreature; +} + +//:: Get the size of a JSON array +int json_GetArraySize(json jArray) +{ + int iSize = 0; + while (JsonArrayGet(jArray, iSize) != JsonNull()) + { + iSize++; + } + return iSize; +} + +//:: Directly modifies oCreature's Base Natural AC if iNewAC is higher. +//:: +json json_UpdateBaseAC(json jCreature, int iNewAC) +{ + //json jBaseAC = GffGetByte(jCreature, "Creature/value/NaturalAC/value"); + json jBaseAC = GffGetByte(jCreature, "NaturalAC"); + + if (jBaseAC == JsonNull()) + { + return JsonNull(); + } + else if (JsonGetInt(jBaseAC) > iNewAC) + { + return jCreature; + } + else + { + jCreature = GffReplaceByte(jCreature, "NaturalAC", iNewAC); + + return jCreature; + } +} + +//:: Directly modifies jCreature's Challenge Rating. +//:: This is useful for most XP calculations. +json json_UpdateCR(json jCreature, int nBaseCR, int nCRMod) +{ + int nNewCR; + +//:: Add CRMod to current CR + nNewCR = nBaseCR + nCRMod; + +//:: Modify Challenge Rating + jCreature = GffReplaceFloat(jCreature, "ChallengeRating", IntToFloat(nNewCR)); + + return jCreature; +} + +//:: Directly modifies ability scores in a creature's JSON GFF. +//:: +json json_UpdateTemplateStats(json jCreature, int iModStr = 0, int iModDex = 0, int iModCon = 0, + int iModInt = 0, int iModWis = 0, int iModCha = 0) +{ + int iCurrent; + + // STR + if (!GffGetFieldExists(jCreature, "Str", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Str", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Str")); + jCreature = GffReplaceByte(jCreature, "Str", iCurrent + iModStr); + + // DEX + if (!GffGetFieldExists(jCreature, "Dex", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Dex", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Dex")); + jCreature = GffReplaceByte(jCreature, "Dex", iCurrent + iModDex); + + // CON + if (!GffGetFieldExists(jCreature, "Con", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Con", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Con")); + jCreature = GffReplaceByte(jCreature, "Con", iCurrent + iModCon); + + // INT + if (!GffGetFieldExists(jCreature, "Int", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Int", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Int")); + jCreature = GffReplaceByte(jCreature, "Int", iCurrent + iModInt); + + // WIS + if (!GffGetFieldExists(jCreature, "Wis", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Wis", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Wis")); + jCreature = GffReplaceByte(jCreature, "Wis", iCurrent + iModWis); + + // CHA + if (!GffGetFieldExists(jCreature, "Cha", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Cha", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Cha")); + jCreature = GffReplaceByte(jCreature, "Cha", iCurrent + iModCha); + + return jCreature; +} + +//:: Directly modifies oCreature's ability scores. +//:: +json json_UpdateCreatureStats(json jCreature, object oBaseCreature, int iModStr = 0, int iModDex = 0, int iModCon = 0, int iModInt = 0, int iModWis = 0, int iModCha = 0) +{ +//:: Retrieve and modify ability scores + int iCurrentStr = GetAbilityScore(oBaseCreature, ABILITY_STRENGTH); + int iCurrentDex = GetAbilityScore(oBaseCreature, ABILITY_DEXTERITY); + int iCurrentCon = GetAbilityScore(oBaseCreature, ABILITY_CONSTITUTION); + int iCurrentInt = GetAbilityScore(oBaseCreature, ABILITY_INTELLIGENCE); + int iCurrentWis = GetAbilityScore(oBaseCreature, ABILITY_WISDOM); + int iCurrentCha = GetAbilityScore(oBaseCreature, ABILITY_CHARISMA); + + jCreature = GffReplaceByte(jCreature, "Str", iCurrentStr + iModStr); + jCreature = GffReplaceByte(jCreature, "Dex", iCurrentDex + iModDex); + jCreature = GffReplaceByte(jCreature, "Con", iCurrentCon + iModCon); + jCreature = GffReplaceByte(jCreature, "Int", iCurrentInt + iModInt); + jCreature = GffReplaceByte(jCreature, "Wis", iCurrentWis + iModWis); + jCreature = GffReplaceByte(jCreature, "Cha", iCurrentCha + iModCha); + + return jCreature; +} + +//:: Increases a creature's Hit Dice in its JSON GFF data by nAmount +json json_AddHitDice(json jCreature, int nAmount) +{ + if (jCreature == JsonNull() || nAmount <= 0) + return jCreature; + + // Get the ClassList + json jClasses = GffGetList(jCreature, "ClassList"); + if (jClasses == JsonNull() || JsonGetLength(jClasses) == 0) + return jCreature; + + // Grab the first class entry + json jFirstClass = JsonArrayGet(jClasses, 0); + + // Only touch ClassLevel; do NOT modify Class type + json jCurrentLevel = GffGetShort(jFirstClass, "ClassLevel"); + int nCurrentLevel = JsonGetInt(jCurrentLevel); + int nNewLevel = nCurrentLevel + nAmount; + + // Replace ClassLevel only + jFirstClass = GffReplaceShort(jFirstClass, "ClassLevel", nNewLevel); + + // Put modified class back into the array + jClasses = JsonArraySet(jClasses, 0, jFirstClass); + + // Replace ClassList in the creature JSON + jCreature = GffReplaceList(jCreature, "ClassList", jClasses); + + return jCreature; +} + +//:: Adjusts a creature's size by nSizeChange (-4 to +4) and updates ability scores accordingly. +json json_AdjustCreatureSize(json jCreature, int nSizeDelta) +{ + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Entering function. nSizeDelta=" + IntToString(nSizeDelta)); + + if (jCreature == JsonNull() || nSizeDelta == 0) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Exiting: jCreature is null or nSizeDelta is 0"); + return jCreature; + } + + // Get Appearance_Type using GFF functions + json jAppearanceType = GffGetWord(jCreature, "Appearance_Type"); + if (jAppearanceType == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to get Appearance_Type"); + return jCreature; + } + + int nAppearance = JsonGetInt(jAppearanceType); + int nCurrentSize = StringToInt(Get2DAString("appearances", "Size", nAppearance)); + + // Default to Medium (4) if invalid + if (nCurrentSize < 0 || nCurrentSize > 8) nCurrentSize = 4; + + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Appearance_Type =" + IntToString(nAppearance) + ", Size =" + IntToString(nCurrentSize)); + + int nSteps = nSizeDelta; + + // Calculate modifiers based on size change + int strMod = nSteps * 4; + int dexMod = nSteps * -1; + int conMod = nSteps * 2; + int naturalAC = nSteps * 1; + int dexSkillMod = nSteps * -2; + + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Applying stat modifiers: STR=" + IntToString(strMod) + + " DEX=" + IntToString(dexMod) + + " CON=" + IntToString(conMod)); + + // Update ability scores using GFF functions with error checking + json jStr = GffGetByte(jCreature, "Str"); + if (jStr != JsonNull()) + { + int nNewStr = JsonGetInt(jStr) + strMod; + if (nNewStr < 1) nNewStr = 1; + if (nNewStr > 255) nNewStr = 255; + + jCreature = GffReplaceByte(jCreature, "Str", nNewStr); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update Str"); + return JsonNull(); + } + } + + json jDex = GffGetByte(jCreature, "Dex"); + if (jDex != JsonNull()) + { + int nNewDex = JsonGetInt(jDex) + dexMod; + if (nNewDex < 1) nNewDex = 1; + if (nNewDex > 255) nNewDex = 255; + + jCreature = GffReplaceByte(jCreature, "Dex", nNewDex); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update Dex"); + return JsonNull(); + } + } + + json jCon = GffGetByte(jCreature, "Con"); + if (jCon != JsonNull()) + { + int nNewCon = JsonGetInt(jCon) + conMod; + if (nNewCon < 1) nNewCon = 1; + if (nNewCon > 255) nNewCon = 255; + + jCreature = GffReplaceByte(jCreature, "Con", nNewCon); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update Con"); + return JsonNull(); + } + } + + // Update Natural AC + json jNaturalAC = GffGetByte(jCreature, "NaturalAC"); + if (jNaturalAC != JsonNull()) + { + int nCurrentNA = JsonGetInt(jNaturalAC); + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Current NaturalAC: " + IntToString(nCurrentNA)); + + int nNewNA = nCurrentNA + naturalAC; + if (nNewNA < 0) nNewNA = 0; + if (nNewNA > 255) nNewNA = 255; + + jCreature = GffReplaceByte(jCreature, "NaturalAC", nNewNA); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update NaturalAC"); + return JsonNull(); + } + } + + // Adjust all Dexterity-based skills by finding them in skills.2da + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Adjusting DEX-based skills"); + + int nSkillID = 0; + while (TRUE) + { + string sKeyAbility = Get2DAString("skills", "KeyAbility", nSkillID); + + // Break when we've reached the end of skills + if (sKeyAbility == "") + break; + + // If this skill uses Dexterity, adjust it + if (sKeyAbility == "DEX") + { + string sSkillLabel = Get2DAString("skills", "Label", nSkillID); + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Adjusting DEX skill: " + sSkillLabel + " (ID: " + IntToString(nSkillID) + ")"); + + jCreature = json_AdjustCreatureSkillByID(jCreature, nSkillID, dexSkillMod); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed adjusting skill ID " + IntToString(nSkillID)); + return JsonNull(); + } + } + + nSkillID++; + } + + if(DEBUG) DoDebug("json_AdjustCreatureSize completed successfully"); + return jCreature; +} + + +//:: Test void +//:: void main (){} \ No newline at end of file diff --git a/src/include/prc_inc_material.nss b/src/include/prc_inc_material.nss index f69e26d..dc050aa 100644 --- a/src/include/prc_inc_material.nss +++ b/src/include/prc_inc_material.nss @@ -16,26 +16,28 @@ const int MATERIAL_TYPE_UNKNOWN = 0; const int MATERIAL_TYPE_BONE = 1; const int MATERIAL_TYPE_CERAMIC = 2; const int MATERIAL_TYPE_CRYSTAL = 3; -const int MATERIAL_TYPE_FABRIC = 4; +const int MATERIAL_TYPE_FIBER = 4; const int MATERIAL_TYPE_LEATHER = 5; const int MATERIAL_TYPE_METAL = 6; const int MATERIAL_TYPE_PAPER = 7; const int MATERIAL_TYPE_ROPE = 8; const int MATERIAL_TYPE_STONE = 9; const int MATERIAL_TYPE_WOOD = 10; +const int MATERIAL_TYPE_BOTANICAL = 11; const string MATERIAL_TYPE_NAME_INVALID = ""; const string MATERIAL_TYPE_NAME_UNKNOWN = "Unknown"; const string MATERIAL_TYPE_NAME_BONE = "Bone"; const string MATERIAL_TYPE_NAME_CERAMIC = "Ceramic"; const string MATERIAL_TYPE_NAME_CRYSTAL = "Crystal"; -const string MATERIAL_TYPE_NAME_FABRIC = "Fabric"; +const string MATERIAL_TYPE_NAME_FIBER = "Fiber"; const string MATERIAL_TYPE_NAME_LEATHER = "Leather"; const string MATERIAL_TYPE_NAME_METAL = "Metal"; const string MATERIAL_TYPE_NAME_PAPER = "Paper"; const string MATERIAL_TYPE_NAME_ROPE = "Rope"; const string MATERIAL_TYPE_NAME_STONE = "Stone"; const string MATERIAL_TYPE_NAME_WOOD = "Wood"; +const string MATERIAL_TYPE_NAME_BOTANICAL = "Bontanical"; //:: Material Itemproperty Constants //:://////////////////////////////////////////////////////////////////////////////// @@ -163,7 +165,8 @@ const int IP_MATERIAL_OBSIDIAN = 140; const int IP_MATERIAL_BAMBOO = 141; const int IP_MATERIAL_POTTERY = 142; const int IP_MATERIAL_GLASSTEEL = 143; -const int IP_NUM_MATERIALS = 143; +const int IP_MATERIAL_HERB = 144; +const int IP_NUM_MATERIALS = 144; const string IP_MATERIAL_NAME_INVALID = ""; const string IP_MATERIAL_NAME_UNKNOWN = "Unknown"; @@ -288,6 +291,7 @@ const string IP_MATERIAL_NAME_OBSIDIAN = "Obsidian"; const string IP_MATERIAL_NAME_BAMBOO = "Bamboo"; const string IP_MATERIAL_NAME_POTTERY = "Pottery"; const string IP_MATERIAL_NAME_GLASSTEEL = "Glassteel"; +const string IP_MATERIAL_NAME_HERB = "Herbs"; //:://///////////////////////////////////////////////////////////// // GetMaterialName( int iMaterialType, int bLowerCase = FALSE) @@ -428,6 +432,7 @@ string GetMaterialName( int iMaterialType, int bLowerCase = FALSE) case IP_MATERIAL_BAMBOO: sName = IP_MATERIAL_NAME_BAMBOO; break; case IP_MATERIAL_POTTERY: sName = IP_MATERIAL_NAME_POTTERY; break; case IP_MATERIAL_GLASSTEEL: sName = IP_MATERIAL_NAME_GLASSTEEL; break; + case IP_MATERIAL_HERB: sName = IP_MATERIAL_NAME_HERB; break; default: return ""; } @@ -573,6 +578,7 @@ int GetIPMaterial( string sMaterialName) else if( sMaterialName == GetStringUpperCase(IP_MATERIAL_NAME_BAMBOO)) return IP_MATERIAL_BAMBOO; else if( sMaterialName == GetStringUpperCase(IP_MATERIAL_NAME_POTTERY)) return IP_MATERIAL_POTTERY; else if( sMaterialName == GetStringUpperCase(IP_MATERIAL_NAME_GLASSTEEL)) return IP_MATERIAL_GLASSTEEL; + else if( sMaterialName == GetStringUpperCase(IP_MATERIAL_NAME_HERB)) return IP_MATERIAL_HERB; return IP_MATERIAL_INVALID; } @@ -806,6 +812,9 @@ int GetMaterialType(int nMaterial) || nMaterial == IP_MATERIAL_DRAKE_IVORY ) return MATERIAL_TYPE_BONE; + else if ( nMaterial == IP_MATERIAL_HERB ) + return MATERIAL_TYPE_BOTANICAL; + else if ( nMaterial == IP_MATERIAL_ELUKIAN_CLAY || nMaterial == IP_MATERIAL_POTTERY ) return MATERIAL_TYPE_CERAMIC; @@ -814,7 +823,7 @@ int GetMaterialType(int nMaterial) || nMaterial == IP_MATERIAL_COTTON || nMaterial == IP_MATERIAL_SILK || nMaterial == IP_MATERIAL_WOOL ) - return MATERIAL_TYPE_FABRIC; + return MATERIAL_TYPE_FIBER; else if ( nMaterial == IP_MATERIAL_GEM || nMaterial == IP_MATERIAL_GEM_ALEXANDRITE diff --git a/src/include/prc_inc_onhit.nss b/src/include/prc_inc_onhit.nss index 3f0da67..ab68887 100644 --- a/src/include/prc_inc_onhit.nss +++ b/src/include/prc_inc_onhit.nss @@ -808,7 +808,24 @@ int GetIsOnHitCastSpell(object oSpellTarget = OBJECT_INVALID, object oSpellCastI if (DEBUG) DoDebug("GetIsOnHitCastSpell: item "+GetName(oSpellCastItem)+" is armor; attacker = "+GetName(oAttacker)+", defender = "+GetName(oDefender)); } // is the spell type item a weapon? - else if (iWeaponType == StringToInt(Get2DACache("baseitems", "WeaponType", iBaseType))) + int nWT = StringToInt(Get2DACache("baseitems", "WeaponType", iBaseType)); + if (nWT > 0) + { + if (oSpellTarget == OBJECT_INVALID) + oSpellTarget = PRCGetSpellTargetObject(oSpellOrigin); + + oAttacker = oSpellOrigin; + oDefender = oSpellTarget; + + if (DEBUG) DoDebug("GetIsOnHitCastSpell: item "+GetName(oSpellCastItem)+" is weapon [WT="+IntToString(nWT)+"]; attacker="+GetName(oAttacker)+", defender="+GetName(oDefender)); + } + else + { + if (DEBUG) DoDebug("GetIsOnHitCastSpell: item "+GetName(oSpellCastItem)+" is neither weapon nor armor; returning FALSE"); + return FALSE; + } + +/* else if (iWeaponType == StringToInt(Get2DACache("baseitems", "WeaponType", iBaseType))) { // determine the target, if not already given if (oSpellTarget == OBJECT_INVALID) @@ -823,7 +840,7 @@ int GetIsOnHitCastSpell(object oSpellTarget = OBJECT_INVALID, object oSpellCastI { if (DEBUG) DoDebug("GetIsOnHitCastSpell: item "+GetName(oSpellCastItem)+" is neither weapon nor armor; returning FALSE"); return FALSE; - } + } */ // the spell origin must possess the item that cast the spell (at least for the aurora engine, in prc_inc_combat that may differ) diff --git a/src/include/prc_inc_skills.nss b/src/include/prc_inc_skills.nss index bbca299..0674604 100644 --- a/src/include/prc_inc_skills.nss +++ b/src/include/prc_inc_skills.nss @@ -115,11 +115,11 @@ int PerformJump(object oPC, location lLoc, int bDoKnockDown = TRUE) iBonus = 4; } } - /*if (GetHasSpellEffect(MOVE_TC_LEAPING_DRAGON, oPC)) + if (GetHasSpellEffect(MOVE_TC_LEAPING_DRAGON, oPC)) { bIsRunningJump = TRUE; - iBonus = 10; - } */ + //iBonus = 10; //:: This is granted in the stance. + } // PnP rules are height * 6 for run and height * 2 for jump. // I can't get height so that is assumed to be 6. // Changed maxed jump distance because the NwN distance is rather short @@ -374,6 +374,12 @@ int PRCIsFlying(object oCreature) if(GetRacialType(oCreature) == RACIAL_TYPE_GLOURA) bFlying = TRUE; + + if(GetRacialType(oCreature) == RACIAL_TYPE_AVARIEL) + bFlying = TRUE; + + if(GetRacialType(oCreature) == RACIAL_TYPE_FEYRI) + bFlying = TRUE; if(GetRacialType(oCreature) == RACIAL_TYPE_SPIRETOPDRAGON) bFlying = TRUE; diff --git a/src/include/prc_inc_spells.nss b/src/include/prc_inc_spells.nss index 19e73d7..18c0efa 100644 --- a/src/include/prc_inc_spells.nss +++ b/src/include/prc_inc_spells.nss @@ -20,6 +20,11 @@ /* Function prototypes */ ////////////////////////////////////////////////// + + +//:: Calculates total Shield AC bonuses from all sources +int GetTotalShieldACBonus(object oCreature); + //:: Handles psuedo-Foritifcation void DoFortification(object oPC = OBJECT_SELF, int nFortification = 25); @@ -376,23 +381,53 @@ const int TYPE_DIVINE = -2; /* Function definitions */ ////////////////////////////////////////////////// + +// Returns TRUE if nSpellID is a subradial spell, FALSE otherwise +int GetIsSubradialSpell(int nSpellID) +{ + string sMaster = Get2DACache("spells", "Master", nSpellID); + + // If the Master column is numeric, this spell is a subradial of that master + if (sMaster != "" && sMaster != "****") + { + return TRUE; + } + + return FALSE; +} + +// Returns the masterspell SpellID for a subradial spell. +int GetMasterSpellFromSubradial(int nSpellID) +{ + string sMaster = Get2DAString("spells", "Master", nSpellID); + + if (sMaster != "****") + { + return StringToInt(sMaster); + } + + return -1; // No master +} + + + int GetPrCAdjustedClassLevel(int nClass, object oCaster = OBJECT_SELF) { int iTemp; // is it arcane, divine or neither? if(GetIsArcaneClass(nClass, oCaster) && nClass != CLASS_TYPE_SUBLIME_CHORD) { - if (GetPrimaryArcaneClass(oCaster) == nClass) // adjust for any PrCs + if (GetPrimaryArcaneClass(oCaster) == nClass) // adjust for any PrCs iTemp = GetArcanePRCLevels(oCaster, nClass); } else if(GetIsDivineClass(nClass, oCaster)) { - if (GetPrimaryDivineClass(oCaster) == nClass) // adjust for any PrCs - iTemp = GetDivinePRCLevels(oCaster, nClass); + if (GetPrimaryDivineClass(oCaster) == nClass) // adjust for any PrCs + iTemp = GetDivinePRCLevels(oCaster, nClass); } else // a non-caster class or a PrC { - return 0; + return 0; } // add the caster class levels return iTemp += GetLevelByClass(nClass, oCaster); @@ -412,7 +447,9 @@ int GetPrCAdjustedCasterLevelByType(int nClassType, object oCaster = OBJECT_SELF { int nClassLvl; int nClass1, nClass2, nClass3, nClass4, nClass5, nClass6, nClass7, nClass8; - int nClass1Lvl, nClass2Lvl, nClass3Lvl, nClass4Lvl, nClass5Lvl, nClass6Lvl, nClass7Lvl, nClass8Lvl; + int nClass1Lvl = 0, nClass2Lvl = 0, nClass3Lvl = 0, nClass4Lvl = 0, + nClass5Lvl = 0, nClass6Lvl = 0, nClass7Lvl = 0, nClass8Lvl = 0; + nClass1 = GetClassByPosition(1, oCaster); nClass2 = GetClassByPosition(2, oCaster); @@ -2223,6 +2260,78 @@ int GetControlledCelestialTotalHD(object oPC = OBJECT_SELF) return nTotalHD; } +//:: Calculates total Shield AC bonuses from all sources +int GetTotalShieldACBonus(object oCreature) +{ + int nShieldBonus = 0; + object oItem; + + // Check left hand for shield + oItem = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oCreature); + if (GetIsObjectValid(oItem)) + { + int nBaseItem = GetBaseItemType(oItem); + if (nBaseItem == BASE_ITEM_SMALLSHIELD || + nBaseItem == BASE_ITEM_LARGESHIELD || + nBaseItem == BASE_ITEM_TOWERSHIELD) + { + nShieldBonus += GetItemACValue(oItem); + if(DEBUG) DoDebug("prc_inc_spells >> GetTotalShieldACBonus: Found Shield AC, bonus = " + IntToString(nShieldBonus)+"."); + } + } + + // Check creature weapon slots for shield AC bonus + oItem = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L, oCreature); + if (GetIsObjectValid(oItem)) + nShieldBonus += GetItemACValue(oItem); + + oItem = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R, oCreature); + if (GetIsObjectValid(oItem)) + nShieldBonus += GetItemACValue(oItem); + + oItem = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B, oCreature); + if (GetIsObjectValid(oItem)) + nShieldBonus += GetItemACValue(oItem); + + // Add shield AC bonuses from magical effects + effect eEffect = GetFirstEffect(oCreature); + while (GetIsEffectValid(eEffect)) + { + int nACType = GetEffectInteger(eEffect, 0); + int nACAmount = GetEffectInteger(eEffect, 1); + + if(GetEffectType(eEffect) == EFFECT_TYPE_AC_INCREASE && nACType == AC_SHIELD_ENCHANTMENT_BONUS) + { + if(DEBUG) DoDebug("prc_inc_spells >> GetTotalShieldACBonus: Found Shield AC effect, bonus = " + IntToString(nACAmount)+"."); + nShieldBonus += nACAmount; + } + + eEffect = GetNextEffect(oCreature); + } + return nShieldBonus; +} + + + + // Add shield AC bonuses from magical effects +/* effect eEffect = GetFirstEffect(oCreature); + while (GetIsEffectValid(eEffect)) + { + if (GetEffectType(eEffect) == EFFECT_TYPE_AC_INCREASE && + GetEffectInteger(eEffect, 1) == AC_SHIELD_ENCHANTMENT_BONUS) + { + int nMod = GetEffectInteger(eEffect, 0); + int nType = GetEffectInteger(eEffect, 1); + nShieldBonus += GetEffectInteger(eEffect, 0); + string s = "Found AC effect: bonus = " + IntToString(nMod) + ", type = " + IntToString(nType); + SendMessageToPC(GetFirstPC(), s); + } + eEffect = GetNextEffect(oCreature); + } + + return nShieldBonus; +}*/ +// //:: Handles psuedo-Foritifcation void DoFortification(object oPC = OBJECT_SELF, int nFortification = 25) { @@ -2275,7 +2384,7 @@ void DoFortification(object oPC = OBJECT_SELF, int nFortification = 25) IPSafeAddItemProperty(oHide, ItemPropertyImmunityMisc(IP_CONST_IMMUNITYMISC_BACKSTAB)); } } - +// // wrapper for DecrementRemainingSpellUses, works for newspellbook 'fake' spells too // should also find and decrement metamagics for newspellbooks diff --git a/src/include/prc_inc_switch.nss b/src/include/prc_inc_switch.nss index 4913e3d..b2c7160 100644 --- a/src/include/prc_inc_switch.nss +++ b/src/include/prc_inc_switch.nss @@ -70,6 +70,8 @@ 43 PRC_CRAFTING_BASE_ITEMS int 1 44 PRC_XP_USE_SIMPLE_LA int 1 45 PRC_XP_USE_SIMPLE_RACIAL_HD int 1 +46 PRC_CREATE_INFUSION_CASTER_LEVEL int 1 +47 PRC_CREATE_INFUSION_OPTIONAL_HERBS int 0 */ /* This variable MUST be updated with every new version of the PRC!!! */ @@ -1952,6 +1954,18 @@ const string PRC_CRAFT_ROD_CASTER_LEVEL = "PRC_CRAFT_ROD_CASTER_LEVE */ const string PRC_CRAFT_STAFF_CASTER_LEVEL = "PRC_CRAFT_STAFF_CASTER_LEVEL"; +/* + * As above, except it applies to herbal infusions + */ +const string PRC_CREATE_INFUSION_CASTER_LEVEL = "PRC_CREATE_INFUSION_CASTER_LEVEL"; + +/* + * Builder's Option: Enables the optional PnP herbs for creating infusions. + * Each herb is keyed to a spell circle level & spell school as shown on pg. 33 + * of the Master's of the Wild sourcebook. + */ +const string PRC_CREATE_INFUSION_OPTIONAL_HERBS = "PRC_CREATE_INFUSION_OPTIONAL_HERBS"; + /* * Characters with a crafting feat always have the appropriate base item in their inventory */ @@ -1961,45 +1975,52 @@ const string PRC_CRAFTING_BASE_ITEMS = "PRC_CRAFTING_BASE_ITEMS"; * Max level of spells brewed into potions * defaults to 3 */ -const string X2_CI_BREWPOTION_MAXLEVEL = "X2_CI_BREWPOTION_MAXLEVEL"; +//const string X2_CI_BREWPOTION_MAXLEVEL = "X2_CI_BREWPOTION_MAXLEVEL"; +const string PRC_X2_BREWPOTION_MAXLEVEL = "PRC_X2_BREWPOTION_MAXLEVEL"; /* * cost modifier of spells brewed into poitions * defaults to 50 */ -const string X2_CI_BREWPOTION_COSTMODIFIER = "X2_CI_BREWPOTION_COSTMODIFIER"; +const string PRC_X2_BREWPOTION_COSTMODIFIER = "PRC_X2_BREWPOTION_COSTMODIFIER"; /* * cost modifier of spells scribed into scrolls * defaults to 25 */ -const string X2_CI_SCRIBESCROLL_COSTMODIFIER = "X2_CI_SCRIBESCROLL_COSTMODIFIER"; +const string PRC_X2_SCRIBESCROLL_COSTMODIFIER = "PRC_X2_SCRIBESCROLL_COSTMODIFIER"; + +/* + * cost modifier of spells infused into herbs + * defaults to 25 + */ +const string PRC_X2_CREATEINFUSION_COSTMODIFIER = "PRC_X2_CREATEINFUSION_COSTMODIFIER"; /* * Max level of spells crafted into wands * defaults to 4 */ -const string X2_CI_CRAFTWAND_MAXLEVEL = "X2_CI_CRAFTWAND_MAXLEVEL"; +const string PRC_X2_CRAFTWAND_MAXLEVEL = "PRC_X2_CRAFTWAND_MAXLEVEL"; /* * cost modifier of spells crafted into wands * defaults to 750 */ -const string X2_CI_CRAFTWAND_COSTMODIFIER = "X2_CI_CRAFTWAND_COSTMODIFIER"; +const string PRC_X2_CRAFTWAND_COSTMODIFIER = "PRC_X2_CRAFTWAND_COSTMODIFIER"; /* * cost modifier of spells crafted into rods * note that adding a second spell costs 75% and 3 or more costs 50% * defaults to 750 */ -const string X2_CI_CRAFTROD_COSTMODIFIER = "X2_CI_CRAFTROD_COSTMODIFIER"; +const string PRC_X2_CRAFTROD_COSTMODIFIER = "PRC_X2_CRAFTROD_COSTMODIFIER"; /* * cost modifier of spells crafted into staffs * note that adding a second spell costs 75% and 3 or more costs 50% * defaults to 750 */ -const string X2_CI_CRAFTSTAFF_COSTMODIFIER = "X2_CI_CRAFTSTAFF_COSTMODIFIER"; +const string PRC_X2_CRAFTSTAFF_COSTMODIFIER = "PRC_X2_CRAFTSTAFF_COSTMODIFIER"; /** * Allows the use of arbitrary itemproperties and uses NWN item costs diff --git a/src/include/prc_inc_turning.nss b/src/include/prc_inc_turning.nss index 5504819..6c819d4 100644 --- a/src/include/prc_inc_turning.nss +++ b/src/include/prc_inc_turning.nss @@ -14,17 +14,6 @@ /* Function prototypes */ ////////////////////////////////////////////////// - - - - - - - - - - - //gets the number of class levels that count for turning int GetTurningClassLevel(object oCaster = OBJECT_SELF, int nTurnType = SPELL_TURN_UNDEAD); @@ -191,6 +180,20 @@ int GetIsTurnOrRebuke(object oTarget, int nTurnType, int nTargetRace) break; } + case SPELL_PLANT_DEFIANCE: + { + if(nTargetRace == RACIAL_TYPE_PLANT) + nReturn = ACTION_TURN; + + break; + } + case SPELL_PLANT_CONTROL: + { + if(nTargetRace == RACIAL_TYPE_PLANT) + nReturn = ACTION_REBUKE; + + break; + } case SPELL_TURN_PLANT: { // Plant domain rebukes or commands plants @@ -383,6 +386,13 @@ int GetTurningClassLevel(object oCaster = OBJECT_SELF, int nTurnType = SPELL_TUR if (nTurnType == SPELL_OPPORTUNISTIC_PIETY_TURN) return GetLevelByClass(CLASS_TYPE_FACTOTUM, oCaster); + + if (nTurnType == SPELL_PLANT_DEFIANCE || nTurnType == SPELL_PLANT_CONTROL) + { + int nDivineLvl = GetPrCAdjustedCasterLevelByType(TYPE_DIVINE, oCaster); + + return nDivineLvl; + } //Baelnorn & Archlich adds all class levels. if(GetLevelByClass(CLASS_TYPE_BAELNORN, oCaster) || GetHasFeat(FEAT_TEMPLATE_ARCHLICH_MARKER, oCaster)) //:: Archlich diff --git a/src/include/prc_inc_wpnrest.nss b/src/include/prc_inc_wpnrest.nss index 401b323..9dcf65a 100644 --- a/src/include/prc_inc_wpnrest.nss +++ b/src/include/prc_inc_wpnrest.nss @@ -40,13 +40,31 @@ int IsProficient(object oPC, int nBaseItem) case BASE_ITEM_CLUB: return GetHasFeat(FEAT_WEAPON_PROFICIENCY_SIMPLE, oPC) - || GetHasFeat(FEAT_WEAPON_PROFICIENCY_DRUID, oPC) - || GetHasFeat(FEAT_WEAPON_PROFICIENCY_CLUB, oPC); + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_DRUID, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_WIZARD, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_ROGUE, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_CLUB, oPC); + + case BASE_ITEM_HEAVYCROSSBOW: + return GetHasFeat(FEAT_WEAPON_PROFICIENCY_SIMPLE, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_DRUID, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_WIZARD, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_ROGUE, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_HEAVY_XBOW, oPC); + + case BASE_ITEM_LIGHTCROSSBOW: + return GetHasFeat(FEAT_WEAPON_PROFICIENCY_SIMPLE, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_DRUID, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_WIZARD, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_ROGUE, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_LIGHT_XBOW, oPC); case BASE_ITEM_DAGGER: return GetHasFeat(FEAT_WEAPON_PROFICIENCY_SIMPLE, oPC) - || GetHasFeat(FEAT_WEAPON_PROFICIENCY_DRUID, oPC) - || GetHasFeat(FEAT_WEAPON_PROFICIENCY_DAGGER, oPC); + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_DRUID, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_WIZARD, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_ROGUE, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_DAGGER, oPC); case BASE_ITEM_LONGSWORD: return GetHasFeat(FEAT_WEAPON_PROFICIENCY_MARTIAL, oPC) @@ -152,10 +170,17 @@ int IsProficient(object oPC, int nBaseItem) || GetHasFeat(FEAT_WEAPON_PROFICIENCY_ROGUE, oPC); case BASE_ITEM_QUARTERSTAFF: - return GetHasFeat(FEAT_WEAPON_PROFICIENCY_SIMPLE, oPC) + return GetHasFeat(FEAT_WEAPON_PROFICIENCY_QUARTERSTAFF, oPC) + ||GetHasFeat(FEAT_WEAPON_PROFICIENCY_SIMPLE, oPC) || GetHasFeat(FEAT_WEAPON_PROFICIENCY_DRUID, oPC) - || GetHasFeat(FEAT_WEAPON_PROFICIENCY_WIZARD, oPC); + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_WIZARD, oPC); + case BASE_ITEM_MAGICSTAFF: + return GetHasFeat(FEAT_WEAPON_PROFICIENCY_QUARTERSTAFF, oPC) + ||GetHasFeat(FEAT_WEAPON_PROFICIENCY_SIMPLE, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_DRUID, oPC) + || GetHasFeat(FEAT_WEAPON_PROFICIENCY_WIZARD, oPC); + case BASE_ITEM_RAPIER: return GetHasFeat(FEAT_WEAPON_PROFICIENCY_RAPIER, oPC) || GetHasFeat(FEAT_WEAPON_PROFICIENCY_MARTIAL, oPC) @@ -295,167 +320,185 @@ int GetWeaponProfFeatByType(int nBaseType) { switch(nBaseType) { - case BASE_ITEM_SHORTSWORD: - return FEAT_WEAPON_PROFICIENCY_SHORTSWORD; + case BASE_ITEM_CLUB: + return FEAT_WEAPON_PROFICIENCY_CLUB; + + case BASE_ITEM_QUARTERSTAFF: + return FEAT_WEAPON_PROFICIENCY_QUARTERSTAFF; - case BASE_ITEM_LONGSWORD: - return FEAT_WEAPON_PROFICIENCY_LONGSWORD; + case BASE_ITEM_MAGICSTAFF: + return FEAT_WEAPON_PROFICIENCY_QUARTERSTAFF; + + case BASE_ITEM_DAGGER: + return FEAT_WEAPON_PROFICIENCY_DAGGER; - case BASE_ITEM_BATTLEAXE: - return FEAT_WEAPON_PROFICIENCY_BATTLEAXE; + case BASE_ITEM_HEAVYCROSSBOW: + return FEAT_WEAPON_PROFICIENCY_HEAVY_XBOW; - case BASE_ITEM_BASTARDSWORD: - return FEAT_WEAPON_PROFICIENCY_BASTARD_SWORD; + case BASE_ITEM_LIGHTCROSSBOW: + return FEAT_WEAPON_PROFICIENCY_LIGHT_XBOW; + + case BASE_ITEM_SHORTSWORD: + return FEAT_WEAPON_PROFICIENCY_SHORTSWORD; - case BASE_ITEM_LIGHTFLAIL: - return FEAT_WEAPON_PROFICIENCY_LIGHT_FLAIL; + case BASE_ITEM_LONGSWORD: + return FEAT_WEAPON_PROFICIENCY_LONGSWORD; - case BASE_ITEM_WARHAMMER: - return FEAT_WEAPON_PROFICIENCY_WARHAMMER; + case BASE_ITEM_BATTLEAXE: + return FEAT_WEAPON_PROFICIENCY_BATTLEAXE; - case BASE_ITEM_LONGBOW: - return FEAT_WEAPON_PROFICIENCY_LONGBOW; + case BASE_ITEM_BASTARDSWORD: + return FEAT_WEAPON_PROFICIENCY_BASTARD_SWORD; - case BASE_ITEM_LIGHTMACE: - return FEAT_WEAPON_PROFICIENCY_LIGHT_MACE; + case BASE_ITEM_LIGHTFLAIL: + return FEAT_WEAPON_PROFICIENCY_LIGHT_FLAIL; - case BASE_ITEM_HALBERD: - return FEAT_WEAPON_PROFICIENCY_HALBERD; + case BASE_ITEM_WARHAMMER: + return FEAT_WEAPON_PROFICIENCY_WARHAMMER; - case BASE_ITEM_SHORTBOW: + case BASE_ITEM_LONGBOW: + return FEAT_WEAPON_PROFICIENCY_LONGBOW; + + case BASE_ITEM_LIGHTMACE: + return FEAT_WEAPON_PROFICIENCY_LIGHT_MACE; + + case BASE_ITEM_HALBERD: + return FEAT_WEAPON_PROFICIENCY_HALBERD; + + case BASE_ITEM_SHORTBOW: return FEAT_WEAPON_PROFICIENCY_SHORTBOW; - case BASE_ITEM_TWOBLADEDSWORD: + case BASE_ITEM_TWOBLADEDSWORD: return FEAT_WEAPON_PROFICIENCY_TWO_BLADED_SWORD; - case BASE_ITEM_GREATSWORD: + case BASE_ITEM_GREATSWORD: return FEAT_WEAPON_PROFICIENCY_GREATSWORD; - case BASE_ITEM_GREATAXE: + case BASE_ITEM_GREATAXE: return FEAT_WEAPON_PROFICIENCY_GREATAXE; - case BASE_ITEM_DART: + case BASE_ITEM_DART: return FEAT_WEAPON_PROFICIENCY_DART; - case BASE_ITEM_DIREMACE: + case BASE_ITEM_DIREMACE: return FEAT_WEAPON_PROFICIENCY_DIRE_MACE; - case BASE_ITEM_DOUBLEAXE: + case BASE_ITEM_DOUBLEAXE: return FEAT_WEAPON_PROFICIENCY_DOUBLE_AXE; - case BASE_ITEM_HEAVYFLAIL: + case BASE_ITEM_HEAVYFLAIL: return FEAT_WEAPON_PROFICIENCY_HEAVY_FLAIL; - case BASE_ITEM_LIGHTHAMMER: + case BASE_ITEM_LIGHTHAMMER: return FEAT_WEAPON_PROFICIENCY_LIGHT_HAMMER; - case BASE_ITEM_HANDAXE: + case BASE_ITEM_HANDAXE: return FEAT_WEAPON_PROFICIENCY_HANDAXE; - case BASE_ITEM_KAMA: + case BASE_ITEM_KAMA: return FEAT_WEAPON_PROFICIENCY_KAMA; - case BASE_ITEM_KATANA: + case BASE_ITEM_KATANA: return FEAT_WEAPON_PROFICIENCY_KATANA; - case BASE_ITEM_KUKRI: + case BASE_ITEM_KUKRI: return FEAT_WEAPON_PROFICIENCY_KUKRI; - case BASE_ITEM_MORNINGSTAR: + case BASE_ITEM_MORNINGSTAR: return FEAT_WEAPON_PROFICIENCY_MORNINGSTAR; - case BASE_ITEM_RAPIER: + case BASE_ITEM_RAPIER: return FEAT_WEAPON_PROFICIENCY_RAPIER; - case BASE_ITEM_SCIMITAR: + case BASE_ITEM_SCIMITAR: return FEAT_WEAPON_PROFICIENCY_SCIMITAR; - case BASE_ITEM_SCYTHE: + case BASE_ITEM_SCYTHE: return FEAT_WEAPON_PROFICIENCY_SCYTHE; - case BASE_ITEM_SHORTSPEAR: + case BASE_ITEM_SHORTSPEAR: return FEAT_WEAPON_PROFICIENCY_SHORTSPEAR; - case BASE_ITEM_SHURIKEN: + case BASE_ITEM_SHURIKEN: return FEAT_WEAPON_PROFICIENCY_SHURIKEN; - case BASE_ITEM_SICKLE: + case BASE_ITEM_SICKLE: return FEAT_WEAPON_PROFICIENCY_SICKLE; - case BASE_ITEM_SLING: + case BASE_ITEM_SLING: return FEAT_WEAPON_PROFICIENCY_SLING; - case BASE_ITEM_THROWINGAXE: + case BASE_ITEM_THROWINGAXE: return FEAT_WEAPON_PROFICIENCY_THROWING_AXE; - case BASE_ITEM_CSLASHWEAPON: + case BASE_ITEM_CSLASHWEAPON: return FEAT_WEAPON_PROFICIENCY_CREATURE; - case BASE_ITEM_CPIERCWEAPON: + case BASE_ITEM_CPIERCWEAPON: return FEAT_WEAPON_PROFICIENCY_CREATURE; - case BASE_ITEM_CBLUDGWEAPON: + case BASE_ITEM_CBLUDGWEAPON: return FEAT_WEAPON_PROFICIENCY_CREATURE; - case BASE_ITEM_CSLSHPRCWEAP: + case BASE_ITEM_CSLSHPRCWEAP: return FEAT_WEAPON_PROFICIENCY_CREATURE; - case BASE_ITEM_TRIDENT: + case BASE_ITEM_TRIDENT: return FEAT_WEAPON_PROFICIENCY_TRIDENT; - case BASE_ITEM_DWARVENWARAXE: + case BASE_ITEM_DWARVENWARAXE: return FEAT_WEAPON_PROFICIENCY_DWARVEN_WARAXE; - case BASE_ITEM_WHIP: + case BASE_ITEM_WHIP: return FEAT_WEAPON_PROFICIENCY_WHIP; - case BASE_ITEM_ELVEN_LIGHTBLADE: + case BASE_ITEM_ELVEN_LIGHTBLADE: return FEAT_WEAPON_PROFICIENCY_ELVEN_LIGHTBLADE; - case BASE_ITEM_ELVEN_THINBLADE: + case BASE_ITEM_ELVEN_THINBLADE: return FEAT_WEAPON_PROFICIENCY_ELVEN_THINBLADE; - case BASE_ITEM_ELVEN_COURTBLADE: + case BASE_ITEM_ELVEN_COURTBLADE: return FEAT_WEAPON_PROFICIENCY_ELVEN_COURTBLADE; - case BASE_ITEM_HEAVY_PICK: + case BASE_ITEM_HEAVY_PICK: return FEAT_WEAPON_PROFICIENCY_HEAVY_PICK; - case BASE_ITEM_LIGHT_PICK: + case BASE_ITEM_LIGHT_PICK: return FEAT_WEAPON_PROFICIENCY_LIGHT_PICK; - case BASE_ITEM_SAI: + case BASE_ITEM_SAI: return FEAT_WEAPON_PROFICIENCY_SAI; - case BASE_ITEM_NUNCHAKU: + case BASE_ITEM_NUNCHAKU: return FEAT_WEAPON_PROFICIENCY_NUNCHAKU; - case BASE_ITEM_FALCHION: + case BASE_ITEM_FALCHION: return FEAT_WEAPON_PROFICIENCY_FALCHION; - case BASE_ITEM_SAP: + case BASE_ITEM_SAP: return FEAT_WEAPON_PROFICIENCY_SAP; - case BASE_ITEM_KATAR: + case BASE_ITEM_KATAR: return FEAT_WEAPON_PROFICIENCY_KATAR; - case BASE_ITEM_HEAVY_MACE: + case BASE_ITEM_HEAVY_MACE: return FEAT_WEAPON_PROFICIENCY_HEAVY_MACE; - case BASE_ITEM_MAUL: + case BASE_ITEM_MAUL: return FEAT_WEAPON_PROFICIENCY_MAUL; - case BASE_ITEM_DOUBLE_SCIMITAR: + case BASE_ITEM_DOUBLE_SCIMITAR: return FEAT_WEAPON_PROFICIENCY_DOUBLE_SCIMITAR; - case BASE_ITEM_GOAD: + case BASE_ITEM_GOAD: return FEAT_WEAPON_PROFICIENCY_GOAD; - case BASE_ITEM_EAGLE_CLAW: + case BASE_ITEM_EAGLE_CLAW: return FEAT_WEAPON_PROFICIENCY_EAGLE_CLAW; - default: - return FEAT_WEAPON_PROFICIENCY_SIMPLE; + default: + return FEAT_WEAPON_PROFICIENCY_SIMPLE; } return 0; diff --git a/src/include/prc_ipfeat_const.nss b/src/include/prc_ipfeat_const.nss index a1829be..d419f0b 100644 --- a/src/include/prc_ipfeat_const.nss +++ b/src/include/prc_ipfeat_const.nss @@ -1206,11 +1206,12 @@ const int IP_CONST_FEAT_REGENERATION_5 = 24820; const int IP_CONST_FEAT_SCENT = 24821; const int IP_CONST_FEAT_GIANT_RACIAL_TYPE = 24822; -const int IP_CONST_FEAT_TEMPLATE_ARCHLICH_MARKER = 16401; //:: Archlich -const int IP_CONST_FEAT_TEMPLATE_TURN_UNDEAD = 16402; -const int IP_CONST_FEAT_TEMPLATE_PROJECTION = 24823; -const int IP_CONST_FEAT_TEMPLATE_END_PROJECTION = 24824; -const int IP_CONST_FEAT_TEMPLATE_ANIMATE_DEAD = 24825; +const int IP_CONST_FEAT_TEMPLATE_ARCHLICH_MARKER = 16401; //:: Archlich +const int IP_CONST_FEAT_TEMPLATE_TURN_UNDEAD = 16402; +const int IP_CONST_FEAT_TEMPLATE_BAELNORN_MARKER = 16409; //:: Baelnorn +const int IP_CONST_FEAT_TEMPLATE_PROJECTION = 24823; +const int IP_CONST_FEAT_TEMPLATE_END_PROJECTION = 24824; +const int IP_CONST_FEAT_TEMPLATE_ANIMATE_DEAD = 24825; const int IP_CONST_FEAT_TEMPLATE_SAINT_SLA_BLESS = 16403; //:: Saint //const int IP_CONST_FEAT_TEMPLATE_SAINT_SLA_GUIDANCE = 16404; const int IP_CONST_FEAT_TEMPLATE_SAINT_SLA_RESISTANCE = 16405; diff --git a/src/include/prc_misc_const.nss b/src/include/prc_misc_const.nss index 0575811..b083a8e 100644 --- a/src/include/prc_misc_const.nss +++ b/src/include/prc_misc_const.nss @@ -29,6 +29,10 @@ const int BASE_ITEM_CRAFTED_STAFF = 201; const int BASE_ITEM_ELVEN_LIGHTBLADE = 202; const int BASE_ITEM_ELVEN_THINBLADE = 203; const int BASE_ITEM_ELVEN_COURTBLADE = 204; +const int BASE_ITEM_CRAFT_SCEPTER = 249; +const int BASE_ITEM_MAGIC_SCEPTER = 250; +const int BASE_ITEM_MUNDANE_HERB = 252; +const int BASE_ITEM_INFUSED_HERB = 253; //::////////////////////////////////////////////// //:: Player Health Const diff --git a/src/include/prc_nui_com_inc.nss b/src/include/prc_nui_com_inc.nss new file mode 100644 index 0000000..bf9bc51 --- /dev/null +++ b/src/include/prc_nui_com_inc.nss @@ -0,0 +1,530 @@ +#include "prc_nui_consts" +#include "inc_newspellbook" +#include "psi_inc_psifunc" +#include "inc_lookups" +#include "nw_inc_nui" + +// +// GetCurrentSpellLevel +// Gets the current spell level the class can achieve at the current +// caster level (ranging from 0-9) +// +// Arguments: +// nClass:int the ClassID +// nLevel:int the caster level +// +// Returns: +// int the circle the class can achieve currently +// +int GetCurrentSpellLevel(int nClass, int nLevel); + +// +// GetMaxSpellLevel +// Gets the highest possible circle the class can achieve (from 0-9) +// +// Arguments: +// nClass:int the ClassID +// +// Returns: +// int the highest circle that can be achieved +// +int GetMaxSpellLevel(int nClass); + +// +// GetMinSpellLevel +// Gets the lowest possible circle the class can achieve (from 0-9) +// +// Arguments: +// nClass:int the ClassID +// +// Returns: +// int the lowest circle that can be achieved +// +int GetMinSpellLevel(int nClass); + +// +// GetHighestLevelPossibleInClass +// Given a class Id this will determine what the max level of a class can be +// achieved +// +// Arguments: +// nClass:int the ClassID +// +// Returns: +// int the highest possible level the class can achieve +// +int GetHighestLevelPossibleInClass(int nClass); + +// +// GetClassSpellbookFile +// Gets the class 2da spellbook/ability for the given class Id +// +// Arguments: +// nClass:int the classID +// +// Returns: +// string the 2da file name for the spell/abilities of the ClassID +// +string GetClassSpellbookFile(int nClass); + +// +// GetBinderSpellToFeatDictionary +// Sets up the Binder Spell Dictionary that is used to match a binder's vestige +// to their feat. This is constructed based off the binder's known location of +// their feat and spell ranges in the base 2das respectivly. After constructing +// this it will be saved to the player locally as a cached result since we do +// not need to call this again. +// +// Argument: +// oPlayer:object the player +// +// Returns: +// json:Dictionary a dictionary of mapping between the SpellID +// and the FeatID of a vestige ability +// +json GetBinderSpellToFeatDictionary(object oPlayer=OBJECT_SELF); + +// +// GetSpellLevelIcon +// Takes the spell circle int and gets the icon appropriate for it (i.e. 0 turns +// into "ir_cantrips" +// +// Arguments: +// spellLevel:int the spell level we want the icon for +// +// Returns: +// string the spell level icon +// +string GetSpellLevelIcon(int spellLevel); + +// +// GetSpellLevelToolTip +// Gets the spell level tool tip text based on the int spell level provided (i.e. +// 0 turns into "Cantrips") +// +// Arguments: +// spellLevel:int the spell level we want the tooltip for +// +// Returns: +// string the spell level toop tip +// +string GetSpellLevelToolTip(int spellLevel); + +// +// GetSpellIcon +// Gets the spell icon based off the spellId, or featId supplied +// +// Arguments: +// nClass:int the class Id +// featId:int the featId we can use the icon for +// spellId:int the spell Id we want the icon for +// +// Returns: +// json:String the string of the icon we want. +// +json GetSpellIcon(int spellId, int featId=0, int nClass=0); +string GetSpellName(int spellId, int realSpellID=0, int featId=0, int nClass=0); + +// +// GreyOutButton +// Takes NUI Button along with it's width and height and greys it out it with a drawn +// colored rectangle to represent it's not been selected or not valid. +// +// Arguments: +// jButton:json the NUI Button +// w:float the width of the button +// h:float the height of the button +// +// Returns: +// json the NUI button greyed out +// +json GreyOutButton(json jButton, float w, float h); + +// +// CreateGreyOutRectangle +// Creates a grey out rectangle for buttons +// +// Arguments: +// w:float the width of the button +// h:float the height of the button +// +// Returns: +// json the transparant black rectangle +// +json CreateGreyOutRectangle(float w, float h); + +void CreateSpellDescriptionNUI(object oPlayer, int featID, int spellId=0, int realSpellId=0, int nClass=0); +void ClearSpellDescriptionNUI(object oPlayer=OBJECT_SELF); + +int GetCurrentSpellLevel(int nClass, int nLevel) +{ + int currentLevel = nLevel; + + // ToB doesn't have a concept of spell levels, but still match up to it + if(nClass == CLASS_TYPE_WARBLADE + || nClass == CLASS_TYPE_SWORDSAGE + || nClass == CLASS_TYPE_CRUSADER + || nClass == CLASS_TYPE_SHADOWCASTER) + { + return 9; + } + + + // Binders don't really have a concept of spell level + if (nClass == CLASS_TYPE_BINDER + || nClass == CLASS_TYPE_DRAGON_SHAMAN) // they can only reach 1st circle + return 1; + + //Shadowsmith has no concept of spell levels + if (nClass == CLASS_TYPE_SHADOWSMITH) + return 2; + + if (nClass == CLASS_TYPE_WARLOCK + || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT) + return 4; + + // Spont casters have their own function + if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS + || nClass == CLASS_TYPE_ARCHIVIST) + { + + int maxLevel = GetMaxSpellLevelForCasterLevel(nClass, currentLevel); + return maxLevel; + } + else + { + // everyone else uses this + string spellLevel2da = GetAMSKnownFileName(nClass); + + currentLevel = nLevel - 1; // Level is 1 off of the row in the 2da + + if (nClass == CLASS_TYPE_FIST_OF_ZUOKEN + || nClass == CLASS_TYPE_PSION + || nClass == CLASS_TYPE_PSYWAR + || nClass == CLASS_TYPE_WILDER + || nClass == CLASS_TYPE_PSYCHIC_ROGUE + || nClass == CLASS_TYPE_WARMIND) + currentLevel = GetManifesterLevel(OBJECT_SELF, nClass, TRUE) - 1; + + int totalLevel = Get2DARowCount(spellLevel2da); + + // in case we somehow go over bounds just don't :) + if (currentLevel >= totalLevel) + currentLevel = totalLevel - 1; + + //Psionics have MaxPowerLevel as their column name + string columnName = "MaxPowerLevel"; + + //Invokers have MaxInvocationLevel + if (nClass == CLASS_TYPE_WARLOCK + || nClass == CLASS_TYPE_DRAGON_SHAMAN + || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT) + columnName = "MaxInvocationLevel"; + + // Truenamers have 3 sets of utterances, ranging from 1-6, EvolvingMind covers the entire range + if (nClass == CLASS_TYPE_TRUENAMER) + { + columnName = "EvolvingMind"; + spellLevel2da = "cls_true_maxlvl"; //has a different 2da we want to look at + } + + if (nClass == CLASS_TYPE_BINDER) + { + columnName = "VestigeLvl"; + spellLevel2da = "cls_bind_binder"; + } + + // ToB doesn't have a concept of this, but we don't care. + + int maxLevel = StringToInt(Get2DACache(spellLevel2da, columnName, currentLevel)); + return maxLevel; + } +} + +int GetMinSpellLevel(int nClass) +{ + // again sponts have their own function + if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS + || nClass == CLASS_TYPE_ARCHIVIST) + { + return GetMinSpellLevelForCasterLevel(nClass, GetHighestLevelPossibleInClass(nClass)); + } + else + { + if (nClass == CLASS_TYPE_FIST_OF_ZUOKEN + || nClass == CLASS_TYPE_PSION + || nClass == CLASS_TYPE_PSYWAR + || nClass == CLASS_TYPE_WILDER + || nClass == CLASS_TYPE_PSYCHIC_ROGUE + || nClass == CLASS_TYPE_WARMIND + || nClass == CLASS_TYPE_WARBLADE + || nClass == CLASS_TYPE_SWORDSAGE + || nClass == CLASS_TYPE_CRUSADER + || nClass == CLASS_TYPE_WARLOCK + || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT + || nClass == CLASS_TYPE_DRAGON_SHAMAN + || nClass == CLASS_TYPE_SHADOWCASTER + || nClass == CLASS_TYPE_SHADOWSMITH + || nClass == CLASS_TYPE_BINDER) + return 1; + + return GetCurrentSpellLevel(nClass, 1); + } + +} + +int GetMaxSpellLevel(int nClass) +{ + if (nClass == CLASS_TYPE_WILDER + || nClass == CLASS_TYPE_PSION) + return 9; + if (nClass == CLASS_TYPE_PSYCHIC_ROGUE + || nClass == CLASS_TYPE_FIST_OF_ZUOKEN + || nClass == CLASS_TYPE_WARMIND) + return 5; + if (nClass == CLASS_TYPE_PSYWAR) + return 6; + + return GetCurrentSpellLevel(nClass, GetHighestLevelPossibleInClass(nClass)); +} + +int GetHighestLevelPossibleInClass(int nClass) +{ + string sFile; + + //sponts have their spells in the classes.2da + if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS + || nClass == CLASS_TYPE_ARCHIVIST) + { + sFile = Get2DACache("classes", "SpellGainTable", nClass); + } + else + { + // everyone else uses this + sFile = GetAMSKnownFileName(nClass); + + if (nClass == CLASS_TYPE_TRUENAMER) + { + sFile = "cls_true_maxlvl"; //has a different 2da we want to look at + } + + if (nClass == CLASS_TYPE_BINDER) + { + sFile = "cls_bind_binder"; + } + } + + return Get2DARowCount(sFile); +} + +string GetClassSpellbookFile(int nClass) +{ + string sFile; + // Spontaneous casters use a specific file name structure + if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS + || nClass == CLASS_TYPE_ARCHIVIST) + { + sFile = GetFileForClass(nClass); + } + // everyone else uses this structure + else + { + sFile = GetAMSDefinitionFileName(nClass); + + if (nClass == CLASS_TYPE_BINDER) + { + sFile = "vestiges"; + } + } + + return sFile; +} + +string GetSpellLevelIcon(int spellLevel) +{ + switch (spellLevel) + { + case 0: return "ir_cantrips"; + case 1: return "ir_level1"; + case 2: return "ir_level2"; + case 3: return "ir_level3"; + case 4: return "ir_level4"; + case 5: return "ir_level5"; + case 6: return "ir_level6"; + case 7: return "ir_level789"; + case 8: return "ir_level789"; + case 9: return "ir_level789"; + } + + return ""; +} + +string GetSpellLevelToolTip(int spellLevel) +{ + switch (spellLevel) + { + case 0: return "Cantrips"; + case 1: return "Level 1"; + case 2: return "Level 2"; + case 3: return "Level 3"; + case 4: return "Level 4"; + case 5: return "Level 5"; + case 6: return "Level 6"; + case 7: return "Level 7"; + case 8: return "Level 8"; + case 9: return "Level 9"; + } + + return ""; +} + + +json GetSpellIcon(int spellId,int featId=0,int nClass=0) +{ + // Binder's spells don't have the FeatID on the spells.2da, so we have to use + // the mapping we constructed to get it. + if (nClass == CLASS_TYPE_BINDER) + { + json binderDict = GetBinderSpellToFeatDictionary(); + int nFeatID = JsonGetInt(JsonObjectGet(binderDict, IntToString(spellId))); + return JsonString(Get2DACache("feat", "Icon", featId)); + } + + if (featId) + return JsonString(Get2DACache("feat", "Icon", featId)); + + int masterSpellID = StringToInt(Get2DACache("spells", "Master", spellId)); + + // if this is a sub radial spell, then we use spell's icon instead + if (masterSpellID) + return JsonString(Get2DACache("spells", "IconResRef", spellId)); + + // the FeatID holds the accurate spell icon, not the SpellID + int nFeatID = StringToInt(Get2DACache("spells", "FeatID", spellId)); + + return JsonString(Get2DACache("feat", "Icon", nFeatID)); +} + +string GetSpellName(int spellId, int realSpellID=0, int featId=0, int nClass=0) +{ + if ((nClass == CLASS_TYPE_SHADOWSMITH + || nClass == CLASS_TYPE_SHADOWCASTER) && spellId) + return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", spellId))); + if (nClass == CLASS_TYPE_TRUENAMER && featId) + return GetStringByStrRef(StringToInt(Get2DACache("feat", "FEAT", featId))); + if (realSpellID) + return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", realSpellID))); + if (spellId) + return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", spellId))); + if (featId) + return GetStringByStrRef(StringToInt(Get2DACache("feat", "FEAT", featId))); + + return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", spellId))); +} + +json GetBinderSpellToFeatDictionary(object oPlayer=OBJECT_SELF) +{ + // a dictionary of + json binderDict = GetLocalJson(oPlayer, NUI_SPELLBOOK_BINDER_DICTIONARY_CACHE_VAR); + // if this hasn't been created, create it now. + if (binderDict == JsonNull()) + binderDict = JsonObject(); + else + return binderDict; + + // the starting row for binder spells + int spellIndex = 19070; + // the starting row for binder feats + int featIndex = 9030; + //the end of the binder spells/feats + while (spellIndex <= 19156 && featIndex <= 9104) + { + // get the SpellID tied to the feat + int spellID = StringToInt(Get2DACache("feat", "SPELLID", featIndex)); + // if the spellID matches the current index, then this is the spell + // attached to the feat + if (spellID == spellIndex) + { + binderDict = JsonObjectSet(binderDict, IntToString(spellID), JsonInt(featIndex)); + + // move to next spell/feat + featIndex++; + spellIndex++; + } + // else we have reached a subdial spell + else + { + // loop through until we reach back at spellID + while (spellIndex < spellID) + { + int masterSpell = StringToInt(Get2DACache("spells", "Master", spellIndex)); + + // add the sub radial to the dict, tied to the master's FeatID + int featId = JsonGetInt(JsonObjectGet(binderDict, IntToString(masterSpell))); + binderDict = JsonObjectSet(binderDict, IntToString(spellIndex), JsonInt(featId)); + + spellIndex++; + } + + + // some feats overlap the same FeatID, can cause this to get stuck. + // if it happens then move on + if (spellIndex > spellID) + featIndex++; + } + } + + // cache the result + SetLocalJson(oPlayer, NUI_SPELLBOOK_BINDER_DICTIONARY_CACHE_VAR, binderDict); + return binderDict; +} + +json GreyOutButton(json jButton, float w, float h) +{ + json retValue = jButton; + + json jBorders = JsonArray(); + jBorders = JsonArrayInsert(jBorders, CreateGreyOutRectangle(w, h)); + + return NuiDrawList(jButton, JsonBool(FALSE), jBorders); +} + +json CreateGreyOutRectangle(float w, float h) +{ + // set the points of the button shape + json jPoints = JsonArray(); + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + jPoints = JsonArrayInsert(jPoints, JsonFloat(h)); + + jPoints = JsonArrayInsert(jPoints, JsonFloat(w)); + jPoints = JsonArrayInsert(jPoints, JsonFloat(h)); + + jPoints = JsonArrayInsert(jPoints, JsonFloat(w)); + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + + return NuiDrawListPolyLine(JsonBool(TRUE), NuiColor(0, 0, 0, 127), JsonBool(TRUE), JsonFloat(2.0), jPoints); +} + +void CreateSpellDescriptionNUI(object oPlayer, int featID, int spellId=0, int realSpellId=0, int nClass=0) +{ + SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_FEATID_VAR, featID); + SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_SPELLID_VAR, spellId); + SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_REAL_SPELLID_VAR, realSpellId); + SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_CLASSID_VAR, nClass); + ExecuteScript("prc_nui_dsc_view", oPlayer); +} + +void ClearSpellDescriptionNUI(object oPlayer=OBJECT_SELF) +{ + DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_FEATID_VAR); + DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_SPELLID_VAR); + DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_REAL_SPELLID_VAR); + DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_CLASSID_VAR); +} + diff --git a/src/include/prc_nui_consts.nss b/src/include/prc_nui_consts.nss index 0cb0efa..e1e9c5c 100644 --- a/src/include/prc_nui_consts.nss +++ b/src/include/prc_nui_consts.nss @@ -110,4 +110,49 @@ const string NUI_PRC_PA_TEXT_BIND = "nui_prc_pa_text_bind"; // Left Button Enabled Bind for Power Attack NUI const string NUI_PRC_PA_LEFT_BUTTON_ENABLED_BIND = "leftButtonEnabled"; // Right Button Enabled Bind for Power Attack NUI -const string NUI_PRC_PA_RIGHT_BUTTON_ENABLED_BIND = "rightButtonEnabled"; \ No newline at end of file +const string NUI_PRC_PA_RIGHT_BUTTON_ENABLED_BIND = "rightButtonEnabled"; + +////////////////////////////////////////////////// +// // +// NUI Level Up // +// // +////////////////////////////////////////////////// + +const string NUI_LEVEL_UP_WINDOW_ID = "prcLevelUpNui"; + +const string NUI_LEVEL_UP_SPELL_CIRCLE_BUTTON_BASEID = "NuiLevelUpCircleButton_"; +const string NUI_LEVEL_UP_SPELL_BUTTON_BASEID = "NuiLevelUpSpellButton_"; +const string NUI_LEVEL_UP_SPELL_DISABLED_BUTTON_BASEID = "NuiLevelUpDisabledSpellButton_"; +const string NUI_LEVEL_UP_SPELL_CHOSEN_BUTTON_BASEID = "NuiLevelUpChosenSpellButton_"; +const string NUI_LEVEL_UP_SPELL_CHOSEN_DISABLED_BUTTON_BASEID = "NuiLevelUpDisabledChosenSpellButton_"; +const string NUI_LEVEL_UP_DONE_BUTTON = "NuiLevelUpDoneButton"; +const string NUI_LEVEL_UP_RESET_BUTTON = "NuiLevelUpResetButton"; + +const string NUI_LEVEL_UP_SELECTED_CLASS_VAR = "NUILevelUpSelectedClass"; +const string NUI_LEVEL_UP_SELECTED_CIRCLE_VAR = "NUILevelUpSelectedCircle"; +const string NUI_LEVEL_UP_KNOWN_SPELLS_VAR = "NUILevelUpKnownSpells"; +const string NUI_LEVEL_UP_CHOSEN_SPELLS_VAR = "NUILevelUpChosenSpells"; +const string NUI_LEVEL_UP_EXPANDED_KNOW_LIST_VAR = "NUILevelUpExpKnowList"; +const string NUI_LEVEL_UP_POWER_LIST_VAR = "NUILevelUpPowerList"; +const string NUI_LEVEL_UP_DISCIPLINE_INFO_VAR = "GetDisciplineInfoObjectCache_"; +const string NUI_LEVEL_UP_SPELLID_LIST_VAR = "NUILevelUpSpellIDList_"; +const string NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR = "NUIRemainingChoicesCache"; +const string NUI_LEVEL_UP_RELEARN_LIST_VAR = "NUILevelUpRelearnList"; +const string NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR = "NUILevelUpArchivistNewSpellsList"; + +const string NUI_LEVEL_UP_EXPANDED_CHOICES_VAR = "NUIExpandedChoices"; +const string NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR = "NUIEpicExpandedChoices"; + +const int NUI_LEVEL_UP_MANEUVER_PREREQ_LIMIT = 6; + +const string NUI_LEVEL_UP_MANEUVER_TOTAL = "ManeuverTotal"; +const string NUI_LEVEL_UP_STANCE_TOTAL = "StanceTotal"; + +const string NUI_LEVEL_UP_SPELLBOOK_OBJECT_CACHE_VAR = "GetSpellListObjectCache_"; +const string NUI_LEVEL_UP_KNOWN_INVOCATIONS_CACHE_VAR = "GetInvokerKnownListObjectCache_"; + +const string NUI_SPELL_DESCRIPTION_FEATID_VAR = "NUISpellDescriptionFeatID"; +const string NUI_SPELL_DESCRIPTION_CLASSID_VAR = "NUISpellDescriptionClassID"; +const string NUI_SPELL_DESCRIPTION_SPELLID_VAR = "NUISpellDescriptionSpellID"; +const string NUI_SPELL_DESCRIPTION_REAL_SPELLID_VAR = "NUISpellDescriptionRealSpellID"; + diff --git a/src/include/prc_nui_lv_inc.nss b/src/include/prc_nui_lv_inc.nss new file mode 100644 index 0000000..bc10b28 --- /dev/null +++ b/src/include/prc_nui_lv_inc.nss @@ -0,0 +1,3339 @@ +//:://///////////////////////////////////////////// +//:: PRC Level Up NUI +//:: prc_nui_lv_inc +//::////////////////////////////////////////////// +/* + This is the logic for the Level Up NUI, holding all the functions needed for + the NUI to operate properly and allow leveling up in different classes. +*/ +//::////////////////////////////////////////////// +//:: Created By: Rakiov +//:: Created On: 20.06.2005 +//::////////////////////////////////////////////// + +#include "prc_nui_com_inc" +#include "tob_inc_tobfunc" +#include "tob_inc_moveknwn" +#include "inv_inc_invfunc" +#include "shd_inc_mystknwn" +#include "shd_inc_shdfunc" +#include "true_inc_truknwn" +#include "true_inc_trufunc" + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Spont Casters / Base /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +// +// GetSpellListObject +// Gets the JSON Object representation of a class's spellbook 2da. This function +// will cache it's result to the object given to it to avoid further calculations +// and will not clear itself since it does not change. +// +// Arguments: +// nClass:int the ClassID +// oPC:Object the player +// +// Returns: +// Json:Dictionary a dictionary of each circle's spellbook Ids. +// +json GetSpellListObject(int nClass, object oPC=OBJECT_SELF); + +// +// GetKnownSpellListObject +// Gets the JSON Object representation of a player's known spell list. This function +// will temporarily cache it's result to the object given to avoid further calculations. +// However this should be cleared after done using the level up screen or reset. +// +// Arguments: +// nClass:int the ClassID +// oPC:Object the player +// +// Returns: +// Json:Dictionary a dictionary of each circle's known spellbook Ids. +// +json GetKnownSpellListObject(int nClass, object oPC=OBJECT_SELF); + +// +// GetKnownSpellListObject +// Gets the JSON Object representation of a player's chosen spell list. This function +// will temporarily cache it's result to the object given to avoid further calculations. +// However this should be cleared after done using the level up screen or reset. +// +// Arguments: +// nClass:int the ClassID +// oPC:Object the player +// +// Returns: +// Json:Dictionary a dictionary of each circle's chosen spellbook Ids. +// +json GetChosenSpellListObject(int nClass, object oPC=OBJECT_SELF); + +// +// ShouldAddSpellToSpellButtons +// Given a classId and a spellbookId, if the player knows the spell already we +// should not add the spell, otherwise we should +// +// Arguments: +// nClass:int Class ID +// spellbookId:int the spell book ID +// oPC:object the player +// +// Returns: +// int:Boolean TRUE if spell should be added, FALSE otherwise +// +int ShouldAddSpellToSpellButtons(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// OpenNUILevelUpWindow +// Opens the Level Up NUI window for the provided class +// +// Arguments: +// nClass:int the ClassID +// +void OpenNUILevelUpWindow(int nClass, object oPC=OBJECT_SELF); + +// +// CloseNUILevelUpWindow +// Closes the NUI Level Up Window if its open +// +void CloseNUILevelUpWindow(object oPC=OBJECT_SELF); + +// +// GetTrueClassType +// Gets the true class Id for a provided class Id, mostly for RHD and for +// ToB prestige classes +// +// Arguments: +// nClass:int classId +// +// Returns: +// int the true classId based off nClass +// +int GetTrueClassType(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingSpellChoices +// Gets the remaining spell choices for a class at the given circle by checking its +// chosen spells and comparing it against the total spells allowed. This value +// is cached on the player and cleared everytime the window is refreshed/closed +// +// Arguments: +// nClass:int the class id +// circleLevel:int the circle being checked +// +// Returns: +// int the amount of choices left at the circle +// +int GetRemainingSpellChoices(int nClass, int circleLevel, object oPC=OBJECT_SELF); + +// +// ShouldSpellButtonBeEnabled +// Checks whether a spell button should be enabled either because all choices have +// been made, replacing spells isn't allowed, or for various other reasons +// +// Arguments: +// nClass:int class id +// circleLevel:int the chosen circle +// spellbookId:int the chosen spell +// +// Returns: +// int:Boolean TRUE if spell button should be enabled, FALSE otherwise +// +int ShouldSpellButtonBeEnabled(int nClass, int circleLevel, int spellbookId, object oPC=OBJECT_SELF); + +// +// AddSpellToChosenList +// Adds spell to the chosen spells list +// +// Arguments: +// nClass:int the classId +// spellbookId:int the spellbook Id +// spellCircle:int the current circle of the spell +// +void AddSpellToChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF); + +// +// RemoveSpellFromChosenList +// Removes a spell from the chosen spell list +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook Id +// spellCircle:int the circle of the spell +// +void RemoveSpellFromChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF); + +// +// LearnSpells +// gives the player the spells they want to learn based off of the chosen spell +// list in a stored variable +// +// Arguments: +// nClass:int the classId +// +void LearnSpells(int nClass, object oPC=OBJECT_SELF); + +// +// RemoveSpells +// removes spells from the player that they may know currently but aren't selected +// based off lists in stored variables +// +// Arguments: +// nClass:int the classId +// +void RemoveSpells(int nClass, object oPC=OBJECT_SELF); + +// +// FinishLevelUp +// Finishes level up NUI by removing spells, learning spells, clearing cache, then closing the NUI +// +// Arguments: +// nClass:int the class id +// +void FinishLevelUp(int nClass, object oPC=OBJECT_SELF); + +// +// ClearLevelUpNUICaches +// Clears the cache (stored local variables) for the level up NUI so it is +// ready to be used for a new level up +// +// Arguments: +// nClass:int class id +// oPC:object the player object this is stored under +// +void ClearLevelUpNUICaches(int nClass, object oPC=OBJECT_SELF); + +// +// SpellIsWithinObject +// checks whether a spell is within a JSON Object structure used by the remaining +// spells object and known spells object, following this structure +// { +// "circleLevel:int": [ 1,2,3...,spellId], +// ... +// } +// +// Arguments +// nClass:int classId +// spellbookId:int the spellbook Id +// circleLevel:int the chosen circle of the spell +// spellList;JsonObject the spell list object being checked +// +// Returns: +// int:Boolean TRUE if it is in the object, FALSE otherwise +// +int SpellIsWithinObject(int nClass, int spellbookId, int circleLevel, json spellList, object oPC=OBJECT_SELF); + +// +// IsLevelUpNUIOpen +// Checks if the Level Up NUI is open for the player or not +// +// Arguments: +// oPC:object the player object +// +// Returns: +// int:Boolean TRUE if it is, FALSE otherwise +// +int IsLevelUpNUIOpen(object oPC=OBJECT_SELF); + +// +// IsClassAllowedToUseLevelUpNUI +// Is the provided class allowed to use the level up NUI +// +// Arguments: +// nClass:int class id +// +// Returns: +// int:Boolean TRUE if it can, FALSE otherwise +// +int IsClassAllowedToUseLevelUpNUI(int nClass); + +// +// EnabledChosenButton +// determines if a chosen spell button should be enabled or not. It may not due to +// class restrictions, replacing is not enabled, or other reason +// +// Arguments: +// nClass:int the class id +// spellbookId: the spellbook Id +// circleLevel: the spell's circle +// +// Returns: +// int:Boolean TRUE if it should be enabled, FALSE otherwise +// +int EnableChosenButton(int nClass, int spellbookId, int circleLevel, object oPC=OBJECT_SELF); + +// +// ResetChoices +// Action for the Level Up NUI's 'Reset' button, resets choices by clearing the cache of +// the user so their choices are forgotten and they can start over. +// +// Arguments: +// oPC:object the player object +// +void ResetChoices(object oPC=OBJECT_SELF); + +// +// RemoveSpellKnown +// Removes a spell from a player based off class id. This is for classes that +// aren't spont casters where we have to go in and adjust persistant arrays +// to say if a spell is known or not. +// +// Arguments: +// nClass:int class id +// spellbookId:int the spellbook Id +// oPC:object the player object +// nList:int the list we are removing the spell from (extra invocations or expanded knowledge) +// +void RemoveSpellKnown(int nClass, int spellbookId, object oPC=OBJECT_SELF, int nList=0); + +// +// GetSpellIDsKnown +// Gets the SpellIDs list of the given class and list and returns it as a JsonObject following this structure +// { +// "spellId:int": TRUE, +// ... +// } +// +// This is to keep lookups at O(1) processing time. This value is cached and is +// cleared when the player finishes level up +// +// Arguments: +// nClass:int class id +// oPC:object the player object +// nList:int the list we are checking if provided (extra invocations or expanded knowledge) +// +// Returns: +// JsonObject the list of spell ids the class knows in JsonObject format +// +json GetSpellIDsKnown(int nClass, object oPC=OBJECT_SELF, int nList=0); + +// +// ReasonForDisabledSpell +// Provides the reason for why a spell choice is disabled +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook Id +// +// Returns: +// string the reason for the disabled button, empty string otherwise +// +string ReasonForDisabledSpell(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// ReasonForDisabledChosen +// Provides the reason for why a chosen spell button is disabled +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook Id +// +// Returns: +// string the reason for the disabled button, empty string otherwise +// +string ReasonForDisabledChosen(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// GetExpandedChoicesList +// Gets the expanded choices list for a class (the list of expanded knowledge or +// extra invocations). It follows this structure +// +// { +// "spellId:int": TRUE, +// ... +// } +// This is cached to reduce process times and is cleared everytime the window is refreshed/closed +// +// Arguments: +// nClass:int the class id +// +// Returns: +// JsonObject the object representation of the expanded choices +// +json GetExpandedChoicesList(int nClass, object oPC=OBJECT_SELF); + +// +// GetExpandedChoicesList +// Gets the epic expanded choices list for a class (the list of expanded knowledge or +// extra invocations). It follows this structure +// +// { +// "spellId:int": TRUE, +// ... +// } +// This is cached to reduce process times and is cleared everytime the window is refreshed/closed +// +// Arguments: +// nClass:int the class id +// +// Returns: +// JsonObject the object representation of the expanded choices +// +json GetEpicExpandedChoicesList(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingExpandedChoices +// Gets the remaining expanded choices for a class based off list, comparing the +// total number of choices allowed and the total number chosen +// +// Arguments: +// nClass: class id +// nList: the list we are checking (extra invocations/expanded knowledge) +// +// Returns: +// int the amount of choices left +// +int GetRemainingExpandedChoices(int nClass, int nList, object oPC=OBJECT_SELF); +// +// IsSpellInExpandedChoices +// tells if a spell is in the expanded choices list or not +// +// Arguments: +// nClass:int class id +// nList:int the list we are checking (extra invocations/expanded knowledge) +// spellId:int the spell id (not the spellbook id) +// +// Returns +// int:Boolean TRUE if it is a expanded choice, FALSE otherwise +// +int IsSpellInExpandedChoices(int nClass, int nList, int spellId, object oPC=OBJECT_SELF); + +// +// GetChosenReplaceListObject +// The chosen list of spells we wish to replace for PnP replacing if Bioware replacing +// is disabled. This is cached and is cleared when the player is finished leveling +// or resets their choices +// +// Arguments: +// oPC:object the player +// +// Returns: +// json the list of spells chosen to replace +// +json GetChosenReplaceListObject(object oPC=OBJECT_SELF); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Psionics /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +// +// IsExpKnowledgePower +// checks if a spell is a expanded knowledge spell +// +// Arguments: +// nClass:int class id +// spellbookId:int the spellbook Id +// +// Returns: +// int:Boolean TRUE if the spell is a expanded knowledge spell, FALSE otherwise +// +int IsExpKnowledgePower(int nClass, int spellbookId); + +// +// GetExpKnowledgePowerListRequired +// Tells what list the spell should be added to based on if it was added to the +// expanded choices or epic expanded choices list +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook Id +// +// Returns: +// int -1 for the expanded knowledge list, -2 for the epic expanded knowledge +// list, 0 if just add it to the normal class list +// +int GetExpKnowledgePowerListRequired(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// GetCurrentPowerList +// Gets the current chosen powers list. This is cached and is cleared when the +// player either finishs leveling up or resets. +// +// Arguments: +// oPC:object the player object +// +// Returns: +// JsonArray the list of chosen powers wanting to learn +// +json GetCurrentPowerList(object oPC=OBJECT_SELF); + +// +// ShouldAddPower +// Tells if the power should be added to the list of choices or not. It may not +// be added because its an expanded knowledge choice and you have no more expanded +// knowledge slots, or it may be a restricted spell like psions list +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook id +// +// Returns: +// int:Boolean TRUE if it should be added, FALSE otherwise +// +int ShouldAddPower(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// LearnPowers +// learns the list of chosen powers for the player based off their chosen power list +// +// Arguments: +// nClass:int class id +// oPC:object the player object where stored variables are +// +void LearnPowers(int nClass, object oPC=OBJECT_SELF); + +// +// GetMaxPowerLevelForClass +// gets the max power level for the player based off their level and the class's +// known 2da +// +// Arguments: +// nClass:int the class id +// oPC:object the player +// +// Returns: +// int the max power level (circle) the player can achieve on that class +// +int GetMaxPowerLevelForClass(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingPowerChoices +// Gets the remaining power choices the character has at the given chosen circle/power level +// +// Arguments: +// nClass:int class id +// chosenCircle:int the chosen circle/power level +// oPC:object the player +// extra:int should we add the expanded knowledge choices or not +// +// Returns: +// int the number of choices left at the given circle +// +int GetRemainingPowerChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Initiators /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +// +// GetDisciplineInfoObject +// Gets the disciplien info for the given class, telling what the chosen spells +// disicpline is, what type of maneuever it is, the different totals, and prerequisites. +// This is cached and is cleared when the window is refreshed/closed +// +// Argument: +// nClass:int class id +// +// Returns: +// JsonObject the object representation of the chosen spells discipline info +// +json GetDisciplineInfoObject(int nClass, object oPC=OBJECT_SELF); + +// +// HasPreRequisitesForManeuver +// Does the player have the prerequisites for the given spell based off their chosen +// spell list +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook id +// oPC:object the player object with stored variables +// +// Returns: +// int:Boolean, TRUE if you have the prerequisites, FALSE otherwise +// +int HasPreRequisitesForManeuver(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// GetMaxInitiatorCircle +// gets the max circle/level a player can obtain with the given class +// +// Arguments: +// nClass:int the class id +// oPC:object the player +// +// Returns: +// int the highest circle the player can achieve with the class +// +int GetMaxInitiatorCircle(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingManeuverChoices +// Gets remaining maneuever choices for the player +// +// Arguments: +// nClass:int class id +// oPC:object the player +// +// Returns: +// int the remaining maneuevers choices +// +int GetRemainingManeuverChoices(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingStanceChoices +// Gets remaining stance choices for the player +// +// Arguments: +// nClass:int class id +// oPC:object the player +// +// Returns: +// int the remaining stance choices +// +int GetRemainingStanceChoices(int nClass, object oPC=OBJECT_SELF); + +// +// IsRequiredForOtherManeuvers +// Checks the given prerequisite number and the chosen spells to see if removing it +// will cause it to fail the requirement for other maneuevers +// +// Arguments: +// nClass:int the class id +// prereq:int the chosen spells prerequisite number of maneuevers needed +// discipline:string the chosen spells discipline +// +// Returns: +// int:Boolean TRUE if it is required, FALSE otherwise +// +int IsRequiredForOtherManeuvers(int nClass, int prereq, string discipline, object oPC=OBJECT_SELF); + +// +// IsAllowedDiscipline +// checks to see if the given spell is a allowed discipline for a class +// +// Arguments: +// nClass:int class id +// spellbookId:int the spellbook id +// +// Returns: +// int:boolean TRUE if it is allowed, FALSE otherwise +// +int IsAllowedDiscipline(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// AddSpellDisciplineInfo +// Adds the maneuver's discipline info to the class's discpline object +// +// Arguments: +// sFile:string the class's spell 2da +// spellbookId:int the spellbook Id +// classDisc:JsonObject the class discipline object we are adding to +// +// Returns: +// json:Object the classDisc with the given spells information added +// +json AddSpellDisciplineInfo(string sFile, int spellbookId, json classDisc); + +// +// IsRequiredForToBPRCClass +// tells if a given maneuver is needed to satisfy a PRC's prerequsitie +// +// Arguments: +// nClass:int class id +// spellbookId:int the spellbook id +// +// Returns: +// int:Boolean TRUE if the maneuver is required for a PRC, FALSE otherwise. +// +int IsRequiredForToBPRCClass(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Invokers /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +// +// GetInvokerKnownListObject +// gets the invokers known invocations list in object format, needed to tell how many +// of each invocation level does a person know at a given level. This is cached on the +// player and not cleared since it never changes. +// +// Arguments: +// nClass:int class id +// +// Returns: +// json:Object the list of invocations known in json format +// +json GetInvokerKnownListObject(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingInvocationChoices +// Gets the remaining invocation choices left +// +// Arguments: +// nClass:int class id +// chosenCircle:int the chosen circle we are checking +// oPC:Object the player +// extra:int should we count the number of extra invocations we have left +// +// Returns: +// int the amount of choices left at the given circle +// +int GetRemainingInvocationChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE); + +// +// IsExtraChoiceInvocation +// tells if a given spell is a extra invocation choice +// +// Arguments: +// nClass:int class id +// spellbookId:int the spellbook id +// +// Returns: +// int;Boolean TRUE if it is a extra choice, FALSE otherwise +// +int IsExtraChoiceInvocation(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Truenamer /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +// +// GetRemainingTruenameChoices +// gets the remaining truename choices left at the given lexicon type +// +// Arguments: +// nClass:int class id +// nType:int the lexicon +// +// Returns: +// int the amount of truename choices left for the given lexicon +// +int GetRemainingTruenameChoices(int nClass, int nType, object oPC=OBJECT_SELF); + +// +// GetLexiconCircleKnownAtLevel +// gets the known circle level for a given lexicon +// +// Arguments: +// nLevel:int the level to check +// nType:int the lexicon we are checking +// +// Returns: +// int the highest circle we can achieve +// +int GetLexiconCircleKnownAtLevel(int nLevel, int nType); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Archivist /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +json GetArchivistNewSpellsList(object oPC=OBJECT_SELF); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Spont Casters / Base /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +int IsLevelUpNUIOpen(object oPC=OBJECT_SELF) +{ + int nPreviousToken = NuiFindWindow(oPC, NUI_LEVEL_UP_WINDOW_ID); + if (nPreviousToken != 0) + { + return TRUE; + } + + return FALSE; +} + +int IsClassAllowedToUseLevelUpNUI(int nClass) +{ + + if (GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS) + return TRUE; + + if (nClass == CLASS_TYPE_PSYWAR + || nClass == CLASS_TYPE_PSYCHIC_ROGUE + || nClass == CLASS_TYPE_PSION + || nClass == CLASS_TYPE_FIST_OF_ZUOKEN + || nClass == CLASS_TYPE_WILDER + || nClass == CLASS_TYPE_WARMIND) + return TRUE; + + if (nClass == CLASS_TYPE_WARBLADE + || nClass == CLASS_TYPE_SWORDSAGE + || nClass == CLASS_TYPE_CRUSADER) + return TRUE; + + if (nClass == CLASS_TYPE_WARLOCK + || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT + || nClass == CLASS_TYPE_DRAGON_SHAMAN) + return TRUE; + + if (nClass == CLASS_TYPE_SHADOWCASTER + || nClass == CLASS_TYPE_SHADOWSMITH) + return TRUE; + + if (nClass == CLASS_TYPE_TRUENAMER) + return TRUE; + + if (nClass == CLASS_TYPE_ARCHIVIST) + return TRUE; + + return FALSE; +} + +int SpellIsWithinObject(int nClass, int spellbookId, int circleLevel, json spellList, object oPC=OBJECT_SELF) +{ + // check to see if the spell circle isn't empty + json currentList = JsonObjectGet(spellList, IntToString(circleLevel)); + if (currentList == JsonNull()) + return FALSE; + + int totalSpells = JsonGetLength(currentList); + + // then loop through the spell list and find the spell. + int i; + for (i = 0; i < totalSpells; i++) + { + int currentSpell = JsonGetInt(JsonArrayGet(currentList, i)); + if (currentSpell == spellbookId) + return TRUE; + } + + return FALSE; +} + +int AllSpellsAreChosen(int nClass, object oPC=OBJECT_SELF) +{ + // we need the max number of circles a class has. + json spellList = GetSpellListObject(nClass, oPC); + json spellCircles = JsonObjectKeys(spellList); + int totalCircles = JsonGetLength(spellCircles); + + int i; + for (i = 0; i < totalCircles; i++) + { + // loop through each circle and check if you have any remaining choices left + // if you do or you have a deficit then you need to remove or add something + // until you get 0 + int spellCircle = StringToInt(JsonGetString(JsonArrayGet(spellCircles, i))); + int remainingChoices = GetRemainingSpellChoices(nClass, spellCircle, oPC); + if (remainingChoices < 0 || remainingChoices > 0) + return FALSE; + } + + return TRUE; +} + +void AddSpellToChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF) +{ + if (GetIsInvocationClass(nClass)) + { + // get the remaining invocation choices left without extra feats + // if it is 0 then we are adding the chosen invocation to the extra lists + int totalInvocations = GetRemainingInvocationChoices(nClass, spellCircle, oPC, FALSE); + if (totalInvocations == 0) + { + string sFile = GetClassSpellbookFile(nClass); + if (GetRemainingExpandedChoices(nClass, INVOCATION_LIST_EXTRA, oPC)) + { + json expList = GetExpandedChoicesList(nClass, oPC); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + expList = JsonObjectSet(expList, spellId, JsonBool(TRUE)); + SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList); + } + else if (GetRemainingExpandedChoices(nClass, INVOCATION_LIST_EXTRA_EPIC, oPC)) + { + json expList = GetEpicExpandedChoicesList(nClass, oPC); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + expList = JsonObjectSet(expList, spellId, JsonBool(TRUE)); + SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList); + } + } + } + if (GetIsPsionicClass(nClass)) + { + // if the power is a expanded knowledge than we immediatly add it to the + // extra list, otherwise check to make sure we have made all choices in our + // base list first before adding it to the extra list. + if (IsExpKnowledgePower(nClass, spellbookId) + || GetRemainingPowerChoices(nClass, spellCircle, oPC, FALSE) == 0) + { + string sFile = GetClassSpellbookFile(nClass); + if (GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC)) + { + json expList = GetExpandedChoicesList(nClass, oPC); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + expList = JsonObjectSet(expList, spellId, JsonBool(TRUE)); + SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList); + } + else if (GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC)) + { + json expList = GetEpicExpandedChoicesList(nClass, oPC); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + expList = JsonObjectSet(expList, spellId, JsonBool(TRUE)); + SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList); + } + } + + // add the power to the current power list. + json currPowerList = GetCurrentPowerList(oPC); + currPowerList = JsonArrayInsert(currPowerList, JsonInt(spellbookId)); + SetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR, currPowerList); + } + + if (nClass == CLASS_TYPE_ARCHIVIST) + { + json newSpells = GetArchivistNewSpellsList(oPC); + newSpells = JsonArrayInsert(newSpells, JsonInt(spellbookId)); + SetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR, newSpells); + } + + // base logic for spont casters, add the spell to the ChosenSpells JSON object + // by adding it to it's appropriate circle. + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json spellsAtCircle = JsonObjectGet(chosenSpells, IntToString(spellCircle)); + if (spellsAtCircle == JsonNull()) + spellsAtCircle = JsonArray(); + spellsAtCircle = JsonArrayInsert(spellsAtCircle, JsonInt(spellbookId)); + chosenSpells = JsonObjectSet(chosenSpells, IntToString(spellCircle), spellsAtCircle); + SetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR, chosenSpells); + + // if we are not using bioware unlearning logic, then we need to limit the + // amount of spells we can replace. + if (!GetPRCSwitch(PRC_BIO_UNLEARN)) + { + json unlearnList = GetChosenReplaceListObject(oPC); + // if the spell belongs to the unlearn list, then remove it to make room + // for a new spell. + if (JsonObjectGet(unlearnList, IntToString(spellbookId)) != JsonNull()) + { + unlearnList = JsonObjectDel(unlearnList, IntToString(spellbookId)); + SetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR, unlearnList); + } + } +} + +void RemoveSpellFromChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF) +{ + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json spellsAtCircle = JsonObjectGet(chosenSpells, IntToString(spellCircle)); + if (spellsAtCircle == JsonNull()) + spellsAtCircle = JsonArray(); + + int totalSpells = JsonGetLength(spellsAtCircle); + + // find the spell at the circle in the chosen list and remove it. + int i; + for (i = 0; i < totalSpells; i++) + { + if (spellbookId == JsonGetInt(JsonArrayGet(spellsAtCircle, i))) + { + spellsAtCircle = JsonArrayDel(spellsAtCircle, i); + break; + } + } + + chosenSpells = JsonObjectSet(chosenSpells, IntToString(spellCircle), spellsAtCircle); + SetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR, chosenSpells); + + // if we re not using bioware unlearn logic we need to limit how many spells + // can be replaced + if (!GetPRCSwitch(PRC_BIO_UNLEARN)) + { + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json spellListAtCircle = JsonObjectGet(knownSpells, IntToString(spellCircle)); + int totalSpells = JsonGetLength(spellListAtCircle); + + // with the list of known spells, check the selected circle and see if the + // current spell belongs in the already known spell list. + for (i = 0; i < totalSpells; i++) + { + int chosenSpell = JsonGetInt(JsonArrayGet(spellListAtCircle, i)); + if (chosenSpell == spellbookId) + { + // if it does we need to add the spell to the unlearn JSON object to track what spells + // are being replaced. + json unlearnList = GetChosenReplaceListObject(oPC); + unlearnList = JsonObjectSet(unlearnList, IntToString(spellbookId), JsonBool(TRUE)); + SetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR, unlearnList); + break; + } + } + } + + if (GetIsPsionicClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + + // for psionics we need to check if the removed spell was a expanded knowledge choice + // or not. The id of the list is -1 or -2. + int i; + for (i == -1; i >= -2; i--) + { + json expList = (i == -1) ? GetExpandedChoicesList(nClass, oPC) : + GetEpicExpandedChoicesList(nClass, oPC); + + //if the spell belongs in the expanded knowledge list, then we need + // to remove it. + if (JsonObjectGet(expList, spellId) != JsonNull()) + { + expList = JsonObjectDel(expList, spellId); + if (i == POWER_LIST_EXP_KNOWLEDGE) + SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList); + else + SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList); + } + } + + // then we need to remove the power from the selected powers list. + json currPowerChoices = GetCurrentPowerList(oPC); + int totalPowers = JsonGetLength(currPowerChoices); + + for (i = 0; i < totalPowers; i++) + { + if (spellbookId == JsonGetInt(JsonArrayGet(currPowerChoices, i))) + { + currPowerChoices = JsonArrayDel(currPowerChoices, i); + break; + } + } + + SetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR, currPowerChoices); + } + if (GetIsInvocationClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + + // for invocations we need to check if the spell was added to the extra + // invocations list, the list ids are the invalid class id, and -2 + int i; + for (i = 0; i <= 1; i++) + { + json expList = (i == 0) ? GetExpandedChoicesList(nClass, oPC) : + GetEpicExpandedChoicesList(nClass, oPC); + + // if the spell was found, remove it. + if (JsonObjectGet(expList, spellId) != JsonNull()) + { + expList = JsonObjectDel(expList, spellId); + if (i == 0) + SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList); + else + SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList); + } + } + } + if (nClass == CLASS_TYPE_ARCHIVIST) + { + json newSpells = GetArchivistNewSpellsList(oPC); + int totalNew = JsonGetLength(newSpells); + + int i; + for (i = 0; i < totalNew; i++) + { + int newSpellbookId = JsonGetInt(JsonArrayGet(newSpells, i)); + if (newSpellbookId == spellbookId) + { + newSpells = JsonArrayDel(newSpells, i); + SetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR, newSpells); + break; + } + } + } +} + +void OpenNUILevelUpWindow(int nClass, object oPC=OBJECT_SELF) +{ + CloseNUILevelUpWindow(oPC); + // set the NUI to the given classId + int currentClass = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + // we need to clear the cache if it was used before to avoid weird behaviors + ClearLevelUpNUICaches(currentClass, oPC); + // sometimes we are given a different classId instead of the base, we need to + // figure out what the true base class is (mostly true for RHD) + int chosenClass = GetTrueClassType(nClass, oPC); + SetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR, chosenClass); + + ExecuteScript("prc_nui_lv_view", oPC); +} + +int GetTrueClassType(int nClass, object oPC=OBJECT_SELF) +{ + if (nClass == CLASS_TYPE_JADE_PHOENIX_MAGE + || nClass == CLASS_TYPE_MASTER_OF_NINE + || nClass == CLASS_TYPE_DEEPSTONE_SENTINEL + || nClass == CLASS_TYPE_BLOODCLAW_MASTER + || nClass == CLASS_TYPE_RUBY_VINDICATOR + || nClass == CLASS_TYPE_ETERNAL_BLADE + || nClass == CLASS_TYPE_SHADOW_SUN_NINJA) + { + int trueClass = GetPrimaryBladeMagicClass(oPC); + return trueClass; + } + + if ((nClass == CLASS_TYPE_SHAPECHANGER + && GetRacialType(oPC) == RACIAL_TYPE_ARANEA) + || (nClass == CLASS_TYPE_OUTSIDER + && GetRacialType(oPC) == RACIAL_TYPE_RAKSHASA) + || (nClass == CLASS_TYPE_ABERRATION + && GetRacialType(oPC) == RACIAL_TYPE_DRIDER) + || (nClass == CLASS_TYPE_MONSTROUS + && GetRacialType(oPC) == RACIAL_TYPE_ARKAMOI) + || (nClass == CLASS_TYPE_MONSTROUS + && GetRacialType(oPC) == RACIAL_TYPE_HOBGOBLIN_WARSOUL) + || (nClass == CLASS_TYPE_MONSTROUS + && GetRacialType(oPC) == RACIAL_TYPE_REDSPAWN_ARCANISS) + || (nClass == CLASS_TYPE_MONSTROUS + && GetRacialType(oPC) == RACIAL_TYPE_MARRUTACT)) + return CLASS_TYPE_SORCERER; + if (nClass == CLASS_TYPE_FEY + && GetRacialType(oPC) == RACIAL_TYPE_GLOURA) + return CLASS_TYPE_BARD; + + return nClass; +} + +void CloseNUILevelUpWindow(object oPC=OBJECT_SELF) +{ + int currentClass = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + // if we are refreshing the NUI but not finished we need to clear some caching done + // to save computation time as they will need to be reprocessed. + DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(currentClass)); + SetLocalInt(oPC, NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR, -20); + int nPreviousToken = NuiFindWindow(oPC, NUI_LEVEL_UP_WINDOW_ID); + if (nPreviousToken != 0) + { + NuiDestroy(oPC, nPreviousToken); + } +} + +int ShouldSpellButtonBeEnabled(int nClass, int circleLevel, int spellbookId, object oPC=OBJECT_SELF) +{ + // logic for psionics + if (GetIsPsionicClass(nClass)) + { + int maxLevel = GetMaxPowerLevelForClass(nClass, oPC); + if (circleLevel > maxLevel) + return FALSE; + + // if its an expanded knowledge choice and we have already made all our + // exp knowledge choices then it needs to be disabled. + if (IsExpKnowledgePower(nClass, spellbookId)) + { + int remainingExp = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC) + + GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC); + if (!remainingExp) + return FALSE; + } + } + + if (GetIsShadowMagicClass(nClass)) + { + // mysteries are weird, the circles are sectioned by 1-3, 4-6, 7-9 + // if you do not have at least 2 choices from a circle you can't progress up + // so you can have access to circles 1,2,4,7,8 + int nType = 1; + if (circleLevel >= 4 && circleLevel <= 6) + nType = 2; + if (circleLevel >= 7 && circleLevel <= 9) + nType = 3; + int maxPossibleCircle = GetMaxMysteryLevelLearnable(oPC, nClass, nType); + if (circleLevel > maxPossibleCircle) + return FALSE; + } + + if (GetIsTruenamingClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", spellbookId)); + // each lexicon learns at different rates + int maxCircle = GetLexiconCircleKnownAtLevel(GetLevelByClass(nClass, oPC), lexicon); + if (circleLevel > maxCircle) + return FALSE; + + if (GetRemainingTruenameChoices(nClass, lexicon, oPC)) + return TRUE; + return FALSE; + } + + // logic for ToB + if (GetIsBladeMagicClass(nClass)) + { + if (circleLevel > GetMaxInitiatorCircle(nClass, oPC)) + return FALSE; + + // if you do not have the prerequisite amount of maneuevers to learn + // the maneuever, then you can't learn it. + if (!HasPreRequisitesForManeuver(nClass, spellbookId, oPC)) + return FALSE; + + // maneuvers and stances have their own seperate limits + string sFile = GetClassSpellbookFile(nClass); + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (type == MANEUVER_TYPE_BOOST + || type == MANEUVER_TYPE_COUNTER + || type == MANEUVER_TYPE_STRIKE + || type == MANEUVER_TYPE_MANEUVER) + { + int remainingMan = GetRemainingManeuverChoices(nClass, oPC); + if (remainingMan) + return TRUE; + return FALSE; + } + if (type == MANEUVER_TYPE_STANCE) + { + int remainingStance = GetRemainingStanceChoices(nClass, oPC); + if (remainingStance) + return TRUE; + return FALSE; + } + } + + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int maxLevel = GetMaxSpellLevelForCasterLevel(nClass, GetCasterLevelByClass(nClass, oPC)); + if (circleLevel > maxLevel) + return FALSE; + } + + // default logic + // determine remaining Spell/Power choices left for player, if there is any + // remaining, enable the buttons. + if (GetRemainingSpellChoices(nClass, circleLevel, oPC)) + return TRUE; + + return FALSE; +} + +int ShouldAddSpellToSpellButtons(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + string sFile = GetClassSpellbookFile(nClass); + + string spellLevel = Get2DACache(sFile, "Level", spellbookId); + json chosenSpellsAtCircle = JsonObjectGet(chosenSpells, spellLevel); + + int chosenSpellCount = JsonGetLength(chosenSpellsAtCircle); + + // if the spell is in the chosen list, then don't add it to the available list + int i; + for (i = 0; i < chosenSpellCount; i++) + { + int chosenSpellId = JsonGetInt(JsonArrayGet(chosenSpellsAtCircle, i)); + if (chosenSpellId == spellbookId) + return FALSE; + } + + if (GetIsBladeMagicClass(nClass)) + return IsAllowedDiscipline(nClass, spellbookId, oPC); + + // if a psionic class we need to see if the power is a expanded knowledge + // choice and if we should show it or not + if (GetIsPsionicClass(nClass)) + return ShouldAddPower(nClass, spellbookId, oPC); + + // for these set of classes we need to only allow 'advanced learning' + // spells to be added + if (nClass == CLASS_TYPE_BEGUILER + || nClass == CLASS_TYPE_DREAD_NECROMANCER + || nClass == CLASS_TYPE_WARMAGE) + { + int advancedLearning = StringToInt(Get2DACache(sFile, "AL", spellbookId)); + if (advancedLearning) + return TRUE; + return FALSE; + } + + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int nLevel = GetLevelByClass(nClass, oPC); + if ((StringToInt(spellLevel) == 0) && (nLevel == 1)) + return FALSE; + int advancedLearning = StringToInt(Get2DACache(sFile, "AL", spellbookId)); + if (advancedLearning) + return FALSE; + } + + return TRUE; +} + +json GetChosenSpellListObject(int nClass, object oPC=OBJECT_SELF) +{ + json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR); + // if this isn't set yet then we the chosen currently is the known spells + if (retValue == JsonNull()) + { + retValue = GetKnownSpellListObject(nClass, oPC); + SetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR, retValue); + } + + return retValue; +} + +json GetKnownSpellListObject(int nClass, object oPC=OBJECT_SELF) +{ + json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_SPELLS_VAR); + if (retValue == JsonNull()) + retValue = JsonObject(); + else + return retValue; + + string sFile = GetClassSpellbookFile(nClass); + int totalSpells = Get2DARowCount(sFile); + + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int i; + for (i = 0; i < 10; i++) + { + string sSpellbook = GetSpellsKnown_Array(nClass, i); + + int nSize = persistant_array_get_size(oPC, sSpellbook); + + int j; + for (j = 0; j < nSize; j++) + { + int knownSpellbookID = persistant_array_get_int(oPC, sSpellbook, j); + // we store things in a JSON Object where the spell circle + // is the key to a JsonArray of spellbookIds. + json spellList = JsonObjectGet(retValue, IntToString(i)); + if (spellList == JsonNull()) + spellList = JsonArray(); + spellList = JsonArrayInsert(spellList, JsonInt(knownSpellbookID)); + retValue = JsonObjectSet(retValue, IntToString(i), spellList); + } + } + } + else + { + // loop through all the spells in the class's 2da + int i; + for (i = 0; i < totalSpells; i++) + { + int featId = StringToInt(Get2DACache(sFile, "FeatID", i)); + // if you have the feat, you know the spell + if (featId && GetHasFeat(featId, oPC, TRUE)) + { + string spellLevel = Get2DACache(sFile, "Level", i); + int nSpellLevel = StringToInt(spellLevel); + // some spells have **** as their level, so make sure we have + // parsed it correctly + if (IntToString(nSpellLevel) == spellLevel) + { + // we store things in a JSON Object where the spell circle + // is the key to a JsonArray of spellbookIds. + json spellList = JsonObjectGet(retValue, spellLevel); + if (spellList == JsonNull()) + spellList = JsonArray(); + spellList = JsonArrayInsert(spellList, JsonInt(i)); + retValue = JsonObjectSet(retValue, spellLevel, spellList); + } + } + } + } + + SetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_SPELLS_VAR, retValue); + return retValue; +} + +json GetSpellListObject(int nClass, object oPC=OBJECT_SELF) +{ + json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_SPELLBOOK_OBJECT_CACHE_VAR + IntToString(nClass)); + if (retValue == JsonNull()) + retValue = JsonObject(); + else + return retValue; + + string sFile = GetClassSpellbookFile(nClass); + int totalSpells = Get2DARowCount(sFile); + + // loop through all the spells in the 2da and convert it to a JSON Object representation + int i; + for (i = 0; i < totalSpells; i++) + { + string spellLevel = Get2DACache(sFile, "Level", i); + int nSpellLevel = StringToInt(spellLevel); + // some spells in the list have **** as spell level. We need to ignore them + if (IntToString(nSpellLevel) == spellLevel) + { + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int reqFeat = StringToInt(Get2DACache(sFile, "ReqFeat", i)); + if (!reqFeat) + { + json spellList = JsonObjectGet(retValue, spellLevel); + if (spellList == JsonNull()) + spellList = JsonArray(); + spellList = JsonArrayInsert(spellList, JsonInt(i)); + retValue = JsonObjectSet(retValue, spellLevel, spellList); + } + } + else + { + json spellList = JsonObjectGet(retValue, spellLevel); + if (spellList == JsonNull()) + spellList = JsonArray(); + spellList = JsonArrayInsert(spellList, JsonInt(i)); + retValue = JsonObjectSet(retValue, spellLevel, spellList); + } + } + } + + SetLocalJson(oPC, NUI_LEVEL_UP_SPELLBOOK_OBJECT_CACHE_VAR + IntToString(nClass), retValue); + return retValue; +} + +int GetRemainingSpellChoices(int nClass, int circleLevel, object oPC=OBJECT_SELF) +{ + int chosenCircle = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR); + int remainingChoices = 0; + + // we only want to cache on the current circle. + if (chosenCircle == circleLevel) + { + remainingChoices = GetLocalInt(oPC, NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR); + // -20 is the chosen number to say there is no cache set since 0 is + // a valid option + if (remainingChoices != -20) + return remainingChoices; + } + + // logic for psionics + if (GetIsPsionicClass(nClass)) + remainingChoices = GetRemainingPowerChoices(nClass, circleLevel, oPC); + + // logic for ToB + if (GetIsBladeMagicClass(nClass)) + remainingChoices = (GetRemainingManeuverChoices(nClass, oPC) + + GetRemainingStanceChoices(nClass, oPC)); + + // logic for Invokers + if (GetIsInvocationClass(nClass)) + remainingChoices = GetRemainingInvocationChoices(nClass, circleLevel, oPC); + + // logic for mysteries + if (GetIsShadowMagicClass(nClass)) + { + int totalChosen = 0; + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json circles = JsonObjectKeys(chosenSpells); + int totalCircles = JsonGetLength(circles); + + int i; + for (i = 0; i < totalCircles; i++) + { + // loop through each circle and add its total spells together since + // we don't care about where you spend your spells, only the amount + string currentCircle = JsonGetString(JsonArrayGet(circles, i)); + json spellList = JsonObjectGet(chosenSpells, currentCircle); + if (spellList != JsonNull()) + totalChosen += JsonGetLength(spellList); + } + + int maxKnown = GetMaxMysteryCount(oPC, nClass); + remainingChoices = (maxKnown - totalChosen); + } + + if (GetIsTruenamingClass(nClass)) + remainingChoices = GetRemainingTruenameChoices(nClass, -1, oPC); + + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int nLevel = GetLevelByClass(CLASS_TYPE_ARCHIVIST, oPC); + int spellsAvailable; + if (nLevel == 1) + spellsAvailable = (3 + GetAbilityModifier(ABILITY_INTELLIGENCE, oPC)); + else + spellsAvailable = 2; + + json newSpells = GetArchivistNewSpellsList(oPC); + int totalNewSpells = JsonGetLength(newSpells); + remainingChoices = (spellsAvailable - totalNewSpells); + } + if (GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS) + { + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + int totalSpellsKnown = 0; + int casterLevel = GetCasterLevelByClass(nClass, oPC); + + // these specific classes only learn at specific rates + int advancedLearning = 0; + // beguiler learns every 4th level starting on 3 + if (nClass == CLASS_TYPE_BEGUILER) + advancedLearning = ((casterLevel+1)/4); + // dread learns every 4th level + if (nClass == CLASS_TYPE_DREAD_NECROMANCER) + advancedLearning = (casterLevel/4); + // warmage is a bastard child that choses when it learns a spell whenever + // it decides it feels like it wants to + if (nClass == CLASS_TYPE_WARMAGE) + { + if (casterLevel >= 3) // 1 choice + advancedLearning++; + if (casterLevel >= 6) // 2 choice + advancedLearning++; + if (casterLevel >= 11) // 3 choice + advancedLearning++; + if (casterLevel >= 16) // 4 choice + advancedLearning++; + if (casterLevel >= 24) // 5 choice + advancedLearning++; + if (casterLevel >= 28) // 6 choice + advancedLearning++; + if (casterLevel >= 32) // 7 choice + advancedLearning++; + if (casterLevel >= 36) // 8 choice + advancedLearning++; + if (casterLevel >= 40) // 9 choice + advancedLearning++; + } + + if (advancedLearning) + { + int maxSpellLevel = GetMaxSpellLevelForCasterLevel(nClass, casterLevel); + // can't learn what you can't achieve + if (circleLevel > maxSpellLevel) + remainingChoices = 0; + else + { + int chosenSpellsAmount = 0; + + json circles = JsonObjectKeys(chosenSpells); + int totalCircles = JsonGetLength(circles); + string sFile = GetClassSpellbookFile(nClass); + + int i; + for (i = 0; i <= totalCircles; i++) + { + string currentCircle = JsonGetString(JsonArrayGet(circles, i)); + json spellList = JsonObjectGet(chosenSpells, currentCircle); + if ((spellList != JsonNull())) + { + // loop through the spells of a given circle and count how + // many advanced learning spells you know + int numOfSpells = JsonGetLength(spellList); + int j; + for (j = 0; j < numOfSpells; j++) + { + int nSpellbookID = JsonGetInt(JsonArrayGet(spellList, j)); + int isAL = StringToInt(Get2DACache(sFile, "AL", nSpellbookID)); + if (isAL) + chosenSpellsAmount++; + } + } + } + + remainingChoices = (advancedLearning - chosenSpellsAmount); + } + } + else + { + // default logic for spont casters + totalSpellsKnown = GetSpellKnownMaxCount(casterLevel, circleLevel, nClass, oPC); + // Favoured Soul has more 0 choices than there are spells for some reason + if (nClass == CLASS_TYPE_FAVOURED_SOUL && circleLevel == 0 && totalSpellsKnown > 6) + totalSpellsKnown = 6; + + // logic for spont casters + json selectedCircle = JsonObjectGet(chosenSpells, IntToString(circleLevel)); + if (selectedCircle == JsonNull()) + return totalSpellsKnown; + + int selectedSpellCount = JsonGetLength(selectedCircle); + remainingChoices = (totalSpellsKnown - selectedSpellCount); + } + } + + if (chosenCircle == circleLevel) + SetLocalInt(oPC, NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR, remainingChoices); + return remainingChoices; +} + +void FinishLevelUp(int nClass, object oPC=OBJECT_SELF) +{ + RemoveSpells(nClass, oPC); + LearnSpells(nClass, oPC); + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int nLevel = GetLevelByClass(nClass, oPC); + SetPersistantLocalInt(oPC, "LastSpellGainLevel", nLevel); + } + ClearLevelUpNUICaches(nClass, oPC); +} + +void ClearLevelUpNUICaches(int nClass, object oPC=OBJECT_SELF) +{ + // clear the chosen spells you made + DeleteLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR); + // clear the known spells you have + DeleteLocalJson(oPC, NUI_LEVEL_UP_KNOWN_SPELLS_VAR); + SetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR, -1); + DeleteLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + // clear the psionics selected choices + DeleteLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR); + // clear the expanded choices for psionics and invokers + DeleteLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR); + // clear the PnP replace list + DeleteLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR); + // for invocation and psionics we grab the list of known extra spells and cache it + // so we need to clear those caches + if (GetIsInvocationClass(nClass)) + { + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_0"); + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(INVOCATION_LIST_EXTRA)); + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(INVOCATION_LIST_EXTRA_EPIC)); + } + if (GetIsPsionicClass(nClass)) + { + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_0"); + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(POWER_LIST_EXP_KNOWLEDGE)); + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(POWER_LIST_EPIC_EXP_KNOWLEDGE)); + } + // for ToB we need to clear all the discipline info for determining PrC choice validity + if (GetIsBladeMagicClass(nClass)) + { + DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(CLASS_TYPE_SWORDSAGE)); + DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(CLASS_TYPE_WARBLADE)); + DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(CLASS_TYPE_CRUSADER)); + } +} + +void RemoveSpells(int nClass, object oPC=OBJECT_SELF) +{ + // we don't remove on psionic classes and archivist + if (GetIsPsionicClass(nClass) || nClass == CLASS_TYPE_ARCHIVIST) + return; + + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json spellCircles = JsonObjectKeys(knownSpells); + int totalCircles = JsonGetLength(spellCircles); + + // loop through all the known spells circles + int i; + for (i = 0; i < totalCircles; i++) + { + string sSpellLevel = JsonGetString(JsonArrayGet(spellCircles, i)); + int nSpellLevel = StringToInt(sSpellLevel); + + json chosenCircle = JsonObjectGet(knownSpells, sSpellLevel); + int totalSpells = JsonGetLength(chosenCircle); + + // loop through the spell list at the given circle + int y; + for (y = 0; y < totalSpells; y++) + { + int nSpellbookID = JsonGetInt(JsonArrayGet(chosenCircle, y)); + // if the spell is not a chosen spell, then it was removed + if (!SpellIsWithinObject(nClass, nSpellbookID, nSpellLevel, chosenSpells, oPC)) + { + if (GetIsInvocationClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", nSpellbookID); + int chosenList = 0; + // check to see if its a extra invocation choice and set it's chosen list + if (GetHasFeat(FEAT_EXTRA_INVOCATION_I, oPC)) + { + json expList = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA); + if (JsonObjectGet(expList, spellId) != JsonNull()) + chosenList = INVOCATION_LIST_EXTRA; + } + if (GetHasFeat(FEAT_EPIC_EXTRA_INVOCATION_I, oPC)) + { + json expList = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA_EPIC); + if (JsonObjectGet(expList, spellId) != JsonNull()) + chosenList = INVOCATION_LIST_EXTRA_EPIC; + } + RemoveSpellKnown(nClass, nSpellbookID, oPC, chosenList); + } + if (GetIsBladeMagicClass(nClass) || GetIsShadowMagicClass(nClass)) + RemoveSpellKnown(nClass, nSpellbookID, oPC); + + if (GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS) + { + string sFile = GetClassSpellbookFile(nClass); + string sSpellBook = GetSpellsKnown_Array(nClass); + // remove the spell from the spellbook + array_extract_int(oPC, sSpellBook, nSpellbookID); + // wipe the spell from the player + int ipFeatID = StringToInt(Get2DACache(sFile, "IPFeatID", nSpellbookID)); + WipeSpellFromHide(ipFeatID, oPC); + } + } + } + } +} + +void LearnSpells(int nClass, object oPC=OBJECT_SELF) +{ + if (GetIsPsionicClass(nClass)) + { + LearnPowers(nClass, oPC); + return; + } + + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json spellCircles = JsonObjectKeys(chosenSpells); + int totalCircles = JsonGetLength(spellCircles); + + // loop through chosen spells circles + int i; + for (i = 0; i < totalCircles; i++) + { + string sSpellLevel = JsonGetString(JsonArrayGet(spellCircles, i)); + int nSpellLevel = StringToInt(sSpellLevel); + + json chosenCircle = JsonObjectGet(chosenSpells, sSpellLevel); + int totalSpells = JsonGetLength(chosenCircle); + + // loop through the spell list at the circle + int y; + for (y = 0; y < totalSpells; y++) + { + int nSpellbookID = JsonGetInt(JsonArrayGet(chosenCircle, y)); + // if the spell is not in the known spell list then it was newly added + if (!SpellIsWithinObject(nClass, nSpellbookID, nSpellLevel, knownSpells, oPC)) + { + if (GetIsTruenamingClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + // find out what lexicon it belongs to and add it to that + int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", nSpellbookID)); + AddUtteranceKnown(oPC, nClass, nSpellbookID, lexicon, TRUE, GetHitDice(oPC)); + } + + if (GetIsShadowMagicClass(nClass)) + AddMysteryKnown(oPC, nClass, nSpellbookID, TRUE, GetHitDice(oPC)); + + if (GetIsInvocationClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", nSpellbookID); + int chosenList = nClass; + json expList = GetExpandedChoicesList(nClass, oPC); + // if the invocation belongs to the extra or epic extra list + // then we need to provide those list ids instead. + if (JsonObjectGet(expList, spellId) != JsonNull()) + chosenList = INVOCATION_LIST_EXTRA; + expList = GetEpicExpandedChoicesList(nClass, oPC); + if (JsonObjectGet(expList, spellId) != JsonNull()) + chosenList = INVOCATION_LIST_EXTRA_EPIC; + AddInvocationKnown(oPC, chosenList, nSpellbookID, TRUE, GetHitDice(oPC)); + } + + if (GetIsBladeMagicClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + int maneuverType = StringToInt(Get2DACache(sFile, "Type", nSpellbookID)); + // we save our moves either to stance or maneuever + if (maneuverType != MANEUVER_TYPE_STANCE) + maneuverType = MANEUVER_TYPE_MANEUVER; + + AddManeuverKnown(oPC, nClass, nSpellbookID, maneuverType, TRUE, GetHitDice(oPC)); + } + int nSpellbookType = GetSpellbookTypeForClass(nClass); + if (nSpellbookType == SPELLBOOK_TYPE_SPONTANEOUS + || nClass == CLASS_TYPE_ARCHIVIST) + { + // these classes have their own syste, + if (nClass == CLASS_TYPE_BEGUILER + || nClass == CLASS_TYPE_DREAD_NECROMANCER + || nClass == CLASS_TYPE_WARMAGE) + { + int casterLevel = GetCasterLevelByClass(nClass, oPC); + // this is taken from prc_s_spellgain as it is coupled with the + // dynamic dialogue system + int advancedLearning = 0; + // beguilers learn every 4th level starting on 3rd + if (nClass == CLASS_TYPE_BEGUILER) + advancedLearning = ((casterLevel+1)/4); + // dread learns every 4th level + if (nClass == CLASS_TYPE_DREAD_NECROMANCER) + advancedLearning = (casterLevel/4); + if (nClass == CLASS_TYPE_WARMAGE) + { + if (casterLevel >= 3) + advancedLearning++; + } + + if (advancedLearning) + { + // incremenet the total advanced learning known + int nAdvLearn = GetPersistantLocalInt(oPC, "AdvancedLearning_"+IntToString(nClass)); + nAdvLearn++; + SetPersistantLocalInt(oPC, "AdvancedLearning_"+IntToString(nClass), nAdvLearn); + } + } + + // get location of persistant storage on the hide + string sSpellbook = GetSpellsKnown_Array(nClass, nSpellLevel); + //object oToken = GetHideToken(oPC); + + // Create spells known persistant array if it is missing + int nSize = persistant_array_get_size(oPC, sSpellbook); + if (nSize < 0) + { + persistant_array_create(oPC, sSpellbook); + nSize = 0; + } + + // Mark the spell as known (e.g. add it to the end of oPCs spellbook) + persistant_array_set_int(oPC, sSpellbook, nSize, nSpellbookID); + + if (nSpellbookType == SPELLBOOK_TYPE_SPONTANEOUS) + { + // add spell + string sFile = GetClassSpellbookFile(nClass); + string sArrayName = "NewSpellbookMem_" + IntToString(nClass); + int featId = StringToInt(Get2DACache(sFile, "FeatID", nSpellbookID)); + int ipFeatID = StringToInt(Get2DACache(sFile, "IPFeatID", nSpellbookID)); + AddSpellUse(oPC, nSpellbookID, nClass, sFile, sArrayName, nSpellbookType, GetPCSkin(oPC), featId, ipFeatID); + } + } + } + } + } +} + +int EnableChosenButton(int nClass, int spellbookId, int circleLevel, object oPC=OBJECT_SELF) +{ + if (GetIsPsionicClass(nClass) + || GetIsShadowMagicClass(nClass) + || GetIsTruenamingClass(nClass) + || nClass == CLASS_TYPE_DREAD_NECROMANCER + || nClass == CLASS_TYPE_BEGUILER + || nClass == CLASS_TYPE_WARMAGE + || nClass == CLASS_TYPE_ARCHIVIST) + { + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel)); + int totalSpells = JsonGetLength(currentCircle); + int i; + for (i = 0; i < totalSpells; i++) + { + // if spell belongs to known spells, then disable, we don't allow + // replacing for these classes. + int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i)); + if (currentSpellbookId == spellbookId) + return FALSE; + } + + } + + if (GetIsBladeMagicClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + int prereqs = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId)); + string discipline = Get2DACache(sFile, "Discipline", spellbookId); + // if the maneuver is required for others to exist, t hen disable it + if (IsRequiredForOtherManeuvers(nClass, prereqs, discipline, oPC)) + return FALSE; + // if it is required for a PRC to exist, then disable it. + if (IsRequiredForToBPRCClass(nClass, spellbookId, oPC)) + return FALSE; + } + + if (GetIsInvocationClass(nClass)) + { + // dragon Shamans can't replace + if (nClass == CLASS_TYPE_DRAGON_SHAMAN) + { + json invokKnown = GetSpellIDsKnown(nClass, oPC); + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + json chosenSpell = JsonObjectGet(invokKnown, spellId); + if (chosenSpell != JsonNull()) + return FALSE; + } + } + + // If we do not use the bioware unlearn system, we follow PnP + if (!GetPRCSwitch(PRC_BIO_UNLEARN)) + { + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel)); + int totalSpells = JsonGetLength(currentCircle); + int i; + for (i = 0; i < totalSpells; i++) + { + int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i)); + // if the spell belongs to the known spell list, then we need to determine + if (currentSpellbookId == spellbookId) + { + json unlearnList = GetChosenReplaceListObject(oPC); + int totalUnlearned = JsonGetLength(unlearnList); + int totalAllowed = GetPRCSwitch(PRC_UNLEARN_SPELL_MAXNR); + // we default to 1 if no max number of unlearns is set + if (!totalAllowed) + totalAllowed = 1; + // we cannot replace a spell if we have more than or equal to the + // amount of relearns allowed, therefore disable. + return totalUnlearned < totalAllowed; + } + } + + } + + return TRUE; +} + +void ResetChoices(object oPC=OBJECT_SELF) +{ + // reset choices made so far + DeleteLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR); +} + +void RemoveSpellKnown(int nClass, int spellbookId, object oPC=OBJECT_SELF, int nList=0) +{ + string sBase; + string levelArrayBaseId; + string generalArrayBaseId; + string totalCountId; + + string sFile = GetClassSpellbookFile(nClass); + int chosenList = (nList != 0) ? nList : nClass; + int spellID = StringToInt(Get2DACache(sFile, "SpellID", spellbookId)); + + // if statements are to change the location of the spellbook we are grabbing + if (GetIsShadowMagicClass(nClass)) + { + sBase = _MYSTERY_LIST_NAME_BASE + IntToString(chosenList); + levelArrayBaseId = _MYSTERY_LIST_LEVEL_ARRAY; + generalArrayBaseId = _MYSTERY_LIST_GENERAL_ARRAY; + totalCountId = _MYSTERY_LIST_TOTAL_KNOWN; + } + + if (GetIsInvocationClass(nClass)) + { + sBase = _INVOCATION_LIST_NAME_BASE + IntToString(chosenList); + levelArrayBaseId = _INVOCATION_LIST_LEVEL_ARRAY; + generalArrayBaseId = _INVOCATION_LIST_GENERAL_ARRAY; + totalCountId = _INVOCATION_LIST_TOTAL_KNOWN; + } + + if (GetIsBladeMagicClass(nClass)) + { + int maneuverType = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (maneuverType != MANEUVER_TYPE_STANCE) maneuverType = MANEUVER_TYPE_MANEUVER; + sBase = _MANEUVER_LIST_NAME_BASE + IntToString(chosenList) + IntToString(maneuverType); + levelArrayBaseId = _MANEUVER_LIST_LEVEL_ARRAY; + generalArrayBaseId = _MANEUVER_LIST_GENERAL_ARRAY; + totalCountId = _MANEUVER_LIST_TOTAL_KNOWN; + } + + if (GetIsTruenamingClass(nClass)) + { + string lexicon = Get2DACache(sFile, "Lexicon", spellbookId); + sBase = _UTTERANCE_LIST_NAME_BASE + IntToString(chosenList) + lexicon; + levelArrayBaseId = _UTTERANCE_LIST_LEVEL_ARRAY; + generalArrayBaseId = _UTTERANCE_LIST_GENERAL_ARRAY; + totalCountId = _UTTERANCE_LIST_TOTAL_KNOWN; + } + + string sTestArray; + + int found = FALSE; + + int i; + for (i = 1; i <= GetHitDice(oPC); i++) + { + sTestArray = sBase + levelArrayBaseId + IntToString(i); + if (persistant_array_exists(oPC, sTestArray)) + { + // if we found the spell, then we remove it. + if (persistant_array_extract_int(oPC, sTestArray, spellID) >= 0) + { + found = TRUE; + break; + } + } + } + + if (!found) + { + // if not found we check the general list where spells aren't set to a level. + sTestArray = sBase + generalArrayBaseId; + if (persistant_array_exists(oPC, sTestArray)) + { + //if we could not find the spell here, something went wrong + if (persistant_array_extract_int(oPC, sTestArray, spellID) < 0) + { + SendMessageToPC(oPC, "Could not find spellID " + IntToString(spellID) + " in the class's spellbook!"); + return; + } + } + } + + // decrement the amount of spells known. + SetPersistantLocalInt(oPC, sBase + totalCountId, + GetPersistantLocalInt(oPC, sBase + totalCountId) - 1 + ); + + // if ToB we need to decrement the specific discipline as well. + if (GetIsBladeMagicClass(nClass)) + { + int maneuverType = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (maneuverType == MANEUVER_TYPE_BOOST + || maneuverType == MANEUVER_TYPE_COUNTER + || maneuverType == MANEUVER_TYPE_STRIKE + || maneuverType == MANEUVER_TYPE_MANEUVER) + maneuverType = MANEUVER_TYPE_MANEUVER; + string sDisciplineArray = _MANEUVER_LIST_DISCIPLINE + IntToString(maneuverType) + "_" + Get2DACache(sFile, "Discipline", spellbookId); + SetPersistantLocalInt(oPC, sDisciplineArray, + GetPersistantLocalInt(oPC, sDisciplineArray) - 1); + } + + // remove spell from player + int ipFeatID = StringToInt(Get2DACache(sFile, "IPFeatID", spellbookId)); + itemproperty ipFeat = PRCItemPropertyBonusFeat(ipFeatID); + object oSkin = GetPCSkin(oPC); + RemoveItemProperty(oSkin, ipFeat); + CheckAndRemoveFeat(oSkin, ipFeat); +} + +json GetSpellIDsKnown(int nClass, object oPC=OBJECT_SELF, int nList=0) +{ + json spellIds = GetLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(nList)); + if (spellIds == JsonNull()) + spellIds = JsonObject(); + else + return spellIds; + + string sBase; + string levelArrayBaseId; + string generalArrayBaseId; + // if we are given a listId then use that instead, used for extra choices and + // expanded knowledge + int chosenList = (nList != 0) ? nList : nClass; + // these if checks are for setting class specific ids + if (nClass == CLASS_TYPE_DRAGON_SHAMAN + || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT + || nClass == CLASS_TYPE_WARLOCK) + { + sBase = _INVOCATION_LIST_NAME_BASE + IntToString(chosenList); + levelArrayBaseId = _INVOCATION_LIST_LEVEL_ARRAY; + generalArrayBaseId = _INVOCATION_LIST_GENERAL_ARRAY; + } + if (GetIsPsionicClass(nClass)) + { + sBase = _POWER_LIST_NAME_BASE + IntToString(chosenList); + levelArrayBaseId = _POWER_LIST_LEVEL_ARRAY; + generalArrayBaseId = _POWER_LIST_GENERAL_ARRAY; + } + + // go through the level list and translate the spellIds into a JSON Object + // structure for easier access. + int i; + for (i = 1; i <= GetHitDice(oPC); i++) + { + string sTestArray = sBase + levelArrayBaseId + IntToString(i); + if (persistant_array_exists(oPC, sTestArray)) + { + int nSize = persistant_array_get_size(oPC, sTestArray); + + int j; + for (j = 0; j < nSize; j++) + { + spellIds = JsonObjectSet(spellIds, IntToString(persistant_array_get_int(oPC, sTestArray, j)), JsonBool(TRUE)); + } + } + } + + // go through the general list and translate the spellIds into a JSON Object + // structure for easier access. + string sTestArray = sBase + generalArrayBaseId; + if (persistant_array_exists(oPC, sTestArray)) + { + int nSize = persistant_array_get_size(oPC, sTestArray); + + int j; + for (j = 0; j < nSize; j++) + { + spellIds = JsonObjectSet(spellIds, IntToString(persistant_array_get_int(oPC, sTestArray, j)), JsonBool(TRUE)); + } + } + + SetLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(nList), spellIds); + return spellIds; +} + +string ReasonForDisabledSpell(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + int circleLevel = StringToInt(Get2DACache(sFile, "Level", spellbookId)); + + // logic for psionics + if (GetIsPsionicClass(nClass)) + { + int maxLevel = GetMaxPowerLevelForClass(nClass, oPC); + if (circleLevel > maxLevel) + return "You are unable to learn at this level currently."; + + // if its an expanded knowledge choice and we have already made all our + // exp knowledge choices then it needs to be disabled. + if (IsExpKnowledgePower(nClass, spellbookId)) + { + int remainingExp = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC) + + GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC); + if (!remainingExp) + return "You have no more expanded knowledge choices left."; + } + } + + if (GetIsShadowMagicClass(nClass)) + { + // mysteries are weird, the circles are sectioned by 1-3, 4-6, 7-9 + // if you do not have at least 2 choices from a circle you can't progress up + // so you can have access to circles 1,2,4,7,8 + int nType = 1; + if (circleLevel >= 4 && circleLevel <= 6) + nType = 2; + if (circleLevel >= 7 && circleLevel <= 9) + nType = 3; + int maxPossibleCircle = GetMaxMysteryLevelLearnable(oPC, nClass, nType); + if (circleLevel > maxPossibleCircle) + return "You are unable to learn at this level currently."; + } + + if (GetIsTruenamingClass(nClass)) + { + int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", spellbookId)); + // each lexicon learns at different rates + int maxCircle = GetLexiconCircleKnownAtLevel(GetLevelByClass(nClass, oPC), lexicon); + if (circleLevel > maxCircle) + return "You are unable to learn at this level currently."; + + if (GetRemainingTruenameChoices(nClass, lexicon, oPC)) + return ""; + return "You have made all your truenaming choices."; + } + + // logic for ToB + if (GetIsBladeMagicClass(nClass)) + { + if (circleLevel > GetMaxInitiatorCircle(nClass, oPC)) + return "You are unable to learn at this level currently."; + + // if you do not have the prerequisite amount of maneuevers to learn + // the maneuever, then you can't learn it. + if (!HasPreRequisitesForManeuver(nClass, spellbookId, oPC)) + return "You do not have the prerequisites for this maneuver."; + + // maneuvers and stances have their own seperate limits + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (type == MANEUVER_TYPE_BOOST + || type == MANEUVER_TYPE_COUNTER + || type == MANEUVER_TYPE_STRIKE + || type == MANEUVER_TYPE_MANEUVER) + { + int remainingMan = GetRemainingManeuverChoices(nClass, oPC); + if (remainingMan) + return ""; + return "You have made all your maneuver choices."; + } + if (type == MANEUVER_TYPE_STANCE) + { + int remainingStance = GetRemainingStanceChoices(nClass, oPC); + if (remainingStance) + return ""; + return "You have made all your stance choices."; + } + } + + // default logic + // determine remaining Spell/Power choices left for player, if there is any + // remaining, enable the buttons. + if (GetRemainingSpellChoices(nClass, circleLevel, oPC)) + return ""; + + return "You have made all your spell choices."; +} + +string ReasonForDisabledChosen(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + int circleLevel = StringToInt(Get2DACache(sFile, "Level", spellbookId)); + + + if (GetIsPsionicClass(nClass) + || GetIsShadowMagicClass(nClass) + || GetIsTruenamingClass(nClass) + || nClass == CLASS_TYPE_DREAD_NECROMANCER + || nClass == CLASS_TYPE_BEGUILER + || nClass == CLASS_TYPE_WARMAGE) + { + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel)); + int totalSpells = JsonGetLength(currentCircle); + int i; + for (i = 0; i < totalSpells; i++) + { + // if spell belongs to known spells, then disable, we don't allow + // replacing for these classes. + int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i)); + if (currentSpellbookId == spellbookId) + return "You cannot replace spells as this class."; + } + + } + + if (GetIsBladeMagicClass(nClass)) + { + int prereqs = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId)); + string discipline = Get2DACache(sFile, "Discipline", spellbookId); + // if the maneuver is required for others to exist, t hen disable it + if (IsRequiredForOtherManeuvers(nClass, prereqs, discipline, oPC)) + return "This maneuver is required for another maneuver."; + // if it is required for a PRC to exist, then disable it. + if (IsRequiredForToBPRCClass(nClass, spellbookId, oPC)) + return "This maneuver is reuquired for a PRC class."; + } + + if (GetIsInvocationClass(nClass)) + { + // dragon Shamans can't replace + if (nClass == CLASS_TYPE_DRAGON_SHAMAN) + { + json invokKnown = GetSpellIDsKnown(nClass, oPC); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + json chosenSpell = JsonObjectGet(invokKnown, spellId); + if (chosenSpell != JsonNull()) + return "You cannot replace invocations as this class."; + } + } + + // If we do not use the bioware unlearn system, we follow PnP + if (!GetPRCSwitch(PRC_BIO_UNLEARN)) + { + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel)); + int totalSpells = JsonGetLength(currentCircle); + int i; + for (i = 0; i < totalSpells; i++) + { + int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i)); + // if the spell belongs to the known spell list, then we need to determine + if (currentSpellbookId == spellbookId) + { + json unlearnList = GetChosenReplaceListObject(oPC); + int totalUnlearned = JsonGetLength(unlearnList); + int totalAllowed = GetPRCSwitch(PRC_UNLEARN_SPELL_MAXNR); + // we default to 1 if no max number of unlearns is set + if (!totalAllowed) + totalAllowed = 1; + // we cannot replace a spell if we have more than or equal to the + // amount of relearns allowed, therefore disable. + if (totalUnlearned < totalAllowed) + return ""; + return "You can only replace " + IntToString(totalAllowed) + " spells during level up."; + } + } + + } + + return ""; +} + +json GetExpandedChoicesList(int nClass, object oPC=OBJECT_SELF) +{ + json expandedChoices = GetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR); + if (expandedChoices != JsonNull()) + return expandedChoices; + + if (GetIsPsionicClass(nClass)) + expandedChoices = GetSpellIDsKnown(nClass, oPC, POWER_LIST_EXP_KNOWLEDGE); + else + expandedChoices = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA); + + SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expandedChoices); + return expandedChoices; +} + +int GetRemainingExpandedChoices(int nClass, int nList, object oPC=OBJECT_SELF) +{ + int remainingChoices = 0; + + json expandedList = (nList == INVOCATION_LIST_EXTRA || nList == POWER_LIST_EXP_KNOWLEDGE ) ? GetExpandedChoicesList(nClass, oPC) + : GetEpicExpandedChoicesList(nClass, oPC); + int expChoicesCount = JsonGetLength(JsonObjectKeys(expandedList)); + int maxExpChoices = (GetIsPsionicClass(nClass)) ? GetMaxPowerCount(oPC, nList) + : GetMaxInvocationCount(oPC, nList); + remainingChoices += (maxExpChoices - expChoicesCount); + + return remainingChoices; +} + +json GetEpicExpandedChoicesList(int nClass, object oPC=OBJECT_SELF) +{ + json expandedChoices = GetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR); + if (expandedChoices != JsonNull()) + return expandedChoices; + + if (GetIsPsionicClass(nClass)) + expandedChoices = GetSpellIDsKnown(nClass, oPC, POWER_LIST_EPIC_EXP_KNOWLEDGE); + else + expandedChoices = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA_EPIC); + + SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expandedChoices); + return expandedChoices; +} + +int IsSpellInExpandedChoices(int nClass, int nList, int spellId, object oPC=OBJECT_SELF) +{ + json expList = (nList == POWER_LIST_EXP_KNOWLEDGE || nList == INVOCATION_LIST_EXTRA) ? GetExpandedChoicesList(nClass, oPC) + : GetEpicExpandedChoicesList(nClass, oPC); + + return (JsonObjectGet(expList, IntToString(spellId)) != JsonNull()); +} + +json GetChosenReplaceListObject(object oPC=OBJECT_SELF) +{ + json replaceList = GetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR); + if (replaceList == JsonNull()) + replaceList = JsonObject(); + else + return replaceList; + + SetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR, replaceList); + return replaceList; +} + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Psionics /// +/// /// +//////////////////////////////////////////////////////////////////////////// + + +int IsExpKnowledgePower(int nClass, int spellbookId) +{ + string sFile = GetClassSpellbookFile(nClass); + int isExp = StringToInt(Get2DACache(sFile, "Exp", spellbookId)); + return isExp; +} + +json GetCurrentPowerList(object oPC=OBJECT_SELF) +{ + json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR); + if (retValue == JsonNull()) + retValue = JsonArray(); + else + return retValue; + + SetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR, retValue); + return retValue; +} + +int ShouldAddPower(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + int featId = StringToInt(Get2DACache(sFile, "FeatID", spellbookId)); + int isExp = StringToInt(Get2DACache(sFile, "Exp", spellbookId)); + // if you don't have the prereqs for a power then don't add it. Specific for + // psions + if (!CheckPowerPrereqs(featId, oPC)) + return FALSE; + // if the power is a expanded knowledge power + if (isExp) + { + // and we have a expanded knowledge choice left to make then show + // the button + int addPower = FALSE; + int maxLevel = GetMaxPowerLevelForClass(nClass, oPC); + int currentCircle = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR); + + int choicesLeft = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC); + if (choicesLeft && (currentCircle <= (maxLevel-1))) + addPower = TRUE; + choicesLeft = GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC); + if (choicesLeft) + addPower = TRUE; + // otherwise don't show the button. + return addPower; + } + + return TRUE; +} + +void LearnPowers(int nClass, object oPC=OBJECT_SELF) +{ + // add normal powers + json powerList = GetCurrentPowerList(oPC); + int totalPowers = JsonGetLength(powerList); + int i; + for (i = 0; i < totalPowers; i++) + { + int nSpellbookID = JsonGetInt(JsonArrayGet(powerList, i)); + // get the expanded knowledge list we are adding to if any + int expKnow = GetExpKnowledgePowerListRequired(nClass, nSpellbookID, oPC); + AddPowerKnown(oPC, nClass, nSpellbookID, TRUE, GetManifesterLevel(oPC, nClass, TRUE), expKnow); + } +} + +int GetExpKnowledgePowerListRequired(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + + int i; + // expanded knowledge is -1, epic epxanded knowledge is -2 + for (i = -1; i >= -2; i--) + { + int spellId = StringToInt(Get2DACache(sFile, "SpellID", spellbookId)); + if (IsSpellInExpandedChoices(nClass, i, spellId, oPC)) + return i; + } + + return 0; +} + +int GetMaxPowerLevelForClass(int nClass, object oPC=OBJECT_SELF) +{ + string sFile = GetAMSKnownFileName(nClass); + int nLevel = GetManifesterLevel(oPC, nClass, TRUE); + // index is level - 1 since it starts at 0. + int maxLevel = StringToInt(Get2DACache(sFile, "MaxPowerLevel", nLevel-1)); + return maxLevel; +} + +int GetRemainingPowerChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE) +{ + int remaining = 0; + + int maxLevel = GetMaxPowerLevelForClass(nClass, oPC); + if (chosenCircle > maxLevel) + return 0; + + json choices = GetCurrentPowerList(oPC); + int totalChoices = JsonGetLength(choices); + int allowedChoices = GetMaxPowerCount(oPC, nClass); + int alreadyChosen = GetPowerCount(oPC, nClass); + string sFile = GetClassSpellbookFile(nClass); + + int i = 0; + for (i = 0; i < totalChoices; i++) + { + int spellbookId = JsonGetInt(JsonArrayGet(choices, i)); + int spellId = StringToInt(Get2DACache(sFile, "SpellID", spellbookId)); + //if the power is a expanded knowledge choice, don't count it + if (!IsSpellInExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, spellId, oPC) + && !IsSpellInExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, spellId, oPC)) + remaining++; + } + remaining = (allowedChoices - remaining - alreadyChosen); + + // if this is true we count expanded knowledge choices + if (extra) + { + if (GetHasFeat(FEAT_EXPANDED_KNOWLEDGE_1, oPC) && (chosenCircle <= (maxLevel-1))) + { + int totalExp = GetMaxPowerCount(oPC, POWER_LIST_EXP_KNOWLEDGE); + json expChoices = GetExpandedChoicesList(nClass, oPC); + int choicesCount = JsonGetLength(JsonObjectKeys(expChoices)); + remaining += (totalExp - choicesCount); + } + if (GetHasFeat(FEAT_EPIC_EXPANDED_KNOWLEDGE_1, oPC)) + { + int totalExp = GetMaxPowerCount(oPC, POWER_LIST_EPIC_EXP_KNOWLEDGE); + json expChoices = GetEpicExpandedChoicesList(nClass, oPC); + int choicesCount = JsonGetLength(JsonObjectKeys(expChoices)); + remaining += (totalExp - choicesCount); + } + } + + return remaining; +} + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Initiators /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +json GetDisciplineInfoObject(int nClass, object oPC=OBJECT_SELF) +{ + json disciplineInfo = GetLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(nClass)); + if (disciplineInfo == JsonNull()) + disciplineInfo = JsonObject(); + else + return disciplineInfo; + + int chosenClass = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + string sFile = GetClassSpellbookFile(nClass); + + //if this is not the chosen class then we do not have a chosen spell list + // need to go through the class's 2da and check if you know the spell or not. + if (nClass != chosenClass) + { + int totalSpells = Get2DARowCount(sFile); + + int i; + for (i = 0; i < totalSpells; i++) + { + int featId = StringToInt(Get2DACache(sFile, "FeatID", i)); + if (featId && GetHasFeat(featId, oPC, TRUE)) + disciplineInfo = AddSpellDisciplineInfo(sFile, i, disciplineInfo); + } + } + else + { + json chosenMans = GetChosenSpellListObject(nClass, oPC); + + json circles = JsonObjectKeys(chosenMans); + int totalCircles = JsonGetLength(circles); + + int i; + for (i = 0; i < totalCircles; i++) + { + string currentCircle = JsonGetString(JsonArrayGet(circles, i)); + json currentList = JsonObjectGet(chosenMans, currentCircle); + int totalSpells = JsonGetLength(currentList); + + int y; + for (y = 0; y < totalSpells; y++) + { + int spellbookId = JsonGetInt(JsonArrayGet(currentList, y)); + disciplineInfo = AddSpellDisciplineInfo(sFile, spellbookId, disciplineInfo); + } + } + } + + SetLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(nClass), disciplineInfo); + return disciplineInfo; +} + +int IsRequiredForOtherManeuvers(int nClass, int prereq, string discipline, object oPC=OBJECT_SELF) +{ + json discInfo = GetDisciplineInfoObject(nClass, oPC); + + int total = 0; + + // loop through each prereq level and add up it's totals (ie how many maneuevrs + // do we know with 0,1,2...,n prereqs. + int i; + for (i = 0; i <= prereq; i++) + { + + json currDisc = JsonObjectGet(discInfo, discipline); + string discKey = ("Prereq_" + IntToString(i)); + int currentDiscPrereq = JsonGetInt(JsonObjectGet(currDisc, discKey)); + total += currentDiscPrereq; + } + + // then from above the given prereq check if we have any prereq maneuevers taken + for (i = (prereq+1); i < NUI_LEVEL_UP_MANEUVER_PREREQ_LIMIT; i++) + { + json currDisc = JsonObjectGet(discInfo, discipline); + string discKey = ("Prereq_" + IntToString(i)); + json discPrereq = JsonObjectGet(currDisc, discKey); + if (discPrereq != JsonNull()) + { + // if we do take the total and subtract by one, if it is lower than + // than the prereq needed, it is required + if (total - 1 < i) + return TRUE; + + // otherwise add how many we have and move up and keep trying. + int currentDiscPrereq = JsonGetInt(discPrereq); + total += currentDiscPrereq; + } + } + + return FALSE; +} + +int HasPreRequisitesForManeuver(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + int prereqs = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId)); + if (!prereqs) + return TRUE; + string discipline = Get2DACache(sFile, "Discipline", spellbookId); + json discInfo = GetDisciplineInfoObject(nClass, oPC); + json chosenDisc = JsonObjectGet(discInfo, discipline); + if (chosenDisc != JsonNull()) + { + int nManCount = (JsonGetInt(JsonObjectGet(chosenDisc, IntToString(MANEUVER_TYPE_MANEUVER))) + + JsonGetInt(JsonObjectGet(chosenDisc, IntToString(MANEUVER_TYPE_STANCE)))); + if (nManCount >= prereqs) + return TRUE; + } + + return FALSE; +} + +int GetMaxInitiatorCircle(int nClass, object oPC=OBJECT_SELF) +{ + int initiatorLevel = GetInitiatorLevel(oPC, nClass); + // initiators learn by ceiling(classLevel) + int highestCircle = (initiatorLevel + 1) / 2; + if (highestCircle > 9) + return 9; + return highestCircle; +} + +int GetRemainingManeuverChoices(int nClass, object oPC=OBJECT_SELF) +{ + json discInfo = GetDisciplineInfoObject(nClass, oPC); + + json jManAmount = JsonObjectGet(discInfo, NUI_LEVEL_UP_MANEUVER_TOTAL); + int nManAmount = 0; + if (jManAmount != JsonNull()) + nManAmount = JsonGetInt(jManAmount); + + int maxAmount = GetMaxManeuverCount(oPC, nClass, MANEUVER_TYPE_MANEUVER); + + return maxAmount - nManAmount; +} + +int GetRemainingStanceChoices(int nClass, object oPC=OBJECT_SELF) +{ + json discInfo = GetDisciplineInfoObject(nClass, oPC); + + json jStanceAmount = JsonObjectGet(discInfo, NUI_LEVEL_UP_STANCE_TOTAL); + int nStanceAmount = 0; + if (jStanceAmount != JsonNull()) + nStanceAmount = JsonGetInt(jStanceAmount); + + int maxAmount = GetMaxManeuverCount(oPC, nClass, MANEUVER_TYPE_STANCE); + + return maxAmount - nStanceAmount; +} + +int IsAllowedDiscipline(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + // logic carried over from private function in discipline inc functions + // uses bitwise matching to tell if the discipline is allowed or not + string sFile = GetClassSpellbookFile(nClass); + int discipline = StringToInt(Get2DACache(sFile, "Discipline", spellbookId)); + + int nOverride = GetPersistantLocalInt(oPC, "AllowedDisciplines"); + if(nOverride == 0) + { + switch(nClass) + { + case CLASS_TYPE_CRUSADER: nOverride = 322; break;//DISCIPLINE_DEVOTED_SPIRIT + DISCIPLINE_STONE_DRAGON + DISCIPLINE_WHITE_RAVEN + case CLASS_TYPE_SWORDSAGE: nOverride = 245; break;//DISCIPLINE_DESERT_WIND + DISCIPLINE_DIAMOND_MIND + DISCIPLINE_SETTING_SUN + DISCIPLINE_SHADOW_HAND + DISCIPLINE_STONE_DRAGON + DISCIPLINE_TIGER_CLAW + case CLASS_TYPE_WARBLADE: nOverride = 460; break;//DISCIPLINE_DIAMOND_MIND + DISCIPLINE_IRON_HEART + DISCIPLINE_STONE_DRAGON + DISCIPLINE_TIGER_CLAW + DISCIPLINE_WHITE_RAVEN + } + } + return nOverride & discipline; +} + +int IsRequiredForToBPRCClass(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + int currentClassPos = 1; + // loop through all the classes and look for a PRC + while (currentClassPos) + { + int currentClass = GetClassByPosition(currentClassPos, oPC); + // if we reached a non existant class, we reached the end. + if (currentClass != CLASS_TYPE_INVALID) + { + string sFile = GetClassSpellbookFile(nClass); + int discipline = StringToInt(Get2DACache(sFile, "Discipline", spellbookId)); + + // check if the class is a ToB PRC Class and if the current spell's + // discipline is used for it. + int isUsed = FALSE; + if (currentClass == CLASS_TYPE_DEEPSTONE_SENTINEL + && (discipline == DISCIPLINE_STONE_DRAGON)) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_BLOODCLAW_MASTER + && (discipline == DISCIPLINE_TIGER_CLAW)) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_RUBY_VINDICATOR + && (discipline == DISCIPLINE_DEVOTED_SPIRIT)) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_JADE_PHOENIX_MAGE) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_MASTER_OF_NINE) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_ETERNAL_BLADE + && (discipline == DISCIPLINE_DEVOTED_SPIRIT + || discipline == DISCIPLINE_DIAMOND_MIND)) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_SHADOW_SUN_NINJA + && (discipline == DISCIPLINE_SETTING_SUN + || discipline == DISCIPLINE_SHADOW_HAND)) + isUsed = TRUE; + + // if any of the above was true than we need to check if this spell + // is required for a PRC + if (isUsed) + { + // get the discipline info for all BladeMagic classes if we have them. + json discInfo = GetDisciplineInfoObject(nClass, oPC); + json classDisc2Info = JsonObject(); + json classDisc3Info = JsonObject(); + if (nClass == CLASS_TYPE_SWORDSAGE) + { + if (GetLevelByClass(CLASS_TYPE_WARBLADE, oPC)) + classDisc2Info = GetDisciplineInfoObject(CLASS_TYPE_WARBLADE, oPC); + if (GetLevelByClass(CLASS_TYPE_CRUSADER, oPC)) + classDisc3Info = GetDisciplineInfoObject(CLASS_TYPE_CRUSADER, oPC); + } + if (nClass == CLASS_TYPE_CRUSADER) + { + if (GetLevelByClass(CLASS_TYPE_WARBLADE, oPC)) + classDisc2Info = GetDisciplineInfoObject(CLASS_TYPE_WARBLADE, oPC); + if (GetLevelByClass(CLASS_TYPE_SWORDSAGE, oPC)) + classDisc3Info = GetDisciplineInfoObject(CLASS_TYPE_SWORDSAGE, oPC); + } + if (nClass == CLASS_TYPE_WARBLADE) + { + if (GetLevelByClass(CLASS_TYPE_CRUSADER, oPC)) + classDisc2Info = GetDisciplineInfoObject(CLASS_TYPE_CRUSADER, oPC); + if (GetLevelByClass(CLASS_TYPE_SWORDSAGE, oPC)) + classDisc3Info = GetDisciplineInfoObject(CLASS_TYPE_SWORDSAGE, oPC); + } + + // Time to begin checking the PRCs + // this should follow the same logic as here + // https://gitea.raptio.us/Jaysyn/PRC8/src/commit/797442d3da7c9c8e1fcf585b97e2ff1cbe56045b/nwn/nwnprc/trunk/scripts/prc_prereq.nss#L991 + + // Check Deepstone Sentinel + if (currentClass == CLASS_TYPE_DEEPSTONE_SENTINEL) + { + // we need to look for 2 Stone Dragon Maneuvers and 1 Stone Dragon + // Stance. So add up the other MagicBlade classes and see if it is satisfied. + int stoneDMan, stoneDStance = 0; + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stoneDMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + stoneDStance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + if (stoneDMan >= 2 && stoneDStance >= 1) + return FALSE; + } + + currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stoneDMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + stoneDStance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + if (stoneDMan >= 2 && stoneDStance >= 1) + return FALSE; + } + // if it still isn't satisfied than the current class is required + // for it to exist. Check to see if it is safe to remove the maneuever + currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stoneDMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + stoneDStance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + //if the current maneuver is a stance, check to see if it is safe to remove + if (type == MANEUVER_TYPE_STANCE) + { + if (stoneDStance - 1 >= 1) + return FALSE; + } + else + { + // if it is not a stance we can just check the maneuevers in general + if (stoneDMan - 1 >= 2) + return FALSE; + } + + // this maneuver is required and should not be removed. + return TRUE; + } + } + + // Check Bloodclaw Master + if (currentClass == CLASS_TYPE_BLOODCLAW_MASTER) + { + // bloodclaw needs 3 Tiger Claw maneuevers + int tigerCMan = 0; + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + if (tigerCMan >= 3) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + if (tigerCMan >= 3) + return FALSE; + } + currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + if (tigerCMan-1 >= 3) + return FALSE; + return TRUE; + } + } + + if (currentClass == CLASS_TYPE_RUBY_VINDICATOR) + { + // Ruby Vindicator needs 1 stance and 1 maneuever from Devoted Spirit + int stance = 0; + int maneuver = 0; + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + if (stance >= 1 && maneuver >= 1) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + if (stance >= 1 && maneuver >= 1) + return FALSE; + } + currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (type == MANEUVER_TYPE_STANCE) + { + if (stance - 1 >= 1) + return FALSE; + return TRUE; + } + if (maneuver - 1 >= 1) + return FALSE; + return TRUE; + } + } + + if (currentClass == CLASS_TYPE_JADE_PHOENIX_MAGE) + { + // Jade Phoenix needs 1 stance and 2 maneuvers of any type + int stance = 0; + int maneuver = 0; + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + if (stance >= 1 && maneuver >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + if (stance >= 1 && maneuver >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (type == MANEUVER_TYPE_STANCE) + { + if ((stance - 1 >= 1) && (maneuver -1 >= 2)) + return FALSE; + return TRUE; + } + if (maneuver - 1 >= 2) + return FALSE; + return TRUE; + } + } + + if (currentClass == CLASS_TYPE_MASTER_OF_NINE) + { + // master of nine needs 1 maneuever from 6 different disciplines + + int totalDiscCount = 0; + int currentClassAndDiscUsed = 0; + int i; + // loop through each possible discipline + for (i = 0; i <= 256; i++) + { + int found = 0; + // only disciplines that exist are stored, and only those + // that are used are stored, so we can loop through and + // find what disciplines we do or don't know. + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(i)); + if (currentDisc != JsonNull()) + found = 1; + + if (!found) + { + json currentDisc = JsonObjectGet(classDisc3Info, IntToString(i)); + if (currentDisc != JsonNull()) + found = 1; + } + + if (!found) + { + json currentDisc = JsonObjectGet(discInfo, IntToString(i)); + if (currentDisc != JsonNull()) + { + if (i == discipline) + currentClassAndDiscUsed = 1; + found = 1; + } + } + + totalDiscCount += found; + } + // if we have more maneuevers than 6, it is not required + if (totalDiscCount > 6) + return FALSE; + // however if we have 6 and this discipline was grabbed we need to make sure it is safe to remove + if (currentClassAndDiscUsed) + { + // if we were to remove this discipline and it is 5 or less total disciplines we have now + // it is important + if (totalDiscCount - 1 >= 6) + return FALSE; + json currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + int stance = JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + int maneuver = JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + // if we were to remove this discipline and are left with no more than + // this was important and it can't be removed + if ((stance + maneuver - 1) >= 1) + return FALSE; + return TRUE; + } + return FALSE; + } + + if (currentClass == CLASS_TYPE_ETERNAL_BLADE) + { + //Eternal blade 2 Devoted Spirits OR 2 Diamond Mind + int nTotal = 0; + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_DEVOTED_SPIRIT)); + if (currentDisc != JsonNull()) + { + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nTotal >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_DIAMOND_MIND)); + if (currentDisc != JsonNull()) + { + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nTotal >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(DISCIPLINE_DEVOTED_SPIRIT)); + if (currentDisc != JsonNull()) + { + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nTotal >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(DISCIPLINE_DIAMOND_MIND)); + if (currentDisc != JsonNull()) + { + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nTotal >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + if ((nTotal - 1) >= 2) + return FALSE; + return TRUE; + } + } + + if (currentClass == CLASS_TYPE_SHADOW_SUN_NINJA) + { + // Shadow Sun Ninja needs 1 lvl2 Setting Sun OR Shadow Hand maneuever + // 1 Setting Sun maneuver AND 1 Shadow Hand maneuver + int nLvl2 = 0; + int shadowHTotal; + int settingSTotal; + + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_SHADOW_HAND)); + if (currentDisc != JsonNull()) + { + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2)) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_SETTING_SUN)); + if (currentDisc != JsonNull()) + { + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2)) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(DISCIPLINE_SHADOW_HAND)); + if (currentDisc != JsonNull()) + { + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2)) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_SETTING_SUN)); + if (currentDisc != JsonNull()) + { + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2)) + return FALSE; + } + currentDisc = JsonObjectGet(discInfo, IntToString(DISCIPLINE_SHADOW_HAND)); + if (currentDisc != JsonNull()) + { + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + } + currentDisc = JsonObjectGet(discInfo, IntToString(DISCIPLINE_SETTING_SUN)); + if (currentDisc != JsonNull()) + { + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + } + int level = StringToInt(Get2DACache(sFile, "Level", spellbookId)); + if (level == 2) + nLvl2 -= 1; + if (discipline == DISCIPLINE_SHADOW_HAND) + shadowHTotal -= 1; + else + settingSTotal -= 1; + + return !(nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2)); + } + } + + currentClassPos += 1; + } + else + currentClassPos = 0; + } + + return FALSE; +} + +json AddSpellDisciplineInfo(string sFile, int spellbookId, json classDisc) +{ + json classDiscCopy = classDisc; + int discipline = StringToInt(Get2DACache(sFile, "Discipline", spellbookId)); + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + int level = StringToInt(Get2DACache(sFile, "Level", spellbookId)); + int prereq = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId)); + + json jDisc = JsonObjectGet(classDisc, IntToString(discipline)); + if (jDisc == JsonNull()) + jDisc = JsonObject(); + + string levelKey = "Level" + IntToString(level) + "_" + IntToString(type); + int nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, levelKey)) + 1); + jDisc = JsonObjectSet(jDisc, levelKey, JsonInt(nTypeTotal)); + + nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, IntToString(type))) + 1); + jDisc = JsonObjectSet(jDisc, IntToString(type), JsonInt(nTypeTotal)); + + if (type != MANEUVER_TYPE_MANEUVER + && type != MANEUVER_TYPE_STANCE) + { + levelKey = "Level" + IntToString(level) + "_" + IntToString(MANEUVER_TYPE_MANEUVER); + nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, levelKey)) + 1); + jDisc = JsonObjectSet(jDisc, levelKey, JsonInt(nTypeTotal)); + + nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, IntToString(MANEUVER_TYPE_MANEUVER))) + 1); + jDisc = JsonObjectSet(jDisc, IntToString(MANEUVER_TYPE_MANEUVER), JsonInt(nTypeTotal)); + } + + string prereqKey = "Prereq_" + IntToString(prereq); + int nPrereqTotal = (JsonGetInt(JsonObjectGet(jDisc, prereqKey)) + 1); + jDisc = JsonObjectSet(jDisc, prereqKey, JsonInt(nPrereqTotal)); + + if (type == MANEUVER_TYPE_STANCE) + { + nTypeTotal = (JsonGetInt(JsonObjectGet(classDisc, NUI_LEVEL_UP_STANCE_TOTAL)) + 1); + classDiscCopy = JsonObjectSet(classDiscCopy, NUI_LEVEL_UP_STANCE_TOTAL, JsonInt(nTypeTotal)); + } + else + { + nTypeTotal = (JsonGetInt(JsonObjectGet(classDisc, NUI_LEVEL_UP_MANEUVER_TOTAL)) + 1); + classDiscCopy = JsonObjectSet(classDiscCopy, NUI_LEVEL_UP_MANEUVER_TOTAL, JsonInt(nTypeTotal)); + } + + return JsonObjectSet(classDiscCopy, IntToString(discipline), jDisc); +} + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Invokers /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +json GetInvokerKnownListObject(int nClass, object oPC=OBJECT_SELF) +{ + json knownObject = GetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_INVOCATIONS_CACHE_VAR + IntToString(nClass)); + if (knownObject == JsonNull()) + knownObject = JsonObject(); + else + return knownObject; + + string sFile = GetAMSKnownFileName(nClass); + int totalRows = Get2DARowCount(sFile); + int maxInvocLevel = StringToInt(Get2DACache(sFile, "MaxInvocationLevel", totalRows-1)); + json previousInvocList = JsonObject(); + + int i; + for (i = 1; i <= maxInvocLevel; i++) + { + previousInvocList = JsonObjectSet(previousInvocList, IntToString(i), JsonInt(0)); + } + + for (i = 0; i < totalRows; i++) + { + int maxInvocation = StringToInt(Get2DACache(sFile, "MaxInvocationLevel", i)); + int invocationKnown = StringToInt(Get2DACache(sFile, "InvocationKnown", i)); + json invocList = previousInvocList; + + int previousInvocTotal = 0; + if (i > 0) + previousInvocTotal = StringToInt(Get2DACache(sFile, "InvocationKnown", i-1)); + int previousInvocAmount = JsonGetInt(JsonObjectGet(previousInvocList, IntToString(maxInvocation))); + int currentInvocationAmount = (invocationKnown - previousInvocTotal + previousInvocAmount); + + invocList = JsonObjectSet(invocList, IntToString(maxInvocation), JsonInt(currentInvocationAmount)); + knownObject = JsonObjectSet(knownObject, IntToString(i+1), invocList); + previousInvocList = invocList; + } + + SetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_INVOCATIONS_CACHE_VAR + IntToString(nClass), knownObject); + return knownObject; +} + +int GetRemainingInvocationChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE) +{ + int remaining = 0; + int nLevel = GetInvokerLevel(oPC, nClass); + + json knownObject = GetInvokerKnownListObject(nClass, oPC); + json chosenInv = GetChosenSpellListObject(nClass, oPC); + json currentLevelKnown = JsonObjectGet(knownObject, IntToString(nLevel)); + + int totalCircles = JsonGetLength(JsonObjectKeys(currentLevelKnown)); + + // logic goes we are given a set amount of invocations at each circle. We can + // take from a circle above us, but not below us. So we need to make sure + // we have a legal amount of choices + int i; + for (i = 1; i <= totalCircles; i++) + { + int currentChosen = 0; + json chosenSpells = JsonObjectGet(chosenInv, IntToString(i)); + if (chosenSpells != JsonNull()) + { + int totalChosen = JsonGetLength(chosenSpells); + int j; + for (j = 0; j < totalChosen; j++) + { + int spellbookId = JsonGetInt(JsonArrayGet(chosenSpells, j)); + // only count non extra invocation choices + if (!IsExtraChoiceInvocation(nClass, spellbookId, oPC)) + currentChosen += 1; + } + } + + int allowedAtCircle = JsonGetInt(JsonObjectGet(currentLevelKnown, IntToString(i))); + + remaining = (allowedAtCircle - currentChosen + remaining); + // if the circle is below the chosen circle and we have a positive remaining, + // we set it to 0 because we cannot use lower circle spells on higher circle. + // however if thge value is negative then we carry it over because we + // have a deficit and need to account for it by using the spells of the + // next circle. + if (i < chosenCircle && remaining > 0) + remaining = 0; + } + + + // count extra and epic invocation choices + if (extra) + { + string sFile = GetAMSKnownFileName(nClass); + int maxCircle = StringToInt(Get2DACache(sFile, "MaxInvocationLevel", nLevel-1)); + + if (GetHasFeat(FEAT_EXTRA_INVOCATION_I, oPC) && (chosenCircle <= (maxCircle-1))) + { + int totalExp = GetMaxInvocationCount(oPC, INVOCATION_LIST_EXTRA); + json expChoices = GetExpandedChoicesList(nClass, oPC); + int choicesCount = JsonGetLength(JsonObjectKeys(expChoices)); + remaining += (totalExp - choicesCount); + } + if (GetHasFeat(FEAT_EPIC_EXTRA_INVOCATION_I, oPC)) + { + int totalExp = GetMaxInvocationCount(oPC, INVOCATION_LIST_EXTRA_EPIC); + json expChoices = GetEpicExpandedChoicesList(nClass, oPC); + int choicesCount = JsonGetLength(JsonObjectKeys(expChoices)); + remaining += (totalExp - choicesCount); + } + } + + return remaining; +} + +int IsExtraChoiceInvocation(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + json extraChoices = GetExpandedChoicesList(nClass, oPC); + json chosenSpell = JsonObjectGet(extraChoices, spellId); + if (chosenSpell != JsonNull()) + return TRUE; + + extraChoices = GetEpicExpandedChoicesList(nClass, oPC); + chosenSpell = JsonObjectGet(extraChoices, spellId); + if (chosenSpell != JsonNull()) + return TRUE; + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Truenamer /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +int GetRemainingTruenameChoices(int nClass, int nType, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json circles = JsonObjectKeys(chosenSpells); + int totalCircles = JsonGetLength(circles); + + int remainingChoices = 0; + + int i; + for (i = 0; i < totalCircles; i++) + { + json spellList = JsonObjectGet(chosenSpells, JsonGetString(JsonArrayGet(circles, i))); + if (spellList != JsonNull()) + { + int totalChoices = JsonGetLength(spellList); + + int j; + for (j = 0; j < totalChoices; j++) + { + int spellbookId = JsonGetInt(JsonArrayGet(spellList, j)); + int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", spellbookId)); + // -1 means count all lexicons + if (nType == -1 || lexicon == nType) + remainingChoices += 1; + } + } + } + + int maxChoices; + // if -1 we count all lexicons to get total remaining + if (nType == -1) + maxChoices = (GetMaxUtteranceCount(oPC, nClass, LEXICON_CRAFTED_TOOL) + + GetMaxUtteranceCount(oPC, nClass, LEXICON_EVOLVING_MIND) + + GetMaxUtteranceCount(oPC, nClass, LEXICON_PERFECTED_MAP)); + else + maxChoices = GetMaxUtteranceCount(oPC, nClass, nType); + return (maxChoices - remainingChoices); +} + +int GetLexiconCircleKnownAtLevel(int nLevel, int nType) +{ + + string sFile = "cls_true_maxlvl"; + + string columnName; + if (nType == LEXICON_EVOLVING_MIND) + columnName = "EvolvingMind"; + else if (nType == LEXICON_CRAFTED_TOOL) + columnName = "CraftedTool"; + else + columnName = "PerfectedMap"; + + return StringToInt(Get2DACache(sFile, columnName, nLevel-1)); +} + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Archivist /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +json GetArchivistNewSpellsList(object oPC=OBJECT_SELF) +{ + json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR); + if (retValue == JsonNull()) + retValue = JsonArray(); + else + return retValue; + + SetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR, retValue); + return retValue; +} + +//:: void main(){} \ No newline at end of file diff --git a/src/include/prc_nui_sc_inc.nss b/src/include/prc_nui_sb_inc.nss similarity index 76% rename from src/include/prc_nui_sc_inc.nss rename to src/include/prc_nui_sb_inc.nss index a1a768e..7148413 100644 --- a/src/include/prc_nui_sc_inc.nss +++ b/src/include/prc_nui_sb_inc.nss @@ -10,10 +10,8 @@ //:: Created By: Rakiov //:: Created On: 24.05.2005 //::////////////////////////////////////////////// -#include "inc_newspellbook" -#include "psi_inc_psifunc" -#include "inc_lookups" -#include "prc_nui_consts" + +#include "prc_nui_com_inc" // // GetSpellListForCircle @@ -43,69 +41,6 @@ json GetSpellListForCircle(object oPlayer, int nClass, int circle); // json GetSupportedNUISpellbookClasses(object oPlayer); -// -// GetCurrentSpellLevel -// Gets the current spell level the class can achieve at the current -// caster level (ranging from 0-9) -// -// Arguments: -// nClass:int the ClassID -// nLevel:int the caster level -// -// Returns: -// int the circle the class can achieve currently -// -int GetCurrentSpellLevel(int nClass, int nLevel); - -// -// GetMaxSpellLevel -// Gets the highest possible circle the class can achieve (from 0-9) -// -// Arguments: -// nClass:int the ClassID -// -// Returns: -// int the highest circle that can be achieved -// -int GetMaxSpellLevel(int nClass); - -// -// GetMinSpellLevel -// Gets the lowest possible circle the class can achieve (from 0-9) -// -// Arguments: -// nClass:int the ClassID -// -// Returns: -// int the lowest circle that can be achieved -// -int GetMinSpellLevel(int nClass); - -// -// GetHighestLevelPossibleInClass -// Given a class Id this will determine what the max level of a class can be -// achieved -// -// Arguments: -// nClass:int the ClassID -// -// Returns: -// int the highest possible level the class can achieve -// -int GetHighestLevelPossibleInClass(int nClass); - -// -// GetClassSpellbookFile -// Gets the class 2da spellbook/ability for the given class Id -// -// Arguments: -// nClass:int the classID -// -// Returns: -// string the 2da file name for the spell/abilities of the ClassID -// -string GetClassSpellbookFile(int nClass); - // // IsSpellKnown // Returns whether the player with the given class, spell file, and spellbook id @@ -234,23 +169,6 @@ json GetMetaMysteryFeatList(); // int GetTrueClassIfRHD(object oPlayer, int nClass); -// -// GetBinderSpellToFeatDictionary -// Sets up the Binder Spell Dictionary that is used to match a binder's vestige -// to their feat. This is constructed based off the binder's known location of -// their feat and spell ranges in the base 2das respectivly. After constructing -// this it will be saved to the player locally as a cached result since we do -// not need to call this again. -// -// Argument: -// oPlayer:object the player -// -// Returns: -// json:Dictionary a dictionary of mapping between the SpellID -// and the FeatID of a vestige ability -// -json GetBinderSpellToFeatDictionary(object oPlayer=OBJECT_SELF); - // // ShouldAddSpell // Given a spellId and a class, determines if the spell should be added to the @@ -318,6 +236,18 @@ json GetInvokerEssenceSpellList(int nClass, object oPlayer=OBJECT_SELF); // int JsonArrayContainsInt(json list, int item); +// +// IsSpellbookNUIOpen +// Checks to see if the Spellbook NUI is open on a given player. +// +// Arguments: +// oPC:object the player +// +// Returns: +// int:Boolean TRUE if window is open, FALSE otherwise +// +int IsSpellbookNUIOpen(object oPC); + json GetSpellListForCircle(object oPlayer, int nClass, int circle) { json retValue = JsonArray(); @@ -397,86 +327,6 @@ int ShouldAddSpell(int nClass, int spellId, object oPlayer=OBJECT_SELF) return TRUE; } -json GetBinderSpellToFeatDictionary(object oPlayer=OBJECT_SELF) -{ - // a dictionary of - json binderDict = GetLocalJson(oPlayer, NUI_SPELLBOOK_BINDER_DICTIONARY_CACHE_VAR); - // if this hasn't been created, create it now. - if (binderDict == JsonNull()) - binderDict = JsonObject(); - else - return binderDict; - - // the starting row for binder spells - int spellIndex = 19070; - // the starting row for binder feats - int featIndex = 9030; - //the end of the binder spells/feats - while (spellIndex <= 19156 && featIndex <= 9104) - { - // get the SpellID tied to the feat - int spellID = StringToInt(Get2DACache("feat", "SPELLID", featIndex)); - // if the spellID matches the current index, then this is the spell - // attached to the feat - if (spellID == spellIndex) - { - binderDict = JsonObjectSet(binderDict, IntToString(spellID), JsonInt(featIndex)); - - // move to next spell/feat - featIndex++; - spellIndex++; - } - // else we have reached a subdial spell - else - { - // loop through until we reach back at spellID - while (spellIndex < spellID) - { - int masterSpell = StringToInt(Get2DACache("spells", "Master", spellIndex)); - - // add the sub radial to the dict, tied to the master's FeatID - int featId = JsonGetInt(JsonObjectGet(binderDict, IntToString(masterSpell))); - binderDict = JsonObjectSet(binderDict, IntToString(spellIndex), JsonInt(featId)); - - spellIndex++; - } - - - // some feats overlap the same FeatID, can cause this to get stuck. - // if it happens then move on - if (spellIndex > spellID) - featIndex++; - } - } - - // cache the result - SetLocalJson(oPlayer, NUI_SPELLBOOK_BINDER_DICTIONARY_CACHE_VAR, binderDict); - return binderDict; -} - -string GetClassSpellbookFile(int nClass) -{ - string sFile; - // Spontaneous casters use a specific file name structure - if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS - || nClass == CLASS_TYPE_ARCHIVIST) - { - sFile = GetFileForClass(nClass); - } - // everyone else uses this structure - else - { - sFile = GetAMSDefinitionFileName(nClass); - - if (nClass == CLASS_TYPE_BINDER) - { - sFile = "vestiges"; - } - } - - return sFile; -} - json GetSupportedNUISpellbookClasses(object oPlayer) { json retValue = JsonArray(); @@ -526,167 +376,6 @@ int IsSpellKnown(object oPlayer, int nClass, int spellId) return FALSE; } -int GetCurrentSpellLevel(int nClass, int nLevel) -{ - int currentLevel = nLevel; - - // ToB doesn't have a concept of spell levels, but still match up to it - if(nClass == CLASS_TYPE_WARBLADE - || nClass == CLASS_TYPE_SWORDSAGE - || nClass == CLASS_TYPE_CRUSADER - || nClass == CLASS_TYPE_SHADOWCASTER) - { - return 9; - } - - - // Binders don't really have a concept of spell level - if (nClass == CLASS_TYPE_BINDER - || nClass == CLASS_TYPE_DRAGON_SHAMAN) // they can only reach 1st circle - return 1; - - //Shadowsmith has no concept of spell levels - if (nClass == CLASS_TYPE_SHADOWSMITH) - return 2; - - if (nClass == CLASS_TYPE_WARLOCK - || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT) - return 4; - - // Spont casters have their own function - if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS - || nClass == CLASS_TYPE_ARCHIVIST) - { - - int maxLevel = GetMaxSpellLevelForCasterLevel(nClass, currentLevel); - return maxLevel; - } - else - { - // everyone else uses this - string spellLevel2da = GetAMSKnownFileName(nClass); - - currentLevel = nLevel - 1; // Level is 1 off of the row in the 2da - - if (nClass == CLASS_TYPE_FIST_OF_ZUOKEN - || nClass == CLASS_TYPE_PSION - || nClass == CLASS_TYPE_PSYWAR - || nClass == CLASS_TYPE_WILDER - || nClass == CLASS_TYPE_PSYCHIC_ROGUE - || nClass == CLASS_TYPE_WARMIND) - currentLevel = GetManifesterLevel(OBJECT_SELF, nClass, TRUE) - 1; - - int totalLevel = Get2DARowCount(spellLevel2da); - - // in case we somehow go over bounds just don't :) - if (currentLevel >= totalLevel) - currentLevel = totalLevel - 1; - - //Psionics have MaxPowerLevel as their column name - string columnName = "MaxPowerLevel"; - - //Invokers have MaxInvocationLevel - if (nClass == CLASS_TYPE_WARLOCK - || nClass == CLASS_TYPE_DRAGON_SHAMAN - || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT) - columnName = "MaxInvocationLevel"; - - // Truenamers have 3 sets of utterances, ranging from 1-6, EvolvingMind covers the entire range - if (nClass == CLASS_TYPE_TRUENAMER) - { - columnName = "EvolvingMind"; - spellLevel2da = "cls_true_maxlvl"; //has a different 2da we want to look at - } - - if (nClass == CLASS_TYPE_BINDER) - { - columnName = "VestigeLvl"; - spellLevel2da = "cls_bind_binder"; - } - - // ToB doesn't have a concept of this, but we don't care. - - int maxLevel = StringToInt(Get2DACache(spellLevel2da, columnName, currentLevel)); - return maxLevel; - } -} - -int GetMinSpellLevel(int nClass) -{ - // again sponts have their own function - if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS - || nClass == CLASS_TYPE_ARCHIVIST) - { - return GetMinSpellLevelForCasterLevel(nClass, GetHighestLevelPossibleInClass(nClass)); - } - else - { - if (nClass == CLASS_TYPE_FIST_OF_ZUOKEN - || nClass == CLASS_TYPE_PSION - || nClass == CLASS_TYPE_PSYWAR - || nClass == CLASS_TYPE_WILDER - || nClass == CLASS_TYPE_PSYCHIC_ROGUE - || nClass == CLASS_TYPE_WARMIND - || nClass == CLASS_TYPE_WARBLADE - || nClass == CLASS_TYPE_SWORDSAGE - || nClass == CLASS_TYPE_CRUSADER - || nClass == CLASS_TYPE_WARLOCK - || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT - || nClass == CLASS_TYPE_DRAGON_SHAMAN - || nClass == CLASS_TYPE_SHADOWCASTER - || nClass == CLASS_TYPE_SHADOWSMITH - || nClass == CLASS_TYPE_BINDER) - return 1; - - return GetCurrentSpellLevel(nClass, 1); - } - -} - -int GetMaxSpellLevel(int nClass) -{ - if (nClass == CLASS_TYPE_WILDER - || nClass == CLASS_TYPE_PSION) - return 9; - if (nClass == CLASS_TYPE_PSYCHIC_ROGUE - || nClass == CLASS_TYPE_FIST_OF_ZUOKEN - || nClass == CLASS_TYPE_WARMIND) - return 5; - if (nClass == CLASS_TYPE_PSYWAR) - return 6; - - return GetCurrentSpellLevel(nClass, GetHighestLevelPossibleInClass(nClass)); -} - -int GetHighestLevelPossibleInClass(int nClass) -{ - string sFile; - - //sponts have their spells in the classes.2da - if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS - || nClass == CLASS_TYPE_ARCHIVIST) - { - sFile = Get2DACache("classes", "SpellGainTable", nClass); - } - else - { - // everyone else uses this - sFile = GetAMSKnownFileName(nClass); - - if (nClass == CLASS_TYPE_TRUENAMER) - { - sFile = "cls_true_maxlvl"; //has a different 2da we want to look at - } - - if (nClass == CLASS_TYPE_BINDER) - { - sFile = "cls_bind_binder"; - } - } - - return Get2DARowCount(sFile); -} - int IsClassAllowedToUseNUISpellbook(object oPlayer, int nClass) { // This controls who can use the Spellbook NUI, if for some reason you don't @@ -698,8 +387,7 @@ int IsClassAllowedToUseNUISpellbook(object oPlayer, int nClass) return TRUE; // Arcane Spont - if (nClass == CLASS_TYPE_ASSASSIN - || nClass == CLASS_TYPE_BEGUILER + if (nClass == CLASS_TYPE_BEGUILER || nClass == CLASS_TYPE_CELEBRANT_SHARESS || nClass == CLASS_TYPE_DREAD_NECROMANCER || nClass == CLASS_TYPE_DUSKBLADE @@ -817,8 +505,7 @@ int CanClassUseMetamagicFeats(int nClass) // I don't want to spend the time looping through each class's // feat 2da so this is the list of all classes that are allowed to use the // Spellbook NUI and can use Metamagic - return (nClass == CLASS_TYPE_ASSASSIN - || nClass == CLASS_TYPE_BARD + return (nClass == CLASS_TYPE_BARD || nClass == CLASS_TYPE_SORCERER || nClass == CLASS_TYPE_BEGUILER || nClass == CLASS_TYPE_DREAD_NECROMANCER @@ -838,7 +525,6 @@ int CanClassUseSuddenMetamagicFeats(int nClass) // Spellbook NUI and can use Sudden Metamagic return (nClass == CLASS_TYPE_SHADOWLORD || nClass == CLASS_TYPE_ARCHIVIST - || nClass == CLASS_TYPE_ASSASSIN || nClass == CLASS_TYPE_BARD || nClass == CLASS_TYPE_BEGUILER || nClass == CLASS_TYPE_DREAD_NECROMANCER @@ -1144,5 +830,16 @@ int JsonArrayContainsInt(json list, int item) return TRUE; } + return FALSE; +} + +int IsSpellbookNUIOpen(object oPC) +{ + int nPreviousToken = NuiFindWindow(oPC, PRC_SPELLBOOK_NUI_WINDOW_ID); + if (nPreviousToken != 0) + { + return TRUE; + } + return FALSE; } \ No newline at end of file diff --git a/src/include/prc_nui_scd_inc.nss b/src/include/prc_nui_sbd_inc.nss similarity index 99% rename from src/include/prc_nui_scd_inc.nss rename to src/include/prc_nui_sbd_inc.nss index 5f14b7e..4f84658 100644 --- a/src/include/prc_nui_scd_inc.nss +++ b/src/include/prc_nui_sbd_inc.nss @@ -1,6 +1,6 @@ //:://///////////////////////////////////////////// //:: PRC Spellbook Description NUI -//:: prc_nui_scd_inc +//:: prc_nui_sbd_inc //::////////////////////////////////////////////// /* This is the view for the Spell Description NUI diff --git a/src/include/prc_spell_const.nss b/src/include/prc_spell_const.nss index bb8b1cd..02a3a2f 100644 --- a/src/include/prc_spell_const.nss +++ b/src/include/prc_spell_const.nss @@ -22,6 +22,9 @@ const int SPELL_BCM_RENDING_CLAWS = 17997; //:: Complete Warrior const int SPELL_RANGED_DISARM = 3493; +//:: Tome of Battle +const int SPELL_TOB_SNAP_KICK = 3794; + //marshal const int SPELL_MINAUR_DEMFORT = 3500; const int SPELL_MINAUR_FORCEWILL = 3501; @@ -1289,6 +1292,69 @@ const int SPELL_SUMMON_CREATURE_IX_WATER = 3200; //:: Player's Handbook Spells const int SPELL_SPIRITUAL_WEAPON = 17249; +const int SPELL_SUMMON_NATURES_ALLY_1 = 17000; +const int SPELL_SUMMON_NATURES_ALLY_1_DIREBADGER = 17001; +const int SPELL_SUMMON_NATURES_ALLY_1_DIRERAT = 17002; +const int SPELL_SUMMON_NATURES_ALLY_1_DOG = 17003; +const int SPELL_SUMMON_NATURES_ALLY_1_HAWK = 17004; +const int SPELL_SUMMON_NATURES_ALLY_1_TINY_VIPER = 17005; + +const int SPELL_SUMMON_NATURES_ALLY_2 = 17010; +const int SPELL_SUMMON_NATURES_ALLY_2_DIREBOAR = 17011; +const int SPELL_SUMMON_NATURES_ALLY_2_COOSHEE = 17012; +const int SPELL_SUMMON_NATURES_ALLY_2_WOLF = 17013; +const int SPELL_SUMMON_NATURES_ALLY_2_SMALL_VIPER = 17014; +const int SPELL_SUMMON_NATURES_ALLY_2_BLACKBEAR = 17015; + +const int SPELL_SUMMON_NATURES_ALLY_3 = 17020; +const int SPELL_SUMMON_NATURES_ALLY_3_BROWNBEAR = 17021; +const int SPELL_SUMMON_NATURES_ALLY_3_DIREWOLK = 17022; +const int SPELL_SUMMON_NATURES_ALLY_3_LARGE_VIPER = 17023; +const int SPELL_SUMMON_NATURES_ALLY_3_LEOPARD = 17024; +const int SPELL_SUMMON_NATURES_ALLY_3_SATYR = 17025; + +const int SPELL_SUMMON_NATURES_ALLY_4 = 17030; +const int SPELL_SUMMON_NATURES_ALLY_4_LION = 17031; +const int SPELL_SUMMON_NATURES_ALLY_4_POLAR_BEAR = 17032; +const int SPELL_SUMMON_NATURES_ALLY_4_DIRE_SPIDER = 17033; +const int SPELL_SUMMON_NATURES_ALLY_4_HUGE_VIPER = 17034; +const int SPELL_SUMMON_NATURES_ALLY_4_WEREBOAR = 17035; + +const int SPELL_SUMMON_NATURES_ALLY_5 = 17040; +const int SPELL_SUMMON_NATURES_ALLY_5_MED_AIR = 17041; +const int SPELL_SUMMON_NATURES_ALLY_5_MED_EARTH = 17042; +const int SPELL_SUMMON_NATURES_ALLY_5_MED_FIRE = 17043; +const int SPELL_SUMMON_NATURES_ALLY_5_MED_WATER = 17044; +const int SPELL_SUMMON_NATURES_ALLY_5_DIRE_BEAR = 17045; + +const int SPELL_SUMMON_NATURES_ALLY_6 = 17050; +const int SPELL_SUMMON_NATURES_ALLY_6_LG_AIR = 17051; +const int SPELL_SUMMON_NATURES_ALLY_6_LG_EARTH = 17052; +const int SPELL_SUMMON_NATURES_ALLY_6_LG_FIRE = 17053; +const int SPELL_SUMMON_NATURES_ALLY_6_LG_WATER = 17054; +const int SPELL_SUMMON_NATURES_ALLY_6_DIRETIGER = 17055; + +const int SPELL_SUMMON_NATURES_ALLY_7 = 17060; +const int SPELL_SUMMON_NATURES_ALLY_7_BULETTE = 17061; +const int SPELL_SUMMON_NATURES_ALLY_7_INVSTALKER = 17062; +const int SPELL_SUMMON_NATURES_ALLY_7_PIXIE = 17063; +const int SPELL_SUMMON_NATURES_ALLY_7_GORGON = 17064; +const int SPELL_SUMMON_NATURES_ALLY_7_MANTICORE = 17065; + +const int SPELL_SUMMON_NATURES_ALLY_8 = 17070; +const int SPELL_SUMMON_NATURES_ALLY_8_GR_AIR = 17071; +const int SPELL_SUMMON_NATURES_ALLY_8_GR_EARTH = 17072; +const int SPELL_SUMMON_NATURES_ALLY_8_GR_FIRE = 17073; +const int SPELL_SUMMON_NATURES_ALLY_8_GR_WATER = 17074; +const int SPELL_SUMMON_NATURES_ALLY_8_NYMPH = 17075; + +const int SPELL_SUMMON_NATURES_ALLY_9 = 17080; +const int SPELL_SUMMON_NATURES_ALLY_9_ELD_AIR = 17081; +const int SPELL_SUMMON_NATURES_ALLY_9_ELD_EARTH = 17082; +const int SPELL_SUMMON_NATURES_ALLY_9_ELD_FIRE = 17083; +const int SPELL_SUMMON_NATURES_ALLY_9_ELD_WATER = 17084; +const int SPELL_SUMMON_NATURES_ALLY_9_ARANEA = 17085; + //:: Player's Handbook II Spells const int SPELL_CHASING_PERFECTION = 2479; @@ -1297,12 +1363,34 @@ const int SPELL_SPIRIT_WORM = 17248; const int SPELL_FORCE_MISSILES = 2480; //:: Masters of the Wild Spells +const int SPELL_FORESTFOLD = 17090; +const int SPELL_CREEPING_COLD = 17091; +const int SPELL_GREATER_CREEPING_COLD = 17092; +const int SPELL_CONTROL_PLANTS = 17237; +const int SPELL_ADRENALINE_SURGE = 17238; +const int SPELL_INVULNERABILITY_TO_ELEMENTS = 17239; +const int SPELL_REGEN_RING = 17241; +const int SPELL_REGEN_CIRCLE = 17242; const int SPELL_REGEN_LIGHT_WOUNDS = 17243; const int SPELL_REGEN_MODERATE_WOUNDS = 17244; const int SPELL_REGEN_SERIOUS_WOUNDS = 17245; const int SPELL_REGEN_CRITICAL_WOUNDS = 17246; const int SPELL_SPEED_WIND = 17247; -const int SPELL_TORTISE_SHELL = 17250; +const int SPELL_TORTISE_SHELL = 17250; + +//:: Book of Exalted Deeds Spells +const int SPELL_LEONALS_ROAR = 17240; + +//:: Master of the Wild Feats +const int SPELL_VL_WILD_SHAPE_TREANT = 17989; +const int SPELL_VL_ANIMATE_TREE = 17990; +const int SPELL_PLANT_DEFIANCE = 17991; +const int SPELL_PLANT_CONTROL = 17992; + +//:: Book of Exalted Deeds Feats +const int SPELL_FOT_LEONALS_ROAR = 17993; +const int SPELL_FOT_LIONS_SWIFTNESS = 17994; +const int SPELL_FAVORED_OF_THE_COMPANIONS = 17995; //x const int SPELL_TENSERS_FLOATING_DISK = 3849; diff --git a/src/include/prc_spellf_inc.nss b/src/include/prc_spellf_inc.nss index 753680f..1b6042f 100644 --- a/src/include/prc_spellf_inc.nss +++ b/src/include/prc_spellf_inc.nss @@ -299,6 +299,7 @@ int SpellfireDrainItem(object oPC, object oItem, int bCharged = TRUE, int bSingl { if((nBase == BASE_ITEM_POTIONS) || + (nBase == BASE_ITEM_INFUSED_HERB) || (nBase == BASE_ITEM_SCROLL) || (nBase == BASE_ITEM_SPELLSCROLL) || (nBase == BASE_ITEM_BLANK_POTION) || @@ -382,6 +383,7 @@ void SpellfireDrain(object oPC, object oTarget, int bCharged = TRUE, int bExempt if(GetPRCSwitch(PRC_SPELLFIRE_DISALLOW_DRAIN_SCROLL_POTION) && ((nBase == BASE_ITEM_POTIONS) || (nBase == BASE_ITEM_SCROLL) || + (nBase == BASE_ITEM_INFUSED_HERB) || (nBase == BASE_ITEM_BLANK_POTION) || (nBase == BASE_ITEM_BLANK_SCROLL) ) @@ -525,3 +527,4 @@ void SpellfireCrown(object oPC) } } +//:: void main() {} \ No newline at end of file diff --git a/src/include/prc_template_con.nss b/src/include/prc_template_con.nss index fc15544..2955524 100644 --- a/src/include/prc_template_con.nss +++ b/src/include/prc_template_con.nss @@ -18,6 +18,7 @@ const int TEMPLATE_CURST = 26; const int TEMPLATE_GRAVETOUCHED_GHOUL = 29; const int TEMPLATE_CRYPTSPAWN = 30; const int TEMPLATE_ARCHLICH = 99; +const int TEMPLATE_BAELNORN = 100; const int TEMPLATE_LICH = 101; const int TEMPLATE_DEMILICH = 102; const int TEMPLATE_NECROPOLITAN = 105; diff --git a/src/include/prc_x2_craft.nss b/src/include/prc_x2_craft.nss index 5834f63..ac1d795 100644 --- a/src/include/prc_x2_craft.nss +++ b/src/include/prc_x2_craft.nss @@ -13,7 +13,7 @@ //:: Created On: 2003-05-09 //:: Last Updated On: 2003-10-14 //::////////////////////////////////////////////// - +#include "prc_x2_itemprop" struct craft_struct { @@ -44,22 +44,24 @@ const string X2_CI_CRAFTSKILL_CONV ="x2_p_craftskills"; /* moved to be code switches const int X2_CI_BREWPOTION_MAXLEVEL = 3; // Max Level for potions -const int X2_CI_BREWPOTION_COSTMODIFIER = 50; // gp Brew Potion XPCost Modifier +const int PRC_X2_BREWPOTION_COSTMODIFIER = 50; // gp Brew Potion XPCost Modifier // Scribe Scroll related constants -const int X2_CI_SCRIBESCROLL_COSTMODIFIER = 25; // Scribescroll Cost Modifier +const int PRC_X2_SCRIBESCROLL_COSTMODIFIER = 25; // Scribescroll Cost Modifier // Craft Wand related constants -const int X2_CI_CRAFTWAND_MAXLEVEL = 4; -const int X2_CI_CRAFTWAND_COSTMODIFIER = 750; +const int PRC_X2_CRAFTWAND_MAXLEVEL = 4; +const int PRC_X2_CRAFTWAND_COSTMODIFIER = 750; */ -const int X2_CI_BREWPOTION_FEAT_ID = 944; // Brew Potion feat simulation -const int X2_CI_SCRIBESCROLL_FEAT_ID = 945; -const int X2_CI_CRAFTWAND_FEAT_ID = 946; -const int X2_CI_CRAFTROD_FEAT_ID = 2927; -const int X2_CI_CRAFTROD_EPIC_FEAT_ID = 3490; -const int X2_CI_CRAFTSTAFF_FEAT_ID = 2928; -const int X2_CI_CRAFTSTAFF_EPIC_FEAT_ID = 3491; +const int X2_CI_BREWPOTION_FEAT_ID = 944; // Brew Potion feat simulation +const int X2_CI_SCRIBESCROLL_FEAT_ID = 945; +const int X2_CI_CRAFTWAND_FEAT_ID = 946; +const int X2_CI_CRAFTROD_FEAT_ID = 2927; +const int X2_CI_CRAFTROD_EPIC_FEAT_ID = 3490; +const int X2_CI_CRAFTSTAFF_FEAT_ID = 2928; +const int X2_CI_CRAFTSTAFF_EPIC_FEAT_ID = 3491; +const int X2_CI_CREATEINFUSION_FEAT_ID = 25960; + const string X2_CI_BREWPOTION_NEWITEM_RESREF = "x2_it_pcpotion"; // ResRef for new potion item const string X2_CI_SCRIBESCROLL_NEWITEM_RESREF = "x2_it_pcscroll"; // ResRef for new scroll item const string X2_CI_CRAFTWAND_NEWITEM_RESREF = "x2_it_pcwand"; @@ -185,6 +187,17 @@ int CheckAlternativeCrafting(object oPC, int nSpell, struct craft_cost_struct co // Returns the maximum of caster level used and other effective levels from emulating spells int GetAlternativeCasterLevel(object oPC, int nLevel); +// ----------------------------------------------------------------------------- +// Create and Return an herbal infusion with an item property +// matching nSpellID. +// ----------------------------------------------------------------------------- +object CICreateInfusion(object oCreator, int nSpellID); + +// ----------------------------------------------------------------------------- +// Returns TRUE if the player successfully performed Create Infusion +// ----------------------------------------------------------------------------- +int CICraftCheckCreateInfusion(object oSpellTarget, object oCaster, int nID = 0); + ////////////////////////////////////////////////// /* Include section */ ////////////////////////////////////////////////// @@ -194,6 +207,7 @@ int GetAlternativeCasterLevel(object oPC, int nLevel); #include "prc_inc_newip" #include "prc_inc_spells" #include "prc_add_spell_dc" +#include "inc_infusion" ////////////////////////////////////////////////// /* Function definitions */ @@ -261,7 +275,8 @@ int CIGetIsCraftFeatBaseItem(object oItem) nBt == BASE_ITEM_BLANK_SCROLL || nBt == BASE_ITEM_BLANK_WAND || nBt == BASE_ITEM_CRAFTED_ROD || - nBt == BASE_ITEM_CRAFTED_STAFF) + nBt == BASE_ITEM_CRAFTED_STAFF || + nBt == BASE_ITEM_MUNDANE_HERB) return TRUE; else return FALSE; @@ -453,11 +468,158 @@ object CICraftCraftWand(object oCreator, int nSpellID ) // ----------------------------------------------------------------------------- // Georg, 2003-06-12 -// Create and Return a magic wand with an item property -// matching nSpellID. Charges are set to d20 + casterlevel -// capped at 50 max +// Create and Return a magic scroll with an item property +// matching nSpellID. // ----------------------------------------------------------------------------- object CICraftScribeScroll(object oCreator, int nSpellID) +{ + if (DEBUG) DoDebug("CICraftScribeScroll: Enter (nSpellID=" + IntToString(nSpellID) + ")"); + + // Keep original and compute one-step master (if subradial) + int nSpellOriginal = nSpellID; + int nSpellMaster = nSpellOriginal; + if (GetIsSubradialSpell(nSpellOriginal)) + { + nSpellMaster = GetMasterSpellFromSubradial(nSpellOriginal); + if (DEBUG) DoDebug("CICraftScribeScroll: subradial detected original=" + IntToString(nSpellOriginal) + " master=" + IntToString(nSpellMaster)); + } + + // Prefer iprp mapping for the original, fallback to master + int nPropID = IPGetIPConstCastSpellFromSpellID(nSpellOriginal); + int nSpellUsedForIP = nSpellOriginal; + if (nPropID < 0) + { + if (DEBUG) DoDebug("CICraftScribeScroll: no iprp for original " + IntToString(nSpellOriginal) + ", trying master " + IntToString(nSpellMaster)); + nPropID = IPGetIPConstCastSpellFromSpellID(nSpellMaster); + nSpellUsedForIP = nSpellMaster; + } + + // If neither original nor master has an iprp row, we can still try templates, + // but most templates expect a matching iprp. Bail out now if nothing found. + if (nPropID < 0) + { + if (DEBUG) DoDebug("CICraftScribeScroll: no iprp_spells entry for original/master -> aborting"); + FloatingTextStringOnCreature("This spell cannot be scribed (no item property mapping).", oCreator, FALSE); + return OBJECT_INVALID; + } + + if (DEBUG) DoDebug("CICraftScribeScroll: using spell " + IntToString(nSpellUsedForIP) + " (iprp row " + IntToString(nPropID) + ") for item property"); + + // Material component check (based on resolved iprp row) + string sMat = GetMaterialComponentTag(nPropID); + if (sMat != "") + { + object oMat = GetItemPossessedBy(oCreator, sMat); + if (oMat == OBJECT_INVALID) + { + FloatingTextStrRefOnCreature(83374, oCreator); // Missing material component + return OBJECT_INVALID; + } + else + { + DestroyObject(oMat); + } + } + + // Resolve class and scroll template + int nClass = PRCGetLastSpellCastClass(); + string sClass = ""; + switch (nClass) + { + case CLASS_TYPE_WIZARD: + case CLASS_TYPE_SORCERER: sClass = "Wiz_Sorc"; break; + case CLASS_TYPE_CLERIC: + case CLASS_TYPE_UR_PRIEST: sClass = "Cleric"; break; + case CLASS_TYPE_PALADIN: sClass = "Paladin"; break; + case CLASS_TYPE_DRUID: + case CLASS_TYPE_BLIGHTER: sClass = "Druid"; break; + case CLASS_TYPE_RANGER: sClass = "Ranger"; break; + case CLASS_TYPE_BARD: sClass = "Bard"; break; + case CLASS_TYPE_ASSASSIN: sClass = "Assassin"; break; + } + + object oTarget = OBJECT_INVALID; + string sResRef = ""; + + // Try to find a class-specific scroll template. + if (sClass != "") + { + // Try original first (so if you made a subradial-specific template it will be used) + sResRef = Get2DACache(X2_CI_2DA_SCROLLS, sClass, nSpellOriginal); + if (sResRef == "") + { + // fallback to the spell that matched an iprp row (master or original) + sResRef = Get2DACache(X2_CI_2DA_SCROLLS, sClass, nSpellUsedForIP); + } + if (sResRef != "") + { + oTarget = CreateItemOnObject(sResRef, oCreator); + if (DEBUG) DoDebug("CICraftScribeScroll: created template " + sResRef + " for class " + sClass); + // Ensure template uses the correct cast-spell property: replace the template's cast-spell IP with ours + if (oTarget != OBJECT_INVALID) + { + itemproperty ipIter = GetFirstItemProperty(oTarget); + while (GetIsItemPropertyValid(ipIter)) + { + if (GetItemPropertyType(ipIter) == ITEM_PROPERTY_CAST_SPELL) + { + RemoveItemProperty(oTarget, ipIter); + break; + } + ipIter = GetNextItemProperty(oTarget); + } + itemproperty ipSpell = ItemPropertyCastSpell(nPropID, IP_CONST_CASTSPELL_NUMUSES_SINGLE_USE); + AddItemProperty(DURATION_TYPE_PERMANENT, ipSpell, oTarget); + } + } + } + + // If no template or sClass was empty, create generic scroll and add itemprop. + if (oTarget == OBJECT_INVALID) + { + sResRef = "craft_scroll"; + oTarget = CreateItemOnObject(sResRef, oCreator); + if (oTarget == OBJECT_INVALID) + { + WriteTimestampedLogEntry("CICraftScribeScroll: failed to create craft_scroll template."); + return OBJECT_INVALID; + } + // Remove existing default IP and add correct one + itemproperty ipFirst = GetFirstItemProperty(oTarget); + if (GetIsItemPropertyValid(ipFirst)) + RemoveItemProperty(oTarget, ipFirst); + + itemproperty ipSpell = ItemPropertyCastSpell(nPropID, IP_CONST_CASTSPELL_NUMUSES_SINGLE_USE); + AddItemProperty(DURATION_TYPE_PERMANENT, ipSpell, oTarget); + } + + // Add PRC metadata (use the same spell that matched the iprp row so metadata and IP line up) + if (GetPRCSwitch(PRC_SCRIBE_SCROLL_CASTER_LEVEL)) + { + int nCasterLevel = GetAlternativeCasterLevel(oCreator, PRCGetCasterLevel(oCreator)); + itemproperty ipLevel = ItemPropertyCastSpellCasterLevel(nSpellUsedForIP, nCasterLevel); + AddItemProperty(DURATION_TYPE_PERMANENT, ipLevel, oTarget); + + itemproperty ipMeta = ItemPropertyCastSpellMetamagic(nSpellUsedForIP, PRCGetMetaMagicFeat()); + AddItemProperty(DURATION_TYPE_PERMANENT, ipMeta, oTarget); + + int nDC = PRCGetSpellSaveDC(nSpellUsedForIP, GetSpellSchool(nSpellUsedForIP), OBJECT_SELF); + itemproperty ipDC = ItemPropertyCastSpellDC(nSpellUsedForIP, nDC); + AddItemProperty(DURATION_TYPE_PERMANENT, ipDC, oTarget); + } + + if (oTarget == OBJECT_INVALID) + { + WriteTimestampedLogEntry("prc_x2_craft::CICraftScribeScroll failed - Resref: " + sResRef + " Class: " + sClass + "(" + IntToString(nClass) + ") " + " SpellID " + IntToString(nSpellID)); + return OBJECT_INVALID; + } + + if (DEBUG) DoDebug("CICraftScribeScroll: Success - created scroll " + sResRef + " for spell " + IntToString(nSpellUsedForIP)); + return oTarget; +} + + +/* object CICraftScribeScroll(object oCreator, int nSpellID) { int nPropID = IPGetIPConstCastSpellFromSpellID(nSpellID); object oTarget; @@ -491,6 +653,7 @@ object CICraftScribeScroll(object oCreator, int nSpellID) break; case CLASS_TYPE_CLERIC: case CLASS_TYPE_UR_PRIEST: + case CLASS_TYPE_OCULAR: sClass = "Cleric"; break; case CLASS_TYPE_PALADIN: @@ -506,6 +669,9 @@ object CICraftScribeScroll(object oCreator, int nSpellID) case CLASS_TYPE_BARD: sClass = "Bard"; break; + case CLASS_TYPE_ASSASSIN: + sClass = "Assassin"; + break; } string sResRef; if (sClass != "") @@ -542,7 +708,7 @@ object CICraftScribeScroll(object oCreator, int nSpellID) } return oTarget; } - + */ // ----------------------------------------------------------------------------- // Returns TRUE if the player used the last spell to brew a potion // ----------------------------------------------------------------------------- @@ -593,7 +759,7 @@ These dont work as IPs since they are hardcoded */ // ------------------------------------------------------------------------- // check if spell is below maxlevel for brew potions // ------------------------------------------------------------------------- - int nPotionMaxLevel = GetPRCSwitch(X2_CI_BREWPOTION_MAXLEVEL); + int nPotionMaxLevel = GetPRCSwitch(PRC_X2_BREWPOTION_MAXLEVEL); if(nPotionMaxLevel == 0) nPotionMaxLevel = 3; @@ -624,7 +790,7 @@ These dont work as IPs since they are hardcoded */ // ------------------------------------------------------------------------- // XP/GP Cost Calculation // ------------------------------------------------------------------------- - int nCostModifier = GetPRCSwitch(X2_CI_BREWPOTION_COSTMODIFIER); + int nCostModifier = GetPRCSwitch(PRC_X2_BREWPOTION_COSTMODIFIER); if(nCostModifier == 0) nCostModifier = 50; int nCost = CIGetCraftGPCost(nLevel, nCostModifier, PRC_BREW_POTION_CASTER_LEVEL); @@ -728,7 +894,7 @@ int CICraftCheckScribeScroll(object oSpellTarget, object oCaster, int nID = 0) // XP/GP Cost Calculation // ------------------------------------------------------------------------- int nLevel = CIGetSpellInnateLevel(nID,TRUE); - int nCostModifier = GetPRCSwitch(X2_CI_SCRIBESCROLL_COSTMODIFIER); + int nCostModifier = GetPRCSwitch(PRC_X2_SCRIBESCROLL_COSTMODIFIER); if(nCostModifier == 0) nCostModifier = 25; int nCost = CIGetCraftGPCost(nLevel, nCostModifier, PRC_SCRIBE_SCROLL_CASTER_LEVEL); @@ -884,7 +1050,7 @@ These dont work as IPs since they are hardcoded */ // ------------------------------------------------------------------------- // check if spell is below maxlevel for craft want // ------------------------------------------------------------------------- - int nMaxLevel = GetPRCSwitch(X2_CI_CRAFTWAND_MAXLEVEL); + int nMaxLevel = GetPRCSwitch(PRC_X2_CRAFTWAND_MAXLEVEL); if(nMaxLevel == 0) nMaxLevel = 4; if (nLevel > nMaxLevel) @@ -896,7 +1062,7 @@ These dont work as IPs since they are hardcoded */ // ------------------------------------------------------------------------- // XP/GP Cost Calculation // ------------------------------------------------------------------------- - int nCostMod = GetPRCSwitch(X2_CI_CRAFTWAND_COSTMODIFIER); + int nCostMod = GetPRCSwitch(PRC_X2_CRAFTWAND_COSTMODIFIER); if(nCostMod == 0) nCostMod = 750; int nCost = CIGetCraftGPCost(nLevel, nCostMod, PRC_CRAFT_WAND_CASTER_LEVEL); @@ -1027,7 +1193,7 @@ int CICraftCheckCraftStaff(object oSpellTarget, object oCaster, int nSpellID = 0 These dont work as IPs since they are hardcoded */ } } - int nCostMod = GetPRCSwitch(X2_CI_CRAFTSTAFF_COSTMODIFIER); + int nCostMod = GetPRCSwitch(PRC_X2_CRAFTSTAFF_COSTMODIFIER); if(!nCostMod) nCostMod = 750; int nLvlRow = IPGetIPConstCastSpellFromSpellID(nSpellID); int nCLevel = StringToInt(Get2DACache("iprp_spells","CasterLvl",nLvlRow)); @@ -1175,7 +1341,7 @@ int CICraftCheckCraftRod(object oSpellTarget, object oCaster, int nSpellID = 0) These dont work as IPs since they are hardcoded */ } } - int nCostMod = GetPRCSwitch(X2_CI_CRAFTROD_COSTMODIFIER); + int nCostMod = GetPRCSwitch(PRC_X2_CRAFTROD_COSTMODIFIER); if(!nCostMod) nCostMod = 750; int nLvlRow = IPGetIPConstCastSpellFromSpellID(nSpellID); int nCLevel = StringToInt(Get2DACache("iprp_spells","CasterLvl",nLvlRow)); @@ -1544,7 +1710,7 @@ int AttuneGem(object oTarget = OBJECT_INVALID, object oCaster = OBJECT_INVALID, // No point scribing Gems from items, and its not allowed. if (oItem != OBJECT_INVALID) { - FloatingTextStringOnCreature("You cannot scribe a Gem from an item.", oCaster, FALSE); + FloatingTextStringOnCreature("You cannot attune a Gem from an item.", oCaster, FALSE); return TRUE; } // oTarget here should be the gem. If it's not, fail. @@ -1951,7 +2117,13 @@ int CIGetSpellWasUsedForItemCreation(object oSpellTarget) // ------------------------------------------------- nRet = CICraftCheckCraftStaff(oSpellTarget,oCaster); break; - + + case BASE_ITEM_MUNDANE_HERB : + // ------------------------------------------------- + // Create Infusion + // ------------------------------------------------- + nRet = CICraftCheckCreateInfusion(oSpellTarget,oCaster); + break; // you could add more crafting basetypes here.... } @@ -2740,6 +2912,11 @@ int GetMagicalArtisanFeat(int nCraftingFeat) nReturn = FEAT_MAGICAL_ARTISAN_CRAFT_SKULL_TALISMAN; break; } + case FEAT_CREATE_INFUSION: + { + nReturn = FEAT_MAGICAL_ARTISAN_CREATE_INFUSION; + break; + } default: { if(DEBUG) DoDebug("GetMagicalArtisanFeat: invalid crafting feat"); @@ -2941,6 +3118,306 @@ int GetAlternativeCasterLevel(object oPC, int nLevel) return nLevel; } +// ----------------------------------------------------------------------------- +// Returns TRUE if the player successfully performed Create Infusion +// ----------------------------------------------------------------------------- +int CICraftCheckCreateInfusion(object oSpellTarget, object oCaster, int nID = 0) +{ + if (nID == 0) nID = PRCGetSpellId(); + + int bIsSubradial = GetIsSubradialSpell(nID); + + if(bIsSubradial) + { + nID = GetMasterSpellFromSubradial(nID); + } + + // ------------------------------------------------------------------------- + // Check if the caster has the Create Infusion feat + // ------------------------------------------------------------------------- + if (!GetHasFeat(FEAT_CREATE_INFUSION, oCaster)) + { + FloatingTextStrRefOnCreature(40487, oCaster); // Missing feat + return TRUE; + } + + // ------------------------------------------------------------------------- + // Divine spellcasters only + // ------------------------------------------------------------------------- + int nClass = PRCGetLastSpellCastClass(); + if (!GetIsDivineClass(nClass)) + { + FloatingTextStringOnCreature("Only divine casters can create infusions.", oCaster, FALSE); + return TRUE; + } + + // ------------------------------------------------------------------------- + // Check if spell is restricted for Create Infusion + // ------------------------------------------------------------------------- + if (CIGetIsSpellRestrictedFromCraftFeat(nID, X2_CI_CREATEINFUSION_FEAT_ID)) + { + FloatingTextStrRefOnCreature(83451, oCaster); // Spell not allowed + return TRUE; + } + + // ------------------------------------------------------------------------- + // Optional PnP Herb check + // ------------------------------------------------------------------------- + int bPnPHerbs = GetPRCSwitch(PRC_CREATE_INFUSION_OPTIONAL_HERBS); + if(bPnPHerbs) + { + int nSpellschool = GetSpellSchool(nID); + int nHerbSchool = GetHerbsSpellSchool(oSpellTarget); + + int nSpellLevel = PRCGetSpellLevelForClass(nID, nClass); + int nHerbLevel = GetHerbsInfusionSpellLevel(oSpellTarget); + + if(nSpellschool != nHerbSchool) + { + // Herb is for wrong spellschool + FloatingTextStringOnCreature("This herb isn't appropriate for this spell school", oCaster); + return TRUE; + } + + if(nSpellLevel > nHerbLevel) + { + // Herb spell circle level too low + FloatingTextStringOnCreature("This herb isn't appropriate for this spell level", oCaster); + return TRUE; + } + } + + // ------------------------------------------------------------------------- + // XP/GP Cost Calculation + // ------------------------------------------------------------------------- + int nLevel = CIGetSpellInnateLevel(nID, TRUE); + int nCostModifier = GetPRCSwitch(PRC_X2_CREATEINFUSION_COSTMODIFIER); + if (nCostModifier == 0) + nCostModifier = 25; + + int nCost = CIGetCraftGPCost(nLevel, nCostModifier, PRC_CREATE_INFUSION_CASTER_LEVEL); + struct craft_cost_struct costs = GetModifiedCostsFromBase(nCost, oCaster, FEAT_CREATE_INFUSION, FALSE); + + // Adjust level for metamagic + if (GetPRCSwitch(PRC_CREATE_INFUSION_CASTER_LEVEL)) + { + int nMetaMagic = PRCGetMetaMagicFeat(); + switch(nMetaMagic) + { + case METAMAGIC_EMPOWER: nLevel += 2; break; + case METAMAGIC_EXTEND: nLevel += 1; break; + case METAMAGIC_MAXIMIZE: nLevel += 3; break; + // Unsupported metamagic IPs not added + } + } + + // ------------------------------------------------------------------------- + // Check Gold + // ------------------------------------------------------------------------- + if (!GetHasGPToSpend(oCaster, costs.nGoldCost)) + { + FloatingTextStrRefOnCreature(3786, oCaster); // Not enough gold + return TRUE; + } + + // ------------------------------------------------------------------------- + // Check XP + // ------------------------------------------------------------------------- + if (!GetHasXPToSpend(oCaster, costs.nXPCost)) + { + FloatingTextStrRefOnCreature(3785, oCaster); // Not enough XP + return TRUE; + } + + // ------------------------------------------------------------------------- + // Check alternative spell emulation requirements + // ------------------------------------------------------------------------- + if (!CheckAlternativeCrafting(oCaster, nID, costs)) + { + FloatingTextStringOnCreature("*Crafting failed!*", oCaster, FALSE); + return TRUE; + } + + // ------------------------------------------------------------------------- + // Create the infused herb item + // ------------------------------------------------------------------------- + object oInfusion = CICreateInfusion(oCaster, nID); + + if (GetIsObjectValid(oInfusion)) + { + // Get the spell's display name from spells.2da via TLK + int nNameStrRef = StringToInt(Get2DAString("spells", "Name", nID)); + string sSpellName = GetStringByStrRef(nNameStrRef); + + // Rename the item + string sNewName = "Infusion of " + sSpellName; + SetName(oInfusion, sNewName); + + // Post-creation actions + SetIdentified(oInfusion, TRUE); + ActionPlayAnimation(ANIMATION_FIREFORGET_READ, 1.0); + SpendXP(oCaster, costs.nXPCost); + SpendGP(oCaster, costs.nGoldCost); + DestroyObject(oSpellTarget); + FloatingTextStrRefOnCreature(8502, oCaster); // Item creation successful + + if (!costs.nTimeCost) costs.nTimeCost = 1; + AdvanceTimeForPlayer(oCaster, RoundsToSeconds(costs.nTimeCost)); + return TRUE; + } + else + { + FloatingTextStringOnCreature("Infusion creation failed", oCaster); // Item creation failed + FloatingTextStrRefOnCreature(76417, oCaster); // Item creation failed + return TRUE; + } + +/* // ------------------------------------------------------------------------- + // Create the infused herb item + // ------------------------------------------------------------------------- + object oInfusion = CICreateInfusion(oCaster, nID); + + if (GetIsObjectValid(oInfusion)) + { + SetIdentified(oInfusion, TRUE); + ActionPlayAnimation(ANIMATION_FIREFORGET_READ, 1.0); + SpendXP(oCaster, costs.nXPCost); + SpendGP(oCaster, costs.nGoldCost); + DestroyObject(oSpellTarget); + FloatingTextStrRefOnCreature(8502, oCaster); // Item creation successful + + if (!costs.nTimeCost) costs.nTimeCost = 1; + AdvanceTimeForPlayer(oCaster, RoundsToSeconds(costs.nTimeCost)); + return TRUE; + } + else + { + FloatingTextStringOnCreature("Infusion creation failed", oCaster); // Item creation failed + FloatingTextStrRefOnCreature(76417, oCaster); // Item creation failed + return TRUE; + } */ + + return FALSE; +} + +// ----------------------------------------------------------------------------- +// Create and return an herbal infusion with an item property matching nSpellID +// ----------------------------------------------------------------------------- +object CICreateInfusion(object oCreator, int nSpellID) +{ + if (DEBUG) DoDebug("prc_x2_craft >> CICreateInfusion: Entering function"); + + // Keep the original spell id the engine gave us (may be a subradial) + int nSpellOriginal = nSpellID; + if (DEBUG) DoDebug("prc_x2_craft >> CICreateInfusion: nSpellOriginal is "+IntToString(nSpellOriginal)+"."); + + // Compute the master if this is a subradial. Keep original intact. + int nSpellMaster = nSpellOriginal; + if (GetIsSubradialSpell(nSpellOriginal)) + { + nSpellMaster = GetMasterSpellFromSubradial(nSpellOriginal); + if (DEBUG) DoDebug("CICreateInfusion: detected subradial " + IntToString(nSpellOriginal) + " master -> " + IntToString(nSpellMaster)); + } + if (DEBUG) DoDebug("prc_x2_craft >> CICreateInfusion: nSpellMaster is "+IntToString(nSpellMaster)+"."); + + // Try to find an iprp_spells row for the original subradial first (preferred). + int nPropID = IPGetIPConstCastSpellFromSpellID(nSpellOriginal); + int nSpellUsedForIP = nSpellOriginal; + + // If not found for original, fall back to the master/base spell. + if (nPropID < 0) + { + if (DEBUG) DoDebug("CICreateInfusion: no iprp row for original " + IntToString(nSpellOriginal) + ", trying master " + IntToString(nSpellMaster)); + nPropID = IPGetIPConstCastSpellFromSpellID(nSpellMaster); + nSpellUsedForIP = nSpellMaster; + } + + // If still invalid, bail out with a helpful message + if (nPropID < 0) + { + if (DEBUG) DoDebug("CICreateInfusion: No iprp_spells entry for either original " + IntToString(nSpellOriginal) + " or master " + IntToString(nSpellMaster)); + FloatingTextStringOnCreature("This spell cannot be infused (no item property mapping).", oCreator, FALSE); + return OBJECT_INVALID; + } + + if (DEBUG) DoDebug("CICreateInfusion: using spell " + IntToString(nSpellUsedForIP) + " (iprp row " + IntToString(nPropID) + ") for item property"); + + // Optional: check for material component (use the resolved iprp row) + string sMat = GetMaterialComponentTag(nPropID); + if (sMat != "") + { + object oMat = GetItemPossessedBy(oCreator, sMat); + if (oMat == OBJECT_INVALID) + { + FloatingTextStrRefOnCreature(83374, oCreator); // Missing material component + return OBJECT_INVALID; + } + else + { + DestroyObject(oMat); + } + } + + // Only allow divine spellcasters + int nClass = PRCGetLastSpellCastClass(); + if (!GetIsDivineClass(nClass)) + { + FloatingTextStringOnCreature("Only divine casters can use Create Infusion.", oCreator, FALSE); + return OBJECT_INVALID; + } + + // Create base infusion item (herb) + string sResRef = "prc_infusion_000"; + object oTarget = CreateItemOnObject(sResRef, oCreator); + if (oTarget == OBJECT_INVALID) + { + WriteTimestampedLogEntry("Create Infusion failed: couldn't create item with resref " + sResRef); + return OBJECT_INVALID; + } + + // Confirm that the item is a herb + int nBaseItem = GetBaseItemType(oTarget); + if (nBaseItem != BASE_ITEM_INFUSED_HERB) + { + FloatingTextStringOnCreature("Only herbs may be infused.", oCreator, FALSE); + DestroyObject(oTarget); + return OBJECT_INVALID; + } + + // Remove all non-material item properties from the herb + itemproperty ipRemove = GetFirstItemProperty(oTarget); + while (GetIsItemPropertyValid(ipRemove)) + { + itemproperty ipNext = GetNextItemProperty(oTarget); + if (GetItemPropertyType(ipRemove) != ITEM_PROPERTY_MATERIAL) + RemoveItemProperty(oTarget, ipRemove); + ipRemove = ipNext; + } + + // Add the cast-spell itemproperty using the iprp row we resolved + itemproperty ipSpell = ItemPropertyCastSpell(nPropID, IP_CONST_CASTSPELL_NUMUSES_SINGLE_USE); + AddItemProperty(DURATION_TYPE_PERMANENT, ipSpell, oTarget); + + // Optional PRC casting metadata: use the SAME spell id that matched the iprp row + // so caster level/DC/meta line up with the actual cast property on the item. + if (GetPRCSwitch(PRC_CREATE_INFUSION_CASTER_LEVEL)) + { + int nCasterLevel = GetAlternativeCasterLevel(oCreator, PRCGetCasterLevel(oCreator)); + // nSpellUsedForIP is either original (if that had an iprp row) or the master (fallback) + itemproperty ipLevel = ItemPropertyCastSpellCasterLevel(nSpellUsedForIP, nCasterLevel); + AddItemProperty(DURATION_TYPE_PERMANENT, ipLevel, oTarget); + + itemproperty ipMeta = ItemPropertyCastSpellMetamagic(nSpellUsedForIP, PRCGetMetaMagicFeat()); + AddItemProperty(DURATION_TYPE_PERMANENT, ipMeta, oTarget); + + int nDC = PRCGetSpellSaveDC(nSpellUsedForIP, GetSpellSchool(nSpellUsedForIP), OBJECT_SELF); + itemproperty ipDC = ItemPropertyCastSpellDC(nSpellUsedForIP, nDC); + AddItemProperty(DURATION_TYPE_PERMANENT, ipDC, oTarget); + } + + return oTarget; +} + // Test main -//void main(){} +// void main(){} diff --git a/src/include/prc_x2_itemprop.nss b/src/include/prc_x2_itemprop.nss index e4a1be0..17885cd 100644 --- a/src/include/prc_x2_itemprop.nss +++ b/src/include/prc_x2_itemprop.nss @@ -768,7 +768,6 @@ int IPGetIsBludgeoningWeapon(object oItem) // ---------------------------------------------------------------------------- // Return the IP_CONST_CASTSPELL_* ID matching to the SPELL_* constant given // in nSPELL_ID. -// This uses Get2DAstring, so it is slow. Avoid using in loops! // returns -1 if there is no matching property for a spell // ---------------------------------------------------------------------------- int IPGetIPConstCastSpellFromSpellID(int nSpellID) @@ -1883,7 +1882,7 @@ int IPDamageConstant(int nDamBon) case 49: nIPBonus = IP_CONST_DAMAGEBONUS_49; break; case 50: nIPBonus = IP_CONST_DAMAGEBONUS_50; break; } - if (nDamBon > 20) nIPBonus = IP_CONST_DAMAGEBONUS_50; + if (nDamBon > 50) nIPBonus = IP_CONST_DAMAGEBONUS_50; return nIPBonus; } diff --git a/src/include/shd_inc_mystknwn.nss b/src/include/shd_inc_mystknwn.nss index bc6ecc5..b335485 100644 --- a/src/include/shd_inc_mystknwn.nss +++ b/src/include/shd_inc_mystknwn.nss @@ -41,6 +41,7 @@ const string _MYSTERY_LIST_MISC_ARRAY = "_MysteriesKnownMiscArray"; const string _MYSTERY_LIST_LEVEL_ARRAY = "_MysteriesKnownLevelArray_"; const string _MYSTERY_LIST_GENERAL_ARRAY = "_MysteriesKnownGeneralArray"; +#include "shd_inc_shdfunc" ////////////////////////////////////////////////// /* Function prototypes */ diff --git a/src/include/shd_inc_shdfunc.nss b/src/include/shd_inc_shdfunc.nss index 63eb943..6ae4f63 100644 --- a/src/include/shd_inc_shdfunc.nss +++ b/src/include/shd_inc_shdfunc.nss @@ -236,12 +236,12 @@ int GetShadowcasterLevel(object oShadow = OBJECT_SELF, int nSpecificClass = CLAS // For when you want to assign the caster level. if(nLevel) { - if(DEBUG) SendMessageToPC(oShadow, "GetShadowcasterLevel(): Forced-level shadowcasting at level " + IntToString(nLevel)); + if(DEBUG) DoDebug("GetShadowcasterLevel(): Forced-level shadowcasting at level " + IntToString(nLevel)); //DelayCommand(1.0, DeleteLocalInt(oShadow, PRC_CASTERLEVEL_OVERRIDE)); return nLevel + nAdjust; } - if (DEBUG) FloatingTextStringOnCreature("GetShadowcasterLevel: "+GetName(oShadow)+" is a "+IntToString(nSpecificClass), oShadow); + if (DEBUG) DoDebug("GetShadowcasterLevel: "+GetName(oShadow)+" is a "+IntToString(nSpecificClass), oShadow); // The function user needs to know the character's Shadowcaster level in a specific class // instead of whatever the character last shadowcast a mystery as if(nSpecificClass != CLASS_TYPE_INVALID) @@ -288,7 +288,7 @@ int GetShadowcasterLevel(object oShadow = OBJECT_SELF, int nSpecificClass = CLAS nLevel -= 4; } - if(DEBUG) FloatingTextStringOnCreature("Shadowcaster Level: " + IntToString(nLevel), oShadow, FALSE); + if(DEBUG) DoDebug("Shadowcaster Level: " + IntToString(nLevel)); return nLevel + nAdjust; } diff --git a/src/include/tob_inc_tobfunc.nss b/src/include/tob_inc_tobfunc.nss index 3d74cbd..d339082 100644 --- a/src/include/tob_inc_tobfunc.nss +++ b/src/include/tob_inc_tobfunc.nss @@ -1154,6 +1154,7 @@ int GetIsDisciplineWeapon(object oWeapon, int nDiscipline) // Invalid is empty handed / Unarmed strike if(nType == BASE_ITEM_INVALID || nType == BASE_ITEM_QUARTERSTAFF + || nType == BASE_ITEM_MAGICSTAFF || nType == BASE_ITEM_SHORTSWORD || nType == BASE_ITEM_NUNCHAKU) return TRUE; diff --git a/src/include/x2_inc_spellhook.nss b/src/include/x2_inc_spellhook.nss index 2785789..92668bf 100644 --- a/src/include/x2_inc_spellhook.nss +++ b/src/include/x2_inc_spellhook.nss @@ -144,8 +144,103 @@ int PRCGetUserSpecificSpellScriptFinished(); #include "pnp_shft_main" #include "inc_dynconv" #include "inc_npc" +#include "inc_infusion" +#include "prc_add_spell_dc" + + +int Spontaneity(object oCaster, int nCastingClass, int nSpellID, int nSpellLevel) +{ + if(GetLocalInt(oCaster, "PRC_SpontRegen")) + { + DeleteLocalInt(oCaster, "PRC_SpontRegen"); + + int nMetamagic = GetMetaMagicFeat();//we need bioware metamagic here + nSpellLevel = PRCGetSpellLevelForClass(nSpellID, nCastingClass); + nSpellLevel += GetMetaMagicSpellLevelAdjustment(nMetamagic); + + int nRegenSpell; + + if(nCastingClass == CLASS_TYPE_DRUID) + { + switch(nSpellLevel) + { + case 0: return TRUE; + case 1: nRegenSpell = SPELL_REGEN_LIGHT_WOUNDS; break; + case 2: nRegenSpell = SPELL_REGEN_MODERATE_WOUNDS; break; + case 3: nRegenSpell = SPELL_REGEN_RING; break; + case 4: nRegenSpell = SPELL_REGEN_SERIOUS_WOUNDS; break; + case 5: nRegenSpell = SPELL_REGEN_CRITICAL_WOUNDS; break; + case 6: nRegenSpell = SPELL_REGEN_CIRCLE; break; + case 7: nRegenSpell = SPELL_REGEN_CIRCLE; break; + case 8: nRegenSpell = SPELL_REGEN_CIRCLE; break; + case 9: nRegenSpell = SPELL_REGENERATE; break; + } + ActionCastSpell(nRegenSpell, 0, 0, 0, METAMAGIC_NONE, CLASS_TYPE_DRUID); + } + else + { + switch(nSpellLevel) + { + case 0: return TRUE; + case 1: nRegenSpell = SPELL_REGEN_LIGHT_WOUNDS; break; + case 2: nRegenSpell = SPELL_REGEN_LIGHT_WOUNDS; break; + case 3: nRegenSpell = SPELL_REGEN_MODERATE_WOUNDS; break; + case 4: nRegenSpell = SPELL_REGEN_MODERATE_WOUNDS; break; + case 5: nRegenSpell = SPELL_REGEN_SERIOUS_WOUNDS; break; + case 6: nRegenSpell = SPELL_REGEN_CRITICAL_WOUNDS; break; + case 7: nRegenSpell = SPELL_REGENERATE; break; + case 8: nRegenSpell = SPELL_REGENERATE; break; + case 9: nRegenSpell = SPELL_REGENERATE; break; + } + + ActionCastSpell(nRegenSpell, 0, 0, 0, METAMAGIC_NONE, nCastingClass); + } + //Don't cast original spell + return FALSE; + } + return TRUE; +} int DruidSpontSummon(object oCaster, int nCastingClass, int nSpellID, int nSpellLevel) +{ + if(nCastingClass != CLASS_TYPE_DRUID) + return TRUE; + + if(GetLocalInt(oCaster, "PRC_SpontSummon")) + { + DeleteLocalInt(oCaster, "PRC_SpontSummon"); + int nMetamagic = GetMetaMagicFeat();//we need bioware metamagic here + int nSpellLevel = PRCGetSpellLevelForClass(nSpellID, CLASS_TYPE_DRUID); + nSpellLevel += GetMetaMagicSpellLevelAdjustment(nMetamagic); + int nSummonSpell; + switch(nSpellLevel) + { + case 0: return TRUE; + case 1: nSummonSpell = SPELL_SUMMON_NATURES_ALLY_1; break; + case 2: nSummonSpell = SPELL_SUMMON_NATURES_ALLY_2; break; + case 3: nSummonSpell = SPELL_SUMMON_NATURES_ALLY_3; break; + case 4: nSummonSpell = SPELL_SUMMON_NATURES_ALLY_4; break; + case 5: nSummonSpell = SPELL_SUMMON_NATURES_ALLY_5; break; + case 6: nSummonSpell = SPELL_SUMMON_NATURES_ALLY_6; break; + case 7: nSummonSpell = SPELL_SUMMON_NATURES_ALLY_7; break; + case 8: nSummonSpell = SPELL_SUMMON_NATURES_ALLY_8; break; + case 9: nSummonSpell = SPELL_SUMMON_NATURES_ALLY_9; break; + } + + //:: All SNA spells are subradial spells + SetLocalInt(oCaster, "DomainOrigSpell", nSummonSpell); + SetLocalInt(oCaster, "DomainCastLevel", nSpellLevel); + SetLocalInt(oCaster, "DomainCastClass", CLASS_TYPE_DRUID); + StartDynamicConversation("prc_domain_conv", oCaster, DYNCONV_EXIT_NOT_ALLOWED, FALSE, TRUE, oCaster); + + //Don't cast original spell + return FALSE; + } + + return TRUE; +} + +/* int DruidSpontSummon(object oCaster, int nCastingClass, int nSpellID, int nSpellLevel) { if(nCastingClass != CLASS_TYPE_DRUID) return TRUE; @@ -191,6 +286,8 @@ int DruidSpontSummon(object oCaster, int nCastingClass, int nSpellID, int nSpell return TRUE; } + */ + int ArcaneSpellFailure(object oCaster, int nCastingClass, int nSpellLevel, int nMetamagic, string sComponents) { if(!GetIsArcaneClass(nCastingClass)) @@ -904,7 +1001,8 @@ int ShifterCasting(object oCaster, object oSpellCastItem, int nSpellLevel, int n { // Potion drinking is not restricted if(GetBaseItemType(oSpellCastItem) == BASE_ITEM_ENCHANTED_POTION - || GetBaseItemType(oSpellCastItem) == BASE_ITEM_POTIONS) + || GetBaseItemType(oSpellCastItem) == BASE_ITEM_POTIONS + || GetBaseItemType(oSpellCastItem) == BASE_ITEM_INFUSED_HERB) return TRUE; //OnHit properties on equipped items not restricted @@ -1441,8 +1539,9 @@ int CheckSecondaryPrC(object oPC = OBJECT_SELF) if (GetHasFeat(FEAT_WILDMAGE_SPELLCASTING_BARD)) return TRUE; if (GetHasFeat(FEAT_WWOC_SPELLCASTING_BARD)) return TRUE; } - else if (bBeguiler) + if (bBeguiler) { + if(DEBUG) DoDebug("x2_inc_spellhook: CheckSecondaryPrC >>> Entering Beguiler", oPC); if (GetHasFeat(FEAT_ABCHAMP_SPELLCASTING_BEGUILER)) return TRUE; if (GetHasFeat(FEAT_AOTS_SPELLCASTING_BEGUILER)) return TRUE; if (GetHasFeat(FEAT_ALCHEM_SPELLCASTING_BEGUILER)) return TRUE; @@ -1492,8 +1591,9 @@ int CheckSecondaryPrC(object oPC = OBJECT_SELF) } - else if (bDuskblade) + if (bDuskblade) { + if(DEBUG) DoDebug("x2_inc_spellhook: CheckSecondaryPrC >>> Entering Dusblade", oPC); if (GetHasFeat(FEAT_ABCHAMP_SPELLCASTING_DUSKBLADE)) return TRUE; if (GetHasFeat(FEAT_AOTS_SPELLCASTING_DUSKBLADE)) return TRUE; if (GetHasFeat(FEAT_ALCHEM_SPELLCASTING_DUSKBLADE)) return TRUE; @@ -1540,8 +1640,9 @@ int CheckSecondaryPrC(object oPC = OBJECT_SELF) } - else if (bSorcerer) + if (bSorcerer) { + if(DEBUG) DoDebug("x2_inc_spellhook: CheckSecondaryPrC >>> Entering Sorcerer", oPC); if (GetHasFeat(FEAT_ABERRATION_SPELLCASTING_DRIDER)) return TRUE; if (GetHasFeat(FEAT_MONSTROUS_SPELLCASTING_ARKAMOI)) return TRUE; if (GetHasFeat(FEAT_MONSTROUS_SPELLCASTING_MARRUTACT)) return TRUE; @@ -1599,8 +1700,9 @@ int CheckSecondaryPrC(object oPC = OBJECT_SELF) if (GetHasFeat(FEAT_WILDMAGE_SPELLCASTING_SORCERER)) return TRUE; if (GetHasFeat(FEAT_WWOC_SPELLCASTING_SORCERER)) return TRUE; } - else if (bWarmage) + if (bWarmage) { + if(DEBUG) DoDebug("x2_inc_spellhook: CheckSecondaryPrC >>> Entering Warmage", oPC); if (GetHasFeat(FEAT_AOTS_SPELLCASTING_WARMAGE)) return TRUE; if (GetHasFeat(FEAT_ALCHEM_SPELLCASTING_WARMAGE)) return TRUE; if (GetHasFeat(FEAT_ANIMA_SPELLCASTING_WARMAGE)) return TRUE; @@ -1662,14 +1764,71 @@ int BardSorcPrCCheck(object oCaster, int nCastingClass, object oSpellCastItem) return TRUE; } - //check its a sorc spell + //check its a sorcerer spell if(nCastingClass == CLASS_TYPE_SORCERER) { - if (CheckSecondaryPrC(oCaster) == TRUE) + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> nCastingClass is Sorcerer.", oCaster); + //no need to check further if new spellbooks are disabled + if(GetPRCSwitch(PRC_SORC_DISALLOW_NEWSPELLBOOK)) { - if (DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> Sorcerer w/RHD found.", oCaster); + if (DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> PRC_SORC_DISALLOW_NEWSPELLBOOK.", oCaster); return TRUE; } + //check they have sorcerer levels + if(!GetLevelByClass(CLASS_TYPE_SORCERER, oCaster)) + { + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> Not a sorcerer.", oCaster); + return TRUE; + } + //check if they are casting via new spellbook + if(GetLocalInt(oCaster, "NSB_Class") != CLASS_TYPE_SORCERER && GetLevelByClass(CLASS_TYPE_ULTIMATE_MAGUS, oCaster)) + { + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> UltMagus using new spellbook.", oCaster); + return FALSE; + } + //check if they are casting via new spellbook + if(GetLocalInt(oCaster, "NSB_Class") == CLASS_TYPE_SORCERER) + { + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> Using new spellbook.", oCaster); + return TRUE; + } + if(GetLevelByClass(CLASS_TYPE_SUBLIME_CHORD, oCaster) > 0 && CheckSecondaryPrC(oCaster) == TRUE) + { + if (DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> Sublime Chord w/RHD found.", oCaster); + FloatingTextStringOnCreature("You must use the new spellbook on the class radial.", oCaster, FALSE); + return FALSE; + } + if (CheckSecondaryPrC(oCaster) == TRUE) + { + if (DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> Sorcerer w/RHD found.", oCaster); + FloatingTextStringOnCreature("You must use the new spellbook on the class radial.", oCaster, FALSE); + return FALSE; + } + //check they have arcane PrC or Draconic Arcane Grace/Breath + if(!(GetArcanePRCLevels(oCaster, nCastingClass) - GetLevelByClass(CLASS_TYPE_SUBLIME_CHORD, oCaster)) + && !(GetHasFeat(FEAT_DRACONIC_GRACE, oCaster) || GetHasFeat(FEAT_DRACONIC_BREATH, oCaster))) + { + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> First Sublime Chord check.", oCaster); + return TRUE; + } + + //check they have sorcerer in first arcane slot + //if(GetPrimaryArcaneClass() != CLASS_TYPE_SORCERER) + if(GetPrCAdjustedCasterLevelByType(TYPE_ARCANE, oCaster, TRUE) != GetPrCAdjustedCasterLevelByType(CLASS_TYPE_SORCERER, oCaster, TRUE)) + { + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> GetPrCAdjustedCasterLevelByType.", oCaster); + return TRUE; + } + //at this point, they must be using the bioware spellbook + //from a class that adds to bard + FloatingTextStringOnCreature("You must use the new spellbook on the class radial.", oCaster, FALSE); + return FALSE; + } + + +/* //check its a sorc spell + if(nCastingClass == CLASS_TYPE_SORCERER) + { //no need to check further if new spellbooks are disabled if(GetPRCSwitch(PRC_SORC_DISALLOW_NEWSPELLBOOK)) return TRUE; @@ -1708,7 +1867,7 @@ int BardSorcPrCCheck(object oCaster, int nCastingClass, object oSpellCastItem) //from a class that adds to sorc FloatingTextStringOnCreature("You must use the new spellbook on the class radial.", oCaster, FALSE); return FALSE; - } + } */ //check its a bard spell if(nCastingClass == CLASS_TYPE_BARD) @@ -3188,6 +3347,28 @@ int X2PreSpellCastCode2() X2BreakConcentrationSpells(); //--------------------------------------------------------------------------- + // Herbal Infusion Use check + //--------------------------------------------------------------------------- + if(nContinue && (GetBaseItemType(oSpellCastItem) == BASE_ITEM_INFUSED_HERB)) + { + int bIsSubradial = GetIsSubradialSpell(nSpellID); + + if(bIsSubradial) + { + nSpellID = GetMasterSpellFromSubradial(nSpellID); + } + int nItemCL = GetCastSpellCasterLevelFromItem(oSpellCastItem, nSpellID); + if(DEBUG) DoDebug("x2_inc_spellhook >> X2PreSpellCastCode2: Item Spellcaster Level: "+IntToString(nItemCL)+"."); + + if(DEBUG) DoDebug("x2_inc_spellhook >> X2PreSpellCastCode2: Herbal Infusion Found"); + if(!DoInfusionUseChecks(oCaster, oSpellCastItem, nSpellID)) + { + ApplyInfusionPoison(oCaster, nItemCL); + nContinue = FALSE; + } + } + + //--------------------------------------------------------------------------- // No casting while using expertise //--------------------------------------------------------------------------- if(nContinue) @@ -3338,6 +3519,12 @@ int X2PreSpellCastCode2() if (nContinue) nContinue = SpellAlignmentRestrictions(oCaster, nSpellID, nCastingClass); + //--------------------------------------------------------------------------- + // Verdant Lord Spontaneous Regernate + //--------------------------------------------------------------------------- + if(nContinue) + Spontaneity(oCaster, nCastingClass, nSpellID, nSpellLevel); + //--------------------------------------------------------------------------- // Druid spontaneous summoning //--------------------------------------------------------------------------- diff --git a/src/module/itp/creaturepalcus.itp.json b/src/module/itp/creaturepalcus.itp.json index 8091aa2..4874f4d 100644 --- a/src/module/itp/creaturepalcus.itp.json +++ b/src/module/itp/creaturepalcus.itp.json @@ -35503,7 +35503,7 @@ "__struct_id": 0, "CR": { "type": "float", - "value": 0.5 + "value": 0.3333 }, "FACTION": { "type": "cexostring", diff --git a/src/module/jrl/module.jrl.json b/src/module/jrl/module.jrl.json index 8b03760..d5f358b 100644 --- a/src/module/jrl/module.jrl.json +++ b/src/module/jrl/module.jrl.json @@ -9,6 +9,159 @@ "type": "cexostring", "value": "" }, + "EntryList": { + "type": "list", + "value": [ + { + "__struct_id": 0, + "End": { + "type": "word", + "value": 0 + }, + "ID": { + "type": "dword", + "value": 1 + }, + "Text": { + "type": "cexolocstring", + "value": { + "0": "3e D&D Experience Chart\n\nLvl XP Needed\n01 0\n02 1000\n03 3000\n04 6000\n05 10000\n06 15000\n07 21000\n08 28000\n09 36000\n10 45000\n11 55000\n12 66000\n13 78000\n14 91000\n15 105000\n16 120000\n17 136000\n18 153000\n19 171000\n20 190000\n21 210000\n22 231000\n23 253000\n24 276000\n25 300000\n26 325000\n27 351000\n28 378000\n29 406000\n30 435000\n31 465000\n32 496000\n33 528000\n34 561000\n35 595000\n36 630000\n37 666000\n38 703000\n39 741000\n40 780000" + } + } + } + ] + }, + "Name": { + "type": "cexolocstring", + "value": { + "0": "| D&D 3e XP Chart |" + } + }, + "Picture": { + "type": "word", + "value": 65535 + }, + "Priority": { + "type": "dword", + "value": 4 + }, + "Tag": { + "type": "cexostring", + "value": "JRNL_XPCHART" + }, + "XP": { + "type": "dword", + "value": 0 + } + }, + { + "__struct_id": 1, + "Comment": { + "type": "cexostring", + "value": "" + }, + "EntryList": { + "type": "list", + "value": [ + { + "__struct_id": 0, + "End": { + "type": "word", + "value": 0 + }, + "ID": { + "type": "dword", + "value": 1 + }, + "Text": { + "type": "cexolocstring", + "value": { + "0": "Level Adjustment Buy-off Table\n\nStarting LA / Level\n \n01 / 03\n02 / 06 09\n03 / 09 15 18\n04 / 12 21 27 30\n05 / 15 27 36\n06 / 18 33\n07 / 21 39\n08 / 24\n09 / 27\n10 / 30\n11 / 33\n12 / 36\n13 / 39\n\n(Buy-off dialog will be in the PRC menu when you qualify.)" + } + } + } + ] + }, + "Name": { + "type": "cexolocstring", + "value": { + "0": "| LA Buy-off Table |" + } + }, + "Picture": { + "type": "word", + "value": 65535 + }, + "Priority": { + "type": "dword", + "value": 4 + }, + "Tag": { + "type": "cexostring", + "value": "JRNL_LA_BUYOFF" + }, + "XP": { + "type": "dword", + "value": 0 + } + }, + { + "__struct_id": 2, + "Comment": { + "type": "cexostring", + "value": "" + }, + "EntryList": { + "type": "list", + "value": [ + { + "__struct_id": 0, + "End": { + "type": "word", + "value": 0 + }, + "ID": { + "type": "dword", + "value": 1 + }, + "Text": { + "type": "cexolocstring", + "value": { + "0": "Visit the PRC8 Discord for ruleset information, as well as other PW servers & dozens of single player PRC8 modules.\n\nhttps://discord.gg/FW9V9RKy5U" + } + } + } + ] + }, + "Name": { + "type": "cexolocstring", + "value": { + "0": "| PRC8 Discord |" + } + }, + "Picture": { + "type": "word", + "value": 65535 + }, + "Priority": { + "type": "dword", + "value": 4 + }, + "Tag": { + "type": "cexostring", + "value": "JRNL_PRC8" + }, + "XP": { + "type": "dword", + "value": 0 + } + }, + { + "__struct_id": 3, + "Comment": { + "type": "cexostring", + "value": "" + }, "EntryList": { "type": "list", "value": [ @@ -72,7 +225,7 @@ } }, { - "__struct_id": 1, + "__struct_id": 4, "Comment": { "type": "cexostring", "value": "Delivery Quest" @@ -225,7 +378,7 @@ } }, { - "__struct_id": 2, + "__struct_id": 5, "Comment": { "type": "cexostring", "value": "" @@ -684,7 +837,7 @@ } }, { - "__struct_id": 3, + "__struct_id": 6, "Comment": { "type": "cexostring", "value": "" @@ -752,7 +905,7 @@ } }, { - "__struct_id": 4, + "__struct_id": 7, "Comment": { "type": "cexostring", "value": "" @@ -820,7 +973,7 @@ } }, { - "__struct_id": 5, + "__struct_id": 8, "Comment": { "type": "cexostring", "value": "" @@ -888,7 +1041,7 @@ } }, { - "__struct_id": 6, + "__struct_id": 9, "Comment": { "type": "cexostring", "value": "" @@ -956,7 +1109,7 @@ } }, { - "__struct_id": 7, + "__struct_id": 10, "Comment": { "type": "cexostring", "value": "" @@ -1024,7 +1177,7 @@ } }, { - "__struct_id": 8, + "__struct_id": 11, "Comment": { "type": "cexostring", "value": "" @@ -1075,7 +1228,7 @@ } }, { - "__struct_id": 9, + "__struct_id": 12, "Comment": { "type": "cexostring", "value": "" @@ -1160,7 +1313,7 @@ } }, { - "__struct_id": 10, + "__struct_id": 13, "Comment": { "type": "cexostring", "value": "" diff --git a/src/module/nss/mod_enter.nss b/src/module/nss/mod_enter.nss index 96d23ce..b08f0f2 100644 --- a/src/module/nss/mod_enter.nss +++ b/src/module/nss/mod_enter.nss @@ -19,12 +19,12 @@ void AddToDynamicMerchant(); void main() { -int iLevel; -int iRnd; -int iRnd2; -int iIndex; -object oPC; -object oAnimal; + int iLevel; + int iRnd; + int iRnd2; + int iIndex; + object oPC = GetEnteringObject(); + object oAnimal; int iAnimal = GetLevelByClass(CLASS_TYPE_DRUID, oPC) + GetLevelByClass(CLASS_TYPE_FACTOTUM, oPC) @@ -32,21 +32,25 @@ object oAnimal; + GetLevelByClass(CLASS_TYPE_RANGER, oPC) + GetLevelByClass(CLASS_TYPE_SOULBORN, oPC) + GetLevelByClass(CLASS_TYPE_TOTEMIST, oPC); + + AddJournalQuestEntry("JRNL_XPCHART", 1, oPC, FALSE, FALSE, FALSE); + AddJournalQuestEntry("JRNL_LA_BUYOFF", 1, oPC, FALSE, FALSE, FALSE); + AddJournalQuestEntry("JRNL_PRC8", 1, oPC, FALSE, FALSE, FALSE); + + SetLocalInt(GetModule(),MODULE_SWITCH_ENABLE_BEBILITH_RUIN_ARMOR,TRUE); + SetMaxHenchmen(4); + + //Randomize based on Timer + iRnd=GetTimeSecond(); + iIndex=1; + while (iIndex<=iRnd) + { + iIndex++; + iRnd2=Random(100); + } -SetLocalInt(GetModule(),MODULE_SWITCH_ENABLE_BEBILITH_RUIN_ARMOR,TRUE); -SetMaxHenchmen(4); - -//Randomize based on Timer -iRnd=GetTimeSecond(); -iIndex=1; -while (iIndex<=iRnd) - { - iIndex++; - iRnd2=Random(100); - } -oPC = GetEnteringObject(); /* //This code will start a character with old values and campaign ranking on