From 687f527d45709f37d608a3fc88028d941a16ba63 Mon Sep 17 00:00:00 2001 From: Jaysyn904 <68194417+Jaysyn904@users.noreply.github.com> Date: Wed, 24 Dec 2025 16:47:31 -0500 Subject: [PATCH] 2025/12/24 Update Hooked up new GUI module event. Updated PRC8 includes Updated nasher.cfg. --- nasher.cfg | 14 +- src/include/inc_dynconv.nss | 60 +- src/include/inc_epicspelldef.nss | 143 +-- src/include/inc_epicspellfnc.nss | 4 +- src/include/inc_epicspells.nss | 11 +- src/include/inc_infusion.nss | 481 +++++++ src/include/inc_item_props.nss | 55 +- src/include/inc_lookups.nss | 42 +- src/include/inc_newspellbook.nss | 165 ++- src/include/inc_npc.nss | 178 ++- src/include/inc_rand_equip.nss | 1 + src/include/inc_rend.nss | 1 + src/include/inc_sp_gain_mem.nss | 11 +- src/include/inc_switch_setup.nss | 106 +- src/include/inv_inc_blast.nss | 1 + src/include/inv_inc_invknown.nss | 3 + src/include/inv_inc_invoke.nss | 2 +- src/include/inv_invokehook.nss | 10 + src/include/moi_inc_moifunc.nss | 7 +- src/include/nw_inc_gff.nss | 623 ++++++++++ src/include/nw_inc_nui.nss | 852 ++++++------- src/include/pnp_shft_poly.nss | 585 ++++++++- src/include/prc_add_spell_dc.nss | 2 + src/include/prc_add_spl_pen.nss | 4 +- src/include/prc_class_const.nss | 5 +- src/include/prc_craft_inc.nss | 23 +- src/include/prc_effect_inc.nss | 82 +- src/include/prc_feat_const.nss | 102 +- src/include/prc_getbest_inc.nss | 3 +- src/include/prc_inc_breath.nss | 2 + src/include/prc_inc_castlvl.nss | 118 +- src/include/prc_inc_chat_pow.nss | 1 + src/include/prc_inc_clsfunc.nss | 5 +- src/include/prc_inc_combat.nss | 27 +- src/include/prc_inc_combmove.nss | 48 +- src/include/prc_inc_core.nss | 9 +- src/include/prc_inc_descrptr.nss | 4 +- src/include/prc_inc_factotum.nss | 22 +- src/include/prc_inc_fork.nss | 95 +- src/include/prc_inc_function.nss | 12 +- src/include/prc_inc_hextor.nss | 2 + src/include/prc_inc_itmrstr.nss | 2 + src/include/prc_inc_json.nss | 1993 ++++++++++++++++++++++++++++++ src/include/prc_inc_material.nss | 21 +- src/include/prc_inc_nat_hb.nss | 264 +++- src/include/prc_inc_natweap.nss | 26 + src/include/prc_inc_nwscript.nss | 5 +- src/include/prc_inc_onhit.nss | 21 +- src/include/prc_inc_shifting.nss | 4 +- src/include/prc_inc_size.nss | 154 +++ src/include/prc_inc_skills.nss | 19 +- src/include/prc_inc_spells.nss | 118 +- src/include/prc_inc_switch.nss | 158 ++- src/include/prc_inc_turning.nss | 34 +- src/include/prc_inc_unarmed.nss | 402 ++++-- src/include/prc_inc_wpnrest.nss | 296 +++-- src/include/prc_ipfeat_const.nss | 13 +- src/include/prc_misc_const.nss | 4 + src/include/prc_nui_com_inc.nss | 85 ++ src/include/prc_nui_consts.nss | 13 + src/include/prc_nui_lv_inc.nss | 128 +- src/include/prc_nui_sb_inc.nss | 9 +- src/include/prc_nui_sbd_inc.nss | 10 +- src/include/prc_sp_func.nss | 18 + src/include/prc_spell_const.nss | 120 +- src/include/prc_spellf_inc.nss | 6 +- src/include/prc_template_con.nss | 1 + src/include/prc_weap_apt.nss | 4 +- src/include/prc_x2_craft.nss | 744 ++++++++++- src/include/prc_x2_itemprop.nss | 161 ++- src/include/prcsp_archmaginc.nss | 1 + src/include/psi_inc_ac_spawn.nss | 1 + src/include/psi_inc_core.nss | 398 ++++-- src/include/psi_inc_metapsi.nss | 1 + src/include/psi_inc_powknown.nss | 6 +- src/include/psi_inc_ppoints.nss | 11 +- src/include/psi_inc_psicraft.nss | 3 +- src/include/psi_inc_psifunc.nss | 30 +- src/include/psi_inc_pwresist.nss | 1 + src/include/psi_spellhook.nss | 10 + src/include/shd_inc_mystknwn.nss | 5 +- src/include/shd_inc_shdfunc.nss | 7 +- src/include/shd_mysthook.nss | 15 +- src/include/tob_movehook.nss | 10 +- src/include/true_inc_truespk.nss | 3 + src/include/true_inc_trufunc.nss | 1 + src/include/true_inc_truknwn.nss | 1 + src/include/true_utterhook.nss | 11 + src/include/x2_inc_spellhook.nss | 132 +- src/include/x3_inc_horse.nss | 5 +- src/module/ifo/module.ifo.json | 2 +- 91 files changed, 8196 insertions(+), 1212 deletions(-) create mode 100644 src/include/inc_infusion.nss create mode 100644 src/include/nw_inc_gff.nss create mode 100644 src/include/prc_inc_json.nss create mode 100644 src/include/prc_inc_size.nss diff --git a/nasher.cfg b/nasher.cfg index ade7f97..56ab5cc 100644 --- a/nasher.cfg +++ b/nasher.cfg @@ -231,8 +231,16 @@ description = "PRC8 version of S1 - Tomb of Horrors." filter = "prc_nui_sc_inc.nss" filter = "prc_nui_scd_inc.nss" filter = "prc_nui_consts.nss" - filter = "nw_inc_nui" + filter = "nw_inc_nui.nss" filter = "xchst_inc.nss" - - [target.rules] + filter = "prc_nui_sbd_inc.nss" + filter = "prc_nui_sb_inc.nss" + filter = "prc_nui_lv_inc.nss" + filter = "prc_nui_com_inc.nss" + filter = "prc_inc_size.nss" + filter = "prc_inc_json.nss" + filter = "prc_inc_gff.nss" + filter = "inc_infusion.nss" + +[target.rules] "*" = "src/module/$ext" \ No newline at end of file diff --git a/src/include/inc_dynconv.nss b/src/include/inc_dynconv.nss index 4603fc4..68aabac 100644 --- a/src/include/inc_dynconv.nss +++ b/src/include/inc_dynconv.nss @@ -12,6 +12,7 @@ //::////////////////////////////////////////////// //::////////////////////////////////////////////// +//PRC8 Token pre-fix = 161838 ////////////////////////////////////////////////// /* Constant definitions */ @@ -21,23 +22,23 @@ const int DYNCONV_EXITED = -2; const int DYNCONV_ABORTED = -3; const int DYNCONV_SETUP_STAGE = -1; -const int DYNCONV_TOKEN_HEADER = 99; -const int DYNCONV_TOKEN_REPLY_0 = 100; -const int DYNCONV_TOKEN_REPLY_1 = 101; -const int DYNCONV_TOKEN_REPLY_2 = 102; -const int DYNCONV_TOKEN_REPLY_3 = 103; -const int DYNCONV_TOKEN_REPLY_4 = 104; -const int DYNCONV_TOKEN_REPLY_5 = 105; -const int DYNCONV_TOKEN_REPLY_6 = 106; -const int DYNCONV_TOKEN_REPLY_7 = 107; -const int DYNCONV_TOKEN_REPLY_8 = 108; -const int DYNCONV_TOKEN_REPLY_9 = 109; -const int DYNCONV_TOKEN_EXIT = 110; -const int DYNCONV_TOKEN_WAIT = 111; -const int DYNCONV_TOKEN_NEXT = 112; -const int DYNCONV_TOKEN_PREV = 113; -const int DYNCONV_MIN_TOKEN = 99; -const int DYNCONV_MAX_TOKEN = 113; +const int DYNCONV_TOKEN_HEADER = 16183899; +const int DYNCONV_TOKEN_REPLY_0 = 161838100; +const int DYNCONV_TOKEN_REPLY_1 = 161838101; +const int DYNCONV_TOKEN_REPLY_2 = 161838102; +const int DYNCONV_TOKEN_REPLY_3 = 161838103; +const int DYNCONV_TOKEN_REPLY_4 = 161838104; +const int DYNCONV_TOKEN_REPLY_5 = 161838105; +const int DYNCONV_TOKEN_REPLY_6 = 161838106; +const int DYNCONV_TOKEN_REPLY_7 = 161838107; +const int DYNCONV_TOKEN_REPLY_8 = 161838108; +const int DYNCONV_TOKEN_REPLY_9 = 161838109; +const int DYNCONV_TOKEN_EXIT = 161838110; +const int DYNCONV_TOKEN_WAIT = 161838111; +const int DYNCONV_TOKEN_NEXT = 161838112; +const int DYNCONV_TOKEN_PREV = 161838113; +const int DYNCONV_MIN_TOKEN = 16183899; +const int DYNCONV_MAX_TOKEN = 161838113; const int DYNCONV_STRREF_PLEASE_WAIT = 16824202; // "Please wait" const int DYNCONV_STRREF_PREVIOUS = 16824203; // "Previous" @@ -477,9 +478,28 @@ void _DynConvInternal_ExitedConvo(object oPC, int bAbort) DeleteLocalInt(oPC, DYNCONV_STAGE); DeleteLocalString(oPC, DYNCONV_SCRIPT); DeleteLocalString(oPC, "DynConv_HeaderText"); - int i; - for(i = DYNCONV_MIN_TOKEN; i <= DYNCONV_MAX_TOKEN; i++) - DeleteLocalString(oPC, GetTokenIDString(i)); + + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_HEADER)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_REPLY_0)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_REPLY_1)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_REPLY_2)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_REPLY_3)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_REPLY_4)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_REPLY_5)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_REPLY_6)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_REPLY_7)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_REPLY_8)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_REPLY_9)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_EXIT)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_WAIT)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_NEXT)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_TOKEN_PREV)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_MIN_TOKEN)); + DeleteLocalString(oPC, GetTokenIDString(DYNCONV_MAX_TOKEN)); + + //int i; + //for(i = DYNCONV_MIN_TOKEN; i <= DYNCONV_MAX_TOKEN; i++) + //DeleteLocalString(oPC, GetTokenIDString(i)); } } } 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..4537d45 100644 --- a/src/include/inc_epicspellfnc.nss +++ b/src/include/inc_epicspellfnc.nss @@ -26,7 +26,7 @@ int GetSpellFromAbrev(string sAbrev); ////////////////////////////////////////////////// #include "inc_utility" -//#include "inc_epicspelldef" +#include "inc_epicspells" // SEED FUNCTIONS @@ -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_epicspells.nss b/src/include/inc_epicspells.nss index ec3eac9..4682672 100644 --- a/src/include/inc_epicspells.nss +++ b/src/include/inc_epicspells.nss @@ -303,11 +303,18 @@ int GetIsEpicShaman(object oPC) && GetAbilityScore(oPC, ABILITY_WISDOM) > 18; } -int GetIsEpicSorcerer(object oPC) +int GetIsEpicSorcerer(object oPC) +{ + return GetHitDice(oPC) >= 21 + && GetCasterLvl(CLASS_TYPE_SORCERER, oPC) > 17 + && GetAbilityScore(oPC, ABILITY_CHARISMA) > 18; +} + +/* int GetIsEpicSorcerer(object oPC) { return GetPrCAdjustedCasterLevel(CLASS_TYPE_SORCERER, oPC, FALSE) > 17 && GetAbilityScore(oPC, ABILITY_CHARISMA) > 18; -} +} */ int GetIsEpicSublimeChord(object oPC) { 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_item_props.nss b/src/include/inc_item_props.nss index b1edfc2..b912fad 100644 --- a/src/include/inc_item_props.nss +++ b/src/include/inc_item_props.nss @@ -1643,7 +1643,60 @@ int GetIsMagicItem(object oItem) int FeatToIprop(int nFeat) { switch(nFeat) - {//: Weapon Focus + { + //:: Weapon Proficiencies + case FEAT_WEAPON_PROFICIENCY_SHORTSWORD: return IP_CONST_FEAT_WEAPON_PROFICIENCY_SHORTSWORD; + case FEAT_WEAPON_PROFICIENCY_LONGSWORD: return IP_CONST_FEAT_WEAPON_PROFICIENCY_LONGSWORD; + case FEAT_WEAPON_PROFICIENCY_BATTLEAXE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_BATTLEAXE; + case FEAT_WEAPON_PROFICIENCY_BASTARD_SWORD: return IP_CONST_FEAT_WEAPON_PROFICIENCY_BASTARD_SWORD; + case FEAT_WEAPON_PROFICIENCY_LIGHT_FLAIL: return IP_CONST_FEAT_WEAPON_PROFICIENCY_LIGHT_FLAIL; + case FEAT_WEAPON_PROFICIENCY_WARHAMMER: return IP_CONST_FEAT_WEAPON_PROFICIENCY_WARHAMMER; + case FEAT_WEAPON_PROFICIENCY_LONGBOW: return IP_CONST_FEAT_WEAPON_PROFICIENCY_LONGBOW; + case FEAT_WEAPON_PROFICIENCY_LIGHT_MACE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_LIGHT_MACE; + case FEAT_WEAPON_PROFICIENCY_HALBERD: return IP_CONST_FEAT_WEAPON_PROFICIENCY_HALBERD; + case FEAT_WEAPON_PROFICIENCY_SHORTBOW: return IP_CONST_FEAT_WEAPON_PROFICIENCY_SHORTBOW; + case FEAT_WEAPON_PROFICIENCY_TWO_BLADED_SWORD: return IP_CONST_FEAT_WEAPON_PROFICIENCY_TWO_BLADED_SWORD; + case FEAT_WEAPON_PROFICIENCY_GREATSWORD: return IP_CONST_FEAT_WEAPON_PROFICIENCY_GREATSWORD; + case FEAT_WEAPON_PROFICIENCY_GREATAXE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_GREATAXE; + case FEAT_WEAPON_PROFICIENCY_DART: return IP_CONST_FEAT_WEAPON_PROFICIENCY_DART; + case FEAT_WEAPON_PROFICIENCY_DIRE_MACE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_DIRE_MACE; + case FEAT_WEAPON_PROFICIENCY_DOUBLE_AXE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_DOUBLE_AXE; + case FEAT_WEAPON_PROFICIENCY_HEAVY_FLAIL: return IP_CONST_FEAT_WEAPON_PROFICIENCY_HEAVY_FLAIL; + case FEAT_WEAPON_PROFICIENCY_LIGHT_HAMMER: return IP_CONST_FEAT_WEAPON_PROFICIENCY_LIGHT_HAMMER; + case FEAT_WEAPON_PROFICIENCY_HANDAXE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_HANDAXE; + case FEAT_WEAPON_PROFICIENCY_KAMA: return IP_CONST_FEAT_WEAPON_PROFICIENCY_KAMA; + case FEAT_WEAPON_PROFICIENCY_KATANA: return IP_CONST_FEAT_WEAPON_PROFICIENCY_KATANA; + case FEAT_WEAPON_PROFICIENCY_KUKRI: return IP_CONST_FEAT_WEAPON_PROFICIENCY_KUKRI; + case FEAT_WEAPON_PROFICIENCY_MORNINGSTAR: return IP_CONST_FEAT_WEAPON_PROFICIENCY_MORNINGSTAR; + case FEAT_WEAPON_PROFICIENCY_RAPIER: return IP_CONST_FEAT_WEAPON_PROFICIENCY_RAPIER; + case FEAT_WEAPON_PROFICIENCY_SCIMITAR: return IP_CONST_FEAT_WEAPON_PROFICIENCY_SCIMITAR; + case FEAT_WEAPON_PROFICIENCY_SCYTHE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_SCYTHE; + case FEAT_WEAPON_PROFICIENCY_SHORTSPEAR: return IP_CONST_FEAT_WEAPON_PROFICIENCY_SHORTSPEAR; + case FEAT_WEAPON_PROFICIENCY_SHURIKEN: return IP_CONST_FEAT_WEAPON_PROFICIENCY_SHURIKEN; + case FEAT_WEAPON_PROFICIENCY_SICKLE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_SICKLE; + case FEAT_WEAPON_PROFICIENCY_SLING: return IP_CONST_FEAT_WEAPON_PROFICIENCY_SLING; + case FEAT_WEAPON_PROFICIENCY_THROWING_AXE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_THROWING_AXE; + case FEAT_WEAPON_PROFICIENCY_TRIDENT: return IP_CONST_FEAT_WEAPON_PROFICIENCY_TRIDENT; + case FEAT_WEAPON_PROFICIENCY_DWARVEN_WARAXE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_DWARVEN_WARAXE; + case FEAT_WEAPON_PROFICIENCY_WHIP: return IP_CONST_FEAT_WEAPON_PROFICIENCY_WHIP; + case FEAT_WEAPON_PROFICIENCY_ELVEN_LIGHTBLADE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_ELVEN_LIGHTBLADE; + case FEAT_WEAPON_PROFICIENCY_ELVEN_THINBLADE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_ELVEN_THINBLADE; + case FEAT_WEAPON_PROFICIENCY_ELVEN_COURTBLADE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_ELVEN_COURTBLADE; + case FEAT_WEAPON_PROFICIENCY_LIGHT_LANCE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_LIGHT_LANCE; + case FEAT_WEAPON_PROFICIENCY_HEAVY_PICK: return IP_CONST_FEAT_WEAPON_PROFICIENCY_HEAVY_PICK; + case FEAT_WEAPON_PROFICIENCY_LIGHT_PICK: return IP_CONST_FEAT_WEAPON_PROFICIENCY_LIGHT_PICK; + case FEAT_WEAPON_PROFICIENCY_SAI: return IP_CONST_FEAT_WEAPON_PROFICIENCY_SAI; + case FEAT_WEAPON_PROFICIENCY_NUNCHAKU: return IP_CONST_FEAT_WEAPON_PROFICIENCY_NUNCHAKU; + case FEAT_WEAPON_PROFICIENCY_FALCHION: return IP_CONST_FEAT_WEAPON_PROFICIENCY_FALCHION; + case FEAT_WEAPON_PROFICIENCY_SAP: return IP_CONST_FEAT_WEAPON_PROFICIENCY_SAP; + case FEAT_WEAPON_PROFICIENCY_KATAR: return IP_CONST_FEAT_WEAPON_PROFICIENCY_KATAR; + case FEAT_WEAPON_PROFICIENCY_HEAVY_MACE: return IP_CONST_FEAT_WEAPON_PROFICIENCY_HEAVY_MACE; + case FEAT_WEAPON_PROFICIENCY_MAUL: return IP_CONST_FEAT_WEAPON_PROFICIENCY_MAUL; + case FEAT_WEAPON_PROFICIENCY_DOUBLE_SCIMITAR: return IP_CONST_FEAT_WEAPON_PROFICIENCY_DOUBLE_SCIMITAR; + case FEAT_WEAPON_PROFICIENCY_GOAD: return IP_CONST_FEAT_WEAPON_PROFICIENCY_GOAD; + case FEAT_WEAPON_PROFICIENCY_EAGLE_CLAW: return IP_CONST_FEAT_WEAPON_PROFICIENCY_EAGLE_CLAW; + + //: Weapon Focus case FEAT_WEAPON_FOCUS_BASTARD_SWORD: return IP_CONST_FEAT_WEAPON_FOCUS_BASTARD_SWORD; case FEAT_WEAPON_FOCUS_BATTLE_AXE: return IP_CONST_FEAT_WEAPON_FOCUS_BATTLE_AXE; case FEAT_WEAPON_FOCUS_CLUB: return IP_CONST_FEAT_WEAPON_FOCUS_CLUB; 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..5a06b4d 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 @@ -20,6 +20,8 @@ Add class to GetCasterLvl() in prc_inc_spells Add Practiced Spellcaster feat to feat.2da and to PracticedSpellcasting() in prc_inc_castlvl Run the assemble_spellbooks.bat file Make the prc_* scripts in newspellbook. The filenames can be found under the spell entries for the class in spells.2da. +Update the fileends for all relevant files in inc_switch_setup +Delete prc_data in the \database\ folder before testing new spells. Spont: Make cls_spgn_*.2da @@ -41,6 +43,8 @@ Add class to prc_amagsys_gain if(CheckMissingSpells(oPC, CLASS_TYPE_SORCERER, Mi Add class to ExecuteScript("prc_amagsys_gain", oPC) list in EvalPRCFeats in prc_inc_function Run the assemble_spellbooks.bat file Make the prc_* scripts in newspellbook +Update the fileends for all relevant files in inc_switch_setup +Delete prc_data in the \database\ folder before testing new spells. prc_classes.2da entry: Label - name for the class @@ -104,11 +108,10 @@ void ProcessPreparedSpellLevel(object oPC, int nClass, int nSpellLevel, int nLev //#include "prc_effect_inc" //access via prc_inc_core //#include "inc_lookups" //access via prc_inc_core #include "prc_inc_core" -#include "inc_sp_gain_mem" //providing child access to prc_inc_core - //Must load in this order. +#include "inc_sp_gain_mem" //#include "prc_inc_castlvl" //access via prc_inc_core //#include "prc_inc_descrptr" //access via prc_inc_core - +#include "inc_item_props" ////////////////////////////////////////////////// /* Function definitions */ @@ -119,6 +122,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 +145,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 +562,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 +583,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 +663,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 +688,7 @@ int GetSpellKnownMaxCount(int nLevel, int nSpellLevel, int nClass, object oPC) } return nKnown; } + */ int GetSpellKnownCurrentCount(object oPC, int nSpellLevel, int nClass) { @@ -693,6 +756,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 +810,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 +951,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 +1279,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 +1431,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 +1474,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 +1564,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 +1590,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" @@ -1517,6 +1621,3 @@ void DoCleanUp(int nMetamagic) DeleteLocalInt(OBJECT_SELF, "NSB_SpellLevel"); DeleteLocalInt(OBJECT_SELF, "NSB_SpellbookID"); } - -//:: Test Void -//:: void main (){} \ No newline at end of file diff --git a/src/include/inc_npc.nss b/src/include/inc_npc.nss index 6b99a4a..8385ddc 100644 --- a/src/include/inc_npc.nss +++ b/src/include/inc_npc.nss @@ -7,8 +7,11 @@ //::////////////////////////////////////////////// //::////////////////////////////////////////////// +//:: Levels up an NPC according to variables set on NPC. +void LevelUpSummon(object oSummon, int iTargetLvl); + // Get the master of oAssociate. -object GetMasterNPC(object oAssociate=OBJECT_SELF); +object GetMasterNPC(object oAssociate0 = OBJECT_SELF); // Returns the associate type of the specified creature. // - Returns ASSOCIATE_TYPE_NONE if the creature is not the associate of anyone. @@ -75,7 +78,6 @@ void DestroySummon(object oSummon) DestroyObject(oSummon); } - object CreateLocalNPC(object oMaster,int nAssociateType,string sTemplate,location loc,int Nth=1,string sTag="") { object oSummon=CreateObject(OBJECT_TYPE_CREATURE,sTemplate,loc,FALSE,sTag); @@ -111,7 +113,7 @@ object CreateLocalNextNPC(object oMaster,int nAssociateType,string sTemplate,loc SetLocalObject(oMaster, IntToString(nAssociateType)+"oHench"+IntToString(nCount), oSummon); SetLocalInt(oSummon, "iAssocNth", nCount); - SetAssociateState(NW_ASC_HAVE_MASTER,TRUE,oSummon); + SetAssociateState(NW_ASC_HAVE_MASTER, TRUE, oSummon); SetAssociateState(NW_ASC_DISTANCE_2_METERS); SetAssociateState(NW_ASC_DISTANCE_4_METERS, FALSE); SetAssociateState(NW_ASC_DISTANCE_6_METERS, FALSE); @@ -122,6 +124,7 @@ object CreateLocalNextNPC(object oMaster,int nAssociateType,string sTemplate,loc return oSummon; } + object GetMasterNPC(object oAssociate=OBJECT_SELF) { object oMaster = GetLocalObject(oAssociate, "oMaster"); @@ -220,4 +223,173 @@ int GetAssociateHealMasterNPC() return FALSE; } +/** + * @brief Levels up a summoned creature based on its master's total casting level, + * while respecting configured HD limits and multiclass transition rules. + * Should only be called on the NPC onSpawn event. + * + * This function: + * - Retrieves the master’s total casting level and clamps it to the creature’s + * minimum and maximum HD (iMinHD, iMaxHD). + * - Repeatedly calls LevelUpHenchman() until the creature reaches that level, + * switching classes when the creature's stored "ClassXStart" thresholds are met. + * + * Local variables recognized on the summoned creature: + * + * | Variable Name | Purpose | + * |-----------------|-------------------------------------------------------------| + * | iMinHD | Minimum HD allowed | + * | iMaxHD | Maximum HD allowed | + * | Class2Start | Level to begin second class progression | + * | Class2 | Class type for second progression | + * | Class2Package | Package for second progression | + * | Class3Start | Level to begin third class progression | + * | Class3 | Class type for third progression | + * | Class3Package | Package for third progression | + * | Class4Start | Level to begin fourth class progression | + * | Class4 | Class type for fourth progression | + * | Class4Package | Package for fourth progression | + * + * Behavior notes: + * - Leveling continues until the creature reaches the master’s effective + * casting level (bounded by iMinHD/iMaxHD). + * - If LevelUpHenchman() returns 0, the creature shouts a failure message. + * - CLASS_TYPE_INVALID causes the creature to level in its current class. + * + * @param oCreature The summoned creature being leveled. Defaults to OBJECT_SELF. + * + * @see LevelUpHenchman + * @see GetLocalInt + * @see GetHitDice + */ +void LevelUpSummon(object oSummon, int iTargetLvl) +{ + int nCurrentHD = GetHitDice(oSummon); + int iNewHD = nCurrentHD; + // Read multiclassing info from locals + int iClass2Start = GetLocalInt(oSummon, "Class2Start"); + int iClass2 = GetLocalInt(oSummon, "Class2"); + int iClass2Package = GetLocalInt(oSummon, "Class2Package"); + + int iClass3Start = GetLocalInt(oSummon, "Class3Start"); + int iClass3 = GetLocalInt(oSummon, "Class3"); + int iClass3Package = GetLocalInt(oSummon, "Class3Package"); + + int iClass4Start = GetLocalInt(oSummon, "Class4Start"); + int iClass4 = GetLocalInt(oSummon, "Class4"); + int iClass4Package = GetLocalInt(oSummon, "Class4Package"); + + int iClass; // current class to level + int iPackage; // package to use + + // Main leveling loop + while (nCurrentHD < iTargetLvl && nCurrentHD > 0) + { + // Determine which class and package to use + if (iClass4Start != 0 && nCurrentHD >= iClass4Start) + { + iClass = iClass4; + iPackage = iClass4Package; + } + else if (iClass3Start != 0 && nCurrentHD >= iClass3Start) + { + iClass = iClass3; + iPackage = iClass3Package; + } + else if (iClass2Start != 0 && nCurrentHD >= iClass2Start) + { + iClass = iClass2; + iPackage = iClass2Package; + } + else + { + // Base class (first class in the sheet) + iClass = CLASS_TYPE_INVALID; // keeps current + iPackage = PACKAGE_INVALID; + } + + // Level up one HD + iNewHD = LevelUpHenchman(oSummon, iClass, TRUE, iPackage); + + if (iNewHD == 0) + { + SpeakString(GetName(oSummon) + " failed to level properly!", TALKVOLUME_SHOUT); + break; + } + + nCurrentHD = iNewHD; + } + + // Force the creature to rest to memorize spells + // PRCForceRest(oSummon); + +} + + + + + +/* void LevelUpSummon(object oSummon, int iTargetLvl) +{ + //get the default hit dice of the summon + int nDefaultHD = GetHitDice(oSummon); + + if (DEBUG) DoDebug("inc_npc >> LevelUpSummon: nDefaultHD = " +IntToString(nDefaultHD)+"."); + + if (DEBUG) DoDebug("inc_npc >> LevelUpSummon: iTargetLvl = " +IntToString(iTargetLvl)+"."); + + //get the multiclassing variables to see if we need to change classes from its base class + int iClass2Start = GetLocalInt(oSummon, "Class2Start"); + int iClass2 = GetLocalInt(oSummon, "Class2"); + int iClass2Package = GetLocalInt(oSummon, "Class2Package"); + + int iClass3Start = GetLocalInt(oSummon, "Class3Start"); + int iClass3 = GetLocalInt(oSummon, "Class3"); + int iClass3Package = GetLocalInt(oSummon, "Class3Package"); + + int iClass4Start = GetLocalInt(oSummon, "Class4Start"); + int iClass4 = GetLocalInt(oSummon, "Class4"); + int iClass4Package = GetLocalInt(oSummon, "Class4Package"); + + //check for zero cause thats an error + //if creatures are not leveling then best bet is they are not legal creatures + while( (nDefaultHD < iTargetLvl) && (nDefaultHD > 0) ) + { + //check the multiclassing numbers to change classes + if( (iClass4Start != 0) && (nDefaultHD >= iClass4Start) ) + { + //level up using the new class and Packageage + nDefaultHD = LevelUpHenchman(oSummon, iClass4 ,TRUE, iClass4Package); + + if(nDefaultHD == 0) + SpeakString(GetName(oSummon) + " Failed on fourth class", TALKVOLUME_SHOUT); + } + else if( (iClass3Start != 0) && (nDefaultHD >= iClass3Start) ) + { + //level up using the new class and Packageage + nDefaultHD = LevelUpHenchman(oSummon, iClass3 ,TRUE, iClass3Package); + + if(nDefaultHD == 0) + SpeakString(GetName(oSummon) + " Failed on third class", TALKVOLUME_SHOUT); + } + else if( (iClass2Start != 0) && (nDefaultHD >= iClass2Start) ) + { + //level up using the new class and Packageage + nDefaultHD = LevelUpHenchman(oSummon, iClass2 ,TRUE, iClass2Package); + + if(nDefaultHD == 0) + SpeakString(GetName(oSummon) + " Failed on second class", TALKVOLUME_SHOUT); + } + else + { + //just level up using the class it already has + nDefaultHD = LevelUpHenchman(oSummon, CLASS_TYPE_INVALID ,TRUE); + + if(nDefaultHD == 0) + SpeakString(GetName(oSummon) + " Failed to level properly", TALKVOLUME_SHOUT); + } + } +} + */ +//:: void main() {} \ No newline at end of file diff --git a/src/include/inc_rand_equip.nss b/src/include/inc_rand_equip.nss index 814f533..07f7191 100644 --- a/src/include/inc_rand_equip.nss +++ b/src/include/inc_rand_equip.nss @@ -3433,6 +3433,7 @@ int PrimoGetWeaponSize(object oItem) case BASE_ITEM_LIGHTFLAIL: case BASE_ITEM_KATANA: case BASE_ITEM_MAGICSTAFF: + case BASE_ITEM_CRAFTED_SCEPTER: case BASE_ITEM_LONGSWORD: case BASE_ITEM_TRIDENT: case BASE_ITEM_MORNINGSTAR: diff --git a/src/include/inc_rend.nss b/src/include/inc_rend.nss index 06e2b2f..07ae6b3 100644 --- a/src/include/inc_rend.nss +++ b/src/include/inc_rend.nss @@ -30,6 +30,7 @@ int GetDamageFromConstant(int nIPConst); void DoFrostRend(object oTarget, object oAttacker, object oWeapon); #include "moi_inc_moifunc" +#include "prc_inc_combat" ////////////////////////////////////////////////// /* Function defintions */ diff --git a/src/include/inc_sp_gain_mem.nss b/src/include/inc_sp_gain_mem.nss index d994896..b3981be 100644 --- a/src/include/inc_sp_gain_mem.nss +++ b/src/include/inc_sp_gain_mem.nss @@ -17,9 +17,6 @@ Created: May 1, 2008 //:: Updated for .35 by Jaysyn 2023/03/11 -//:: Test Void -//void main (){} - //::////////////////////////////////////////////// //:: Constants //::////////////////////////////////////////////// @@ -63,6 +60,14 @@ string GetMetaMagicString(int nMetaMagic); int GetMetaMagicFromFeat(int nFeat); int GetMetaMagicOfCaster(object oPC = OBJECT_SELF); +string GetFileForClass(int nClass); +int GetSpellslotLevel(int nClass, object oPC); +int GetSpellKnownMaxCount(int nLevel, int nSpellLevel, int nClass, object oPC); +int GetSpellbookTypeForClass(int nClass); + +#include "inc_pers_array" +#include "inc_2dacache" + // name of the new spellbook file (cls_spell_*) string GetNSBDefinitionFileName(int nClass) { diff --git a/src/include/inc_switch_setup.nss b/src/include/inc_switch_setup.nss index 95e378b..86c526c 100644 --- a/src/include/inc_switch_setup.nss +++ b/src/include/inc_switch_setup.nss @@ -44,6 +44,7 @@ void CreateSwitchNameArray(); #include "prc_inc_array" // Needs direct include instead of inc_utility #include "prc_inc_switch" +#include "inc_2dacache" ////////////////////////////////////////////////// /* Function definitions */ @@ -221,9 +222,9 @@ void SetDefaultFileEnds() SetPRCSwitch("PRC_FILE_END_cls_spcr_bard", 144); SetPRCSwitch("PRC_FILE_END_cls_spcr_beguil", 142); SetPRCSwitch("PRC_FILE_END_cls_spcr_blkgrd", 47); - SetPRCSwitch("PRC_FILE_END_cls_spcr_dnecro", 137); + SetPRCSwitch("PRC_FILE_END_cls_spcr_dnecro", 138); SetPRCSwitch("PRC_FILE_END_cls_spcr_duskbl", 69); - SetPRCSwitch("PRC_FILE_END_cls_spcr_favsol", 290); + SetPRCSwitch("PRC_FILE_END_cls_spcr_favsol", 300); SetPRCSwitch("PRC_FILE_END_cls_spcr_harper", 35); SetPRCSwitch("PRC_FILE_END_cls_spcr_healer", 77); SetPRCSwitch("PRC_FILE_END_cls_spcr_hexbl", 73); @@ -251,9 +252,9 @@ void SetDefaultFileEnds() SetPRCSwitch("PRC_FILE_END_cls_spell_bard", 169); SetPRCSwitch("PRC_FILE_END_cls_spell_beguil", 119); SetPRCSwitch("PRC_FILE_END_cls_spell_blkgrd", 163); - SetPRCSwitch("PRC_FILE_END_cls_spell_dnecro", 134); + SetPRCSwitch("PRC_FILE_END_cls_spell_dnecro", 135); SetPRCSwitch("PRC_FILE_END_cls_spell_duskbl", 84); - SetPRCSwitch("PRC_FILE_END_cls_spell_favsol", 363); + SetPRCSwitch("PRC_FILE_END_cls_spell_favsol", 373); SetPRCSwitch("PRC_FILE_END_cls_spell_harper", 21); SetPRCSwitch("PRC_FILE_END_cls_spell_healer", 271); SetPRCSwitch("PRC_FILE_END_cls_spell_hexbl", 79); @@ -267,7 +268,7 @@ void SetDefaultFileEnds() SetPRCSwitch("PRC_FILE_END_cls_spell_sod", 110); SetPRCSwitch("PRC_FILE_END_cls_spell_sohei", 131); SetPRCSwitch("PRC_FILE_END_cls_spell_sol", 114); - SetPRCSwitch("PRC_FILE_END_cls_spell_sorc", 541); + SetPRCSwitch("PRC_FILE_END_cls_spell_sorc", 550); SetPRCSwitch("PRC_FILE_END_cls_spell_suel", 160); SetPRCSwitch("PRC_FILE_END_cls_spell_templ", 95); SetPRCSwitch("PRC_FILE_END_cls_spell_tfshad", 70); @@ -335,7 +336,7 @@ void SetDefaultFileEnds() SetPRCSwitch("PRC_FILE_END_craft_golem", 40); SetPRCSwitch("PRC_FILE_END_craft_ring", 41); SetPRCSwitch("PRC_FILE_END_craft_weapon", 46); - SetPRCSwitch("PRC_FILE_END_craft_wondrous", 115); + SetPRCSwitch("PRC_FILE_END_craft_wondrous", 131); SetPRCSwitch("PRC_FILE_END_creaturesize", 5); SetPRCSwitch("PRC_FILE_END_creaturespeed", 8); SetPRCSwitch("PRC_FILE_END_crtemplates", 10); @@ -355,7 +356,7 @@ void SetDefaultFileEnds() SetPRCSwitch("PRC_FILE_END_des_crft_poison", 100); SetPRCSwitch("PRC_FILE_END_des_crft_props", 27); SetPRCSwitch("PRC_FILE_END_des_crft_scroll", 3999); - SetPRCSwitch("PRC_FILE_END_des_crft_spells", 19348); + SetPRCSwitch("PRC_FILE_END_des_crft_spells", 20000); SetPRCSwitch("PRC_FILE_END_des_crft_weapon", 29); SetPRCSwitch("PRC_FILE_END_des_cutconvdur", 26); SetPRCSwitch("PRC_FILE_END_des_feat2item", 1000); @@ -408,7 +409,7 @@ void SetDefaultFileEnds() SetPRCSwitch("PRC_FILE_END_iprp_ammocost", 15); SetPRCSwitch("PRC_FILE_END_iprp_ammotype", 2); SetPRCSwitch("PRC_FILE_END_iprp_amount", 4); - SetPRCSwitch("PRC_FILE_END_iprp_aoe", 7); + SetPRCSwitch("PRC_FILE_END_iprp_aoe", 8); SetPRCSwitch("PRC_FILE_END_iprp_arcspell", 19); SetPRCSwitch("PRC_FILE_END_iprp_base1", -1); SetPRCSwitch("PRC_FILE_END_iprp_bladecost", 5); @@ -423,15 +424,15 @@ void SetDefaultFileEnds() SetPRCSwitch("PRC_FILE_END_iprp_damvulcost", 7); SetPRCSwitch("PRC_FILE_END_iprp_decvalue1", 9); SetPRCSwitch("PRC_FILE_END_iprp_decvalue2", 9); - SetPRCSwitch("PRC_FILE_END_iprp_feats", 24819); + SetPRCSwitch("PRC_FILE_END_iprp_feats", 26999); SetPRCSwitch("PRC_FILE_END_iprp_immuncost", 7); SetPRCSwitch("PRC_FILE_END_iprp_immunity", 9); SetPRCSwitch("PRC_FILE_END_iprp_incvalue1", 9); SetPRCSwitch("PRC_FILE_END_iprp_incvalue2", 9); SetPRCSwitch("PRC_FILE_END_iprp_kitcost", 50); SetPRCSwitch("PRC_FILE_END_iprp_lightcost", 4); - SetPRCSwitch("PRC_FILE_END_iprp_matcost", 77); - SetPRCSwitch("PRC_FILE_END_iprp_material", 77); + SetPRCSwitch("PRC_FILE_END_iprp_matcost", 145); + SetPRCSwitch("PRC_FILE_END_iprp_material", 145); SetPRCSwitch("PRC_FILE_END_iprp_maxpp", 8); SetPRCSwitch("PRC_FILE_END_iprp_meleecost", 20); SetPRCSwitch("PRC_FILE_END_iprp_metamagic", 6); @@ -458,11 +459,11 @@ void SetDefaultFileEnds() SetPRCSwitch("PRC_FILE_END_iprp_soakcost", 50); SetPRCSwitch("PRC_FILE_END_iprp_speed_dec", 9); SetPRCSwitch("PRC_FILE_END_iprp_speed_enh", 9); - SetPRCSwitch("PRC_FILE_END_iprp_spellcost", 243); + SetPRCSwitch("PRC_FILE_END_iprp_spellcost", 298); SetPRCSwitch("PRC_FILE_END_iprp_spellcstr", 42); SetPRCSwitch("PRC_FILE_END_iprp_spelllvcost", 9); SetPRCSwitch("PRC_FILE_END_iprp_spelllvlimm", 9); - SetPRCSwitch("PRC_FILE_END_iprp_spells", 1456); + SetPRCSwitch("PRC_FILE_END_iprp_spells", 1552); SetPRCSwitch("PRC_FILE_END_iprp_spellshl", 7); SetPRCSwitch("PRC_FILE_END_iprp_srcost", 99); SetPRCSwitch("PRC_FILE_END_iprp_staminacost", -1); @@ -492,9 +493,9 @@ void SetDefaultFileEnds() SetPRCSwitch("PRC_FILE_END_itmwizwands", 38); SetPRCSwitch("PRC_FILE_END_keymap", 70); SetPRCSwitch("PRC_FILE_END_lightcolor", 32); - SetPRCSwitch("PRC_FILE_END_loadhints", 88); + SetPRCSwitch("PRC_FILE_END_loadhints", 101); SetPRCSwitch("PRC_FILE_END_loadscreens", 259); - SetPRCSwitch("PRC_FILE_END_masterfeats", 113); + SetPRCSwitch("PRC_FILE_END_masterfeats", 125); SetPRCSwitch("PRC_FILE_END_materialcomp", 200); SetPRCSwitch("PRC_FILE_END_metamagic", 6); SetPRCSwitch("PRC_FILE_END_namefilter", 3); @@ -720,7 +721,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 +768,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); @@ -866,6 +867,31 @@ void CreateSwitchNameArray() //if you add more switches, add them to this list array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_DEBUG); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_COMBAT_DEBUG); + +//craft + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_DISABLE_CRAFT); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_TIMER_MULTIPLIER); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_TIMER_MAX); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_TIMER_MIN); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_BREW_POTION_CASTER_LEVEL); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_SCRIBE_SCROLL_CASTER_LEVEL); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_WAND_CASTER_LEVEL); + 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"), 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); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CREATE_INFUSION_OPTIONAL_HERBS); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_SCEPTER_CASTER_LEVEL); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_MATERIAL_COMPONENTS); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_DISABLE_COMPONENTS_SHOP); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_PNP_TRUESEEING); @@ -876,6 +902,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_ALLOWED_TO_REMOVE_FRIENDLY_SPELLS); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_ALLOWED_TO_SEE_HOSTILE_SPELLS); 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); @@ -994,7 +1022,7 @@ void CreateSwitchNameArray() array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_USES_PER_WEAPON_POISON_COUNT); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_USES_PER_WEAPON_POISON_DIE); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_POISON_ALLOW_CLEAN_IN_EQUIP); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_POISON_USE_INGREDIENST); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_POISON_USE_INGREDIENTS); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_PSI_ASTRAL_CONSTRUCT_USE_2DA); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_PSI_ASTRAL_CONSTRUCT_DUR_MOD); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_PNP_RAPID_METABOLISM); @@ -1058,29 +1086,31 @@ void CreateSwitchNameArray() array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_LETOSCRIPT_UNICORN_SQL); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_LETOSCRIPT_GETNEWESTBIC); -//craft - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_DISABLE_CRAFT); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_TIMER_MULTIPLIER); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_TIMER_MAX); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_TIMER_MIN); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_BREW_POTION_CASTER_LEVEL); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_SCRIBE_SCROLL_CASTER_LEVEL); - array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_CRAFT_WAND_CASTER_LEVEL); - 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_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); - //spells //shifter + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_WEREWOLF_HYBRID_USE_SHIFTER_SHAPECHANGE); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_WILDSHAPE_ALLOWS_ARMS_SLOT); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_USECR); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_S_HUGE); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_S_LARGE); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_S_MEDIUM); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_S_SMALL); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_S_TINY); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_OUTSIDER); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_ELEMENTAL); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_CONSTRUCT); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_UNDEAD); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_DRAGON); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_ABERRATION); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_OOZE); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_MAGICALBEAST); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_GIANT); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_VERMIN); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_BEAST); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_ANIMAL); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_MONSTROUSHUMANOID); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PNP_SHFT_F_HUMANOID); //general diff --git a/src/include/inv_inc_blast.nss b/src/include/inv_inc_blast.nss index 683b9cd..94709f6 100644 --- a/src/include/inv_inc_blast.nss +++ b/src/include/inv_inc_blast.nss @@ -1,4 +1,5 @@ #include "prc_inc_clsfunc" +#include "prc_inc_sp_tch" int GetBlastDamageDices(object oInvoker, int nInvokerLevel) { diff --git a/src/include/inv_inc_invknown.nss b/src/include/inv_inc_invknown.nss index dbd7acb..95800ad 100644 --- a/src/include/inv_inc_invknown.nss +++ b/src/include/inv_inc_invknown.nss @@ -135,6 +135,9 @@ int GetHasInvocation(int nInvocation, object oCreature = OBJECT_SELF); /* Includes */ ////////////////////////////////////////////////// +int GetPrimaryInvocationClass(object oCreature = OBJECT_SELF); +int GetInvocationPRCLevels(object oCaster); + #include "inc_item_props" #include "prc_x2_itemprop" #include "inc_lookups" diff --git a/src/include/inv_inc_invoke.nss b/src/include/inv_inc_invoke.nss index 0abd1e1..da67444 100644 --- a/src/include/inv_inc_invoke.nss +++ b/src/include/inv_inc_invoke.nss @@ -123,7 +123,7 @@ void DeleteLocalInvocation(object oObject, string sName); /* Includes */ ////////////////////////////////////////////////// -//#include "inv_inc_invfunc" //Access in parent +#include "inv_inc_invfunc" //Access in parent #include "prc_spellf_inc" ////////////////////////////////////////////////// diff --git a/src/include/inv_invokehook.nss b/src/include/inv_invokehook.nss index 92b0f1f..53010f3 100644 --- a/src/include/inv_invokehook.nss +++ b/src/include/inv_invokehook.nss @@ -77,6 +77,15 @@ int PreInvocationCastCode() int nContinue = !ExecuteScriptAndReturnInt("prespellcode", oInvoker); + //--------------------------------------------------------------------------- + // Block forsakers from using invocations + //--------------------------------------------------------------------------- + if(GetLevelByClass(CLASS_TYPE_FORSAKER, oInvoker) > 0) + { + SendMessageToPC(oInvoker, "Forsakers cannot use invocations."); + return FALSE; + } + //--------------------------------------------------------------------------- // Break any spell require maintaining concentration //--------------------------------------------------------------------------- @@ -168,3 +177,4 @@ int PreInvocationCastCode() return nContinue; } +//:: void main (){} \ No newline at end of file diff --git a/src/include/moi_inc_moifunc.nss b/src/include/moi_inc_moifunc.nss index d48a441..62af858 100644 --- a/src/include/moi_inc_moifunc.nss +++ b/src/include/moi_inc_moifunc.nss @@ -1170,7 +1170,10 @@ int GetMaxEssentiaCapacityFeat(object oMeldshaper) { int nMax = 1; // Always can invest one int nHD = GetHitDice(oMeldshaper); - if (nHD >= 31) nMax = 5; + if (nHD >= 61) nMax = 8; + else if (nHD >= 51) nMax = 7; + else if (nHD >= 41) nMax = 6; + else if (nHD >= 31) nMax = 5; else if (nHD >= 18) nMax = 4; else if (nHD >= 12) nMax = 3; else if (nHD >= 6) nMax = 2; @@ -1182,7 +1185,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/pnp_shft_poly.nss b/src/include/pnp_shft_poly.nss index ce6d860..21b6663 100644 --- a/src/include/pnp_shft_poly.nss +++ b/src/include/pnp_shft_poly.nss @@ -10,9 +10,586 @@ void ShifterCheck(object oPC); #include "pnp_shft_main" #include "prc_inc_shifting" +//::////////////////Begin Werewolf////////////////// const string PRC_PNP_SHIFTING = "PRC_Shift"; -////////////////Begin Werewolf////////////////// +void LycanthropePoly(object oPC, int nPoly) +{ + effect eVis = EffectVisualEffect(VFX_IMP_POLYMORPH); + effect ePoly = SupernaturalEffect(EffectPolymorph(nPoly)); + + int bMonkGloves = GetLocalInt(oPC, "WEARING_MONK_GLOVES"); + int bArmsSlotAllowed = GetPRCSwitch(PRC_WILDSHAPE_ALLOWS_ARMS_SLOT); + + int bWeapon = StringToInt(Get2DACache("polymorph","MergeW",nPoly)) == 1; + int bArmor = StringToInt(Get2DACache("polymorph","MergeA",nPoly)) == 1; + int bItems = StringToInt(Get2DACache("polymorph","MergeI",nPoly)) == 1; + + object oWeaponOld = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC); + object oArmorOld = GetItemInSlot(INVENTORY_SLOT_CHEST,oPC); + object oRing1Old = GetItemInSlot(INVENTORY_SLOT_LEFTRING,oPC); + object oRing2Old = GetItemInSlot(INVENTORY_SLOT_RIGHTRING,oPC); + object oAmuletOld = GetItemInSlot(INVENTORY_SLOT_NECK,oPC); + object oCloakOld = GetItemInSlot(INVENTORY_SLOT_CLOAK,oPC); + object oBootsOld = GetItemInSlot(INVENTORY_SLOT_BOOTS,oPC); + object oBeltOld = GetItemInSlot(INVENTORY_SLOT_BELT,oPC); + object oHelmetOld = GetItemInSlot(INVENTORY_SLOT_HEAD,oPC); + object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND,oPC); + object oGlovesOld = GetItemInSlot(INVENTORY_SLOT_ARMS,oPC); + + if (GetIsObjectValid(oShield)) + { + int nShieldType = GetBaseItemType(oShield); + if (nShieldType != BASE_ITEM_LARGESHIELD && + nShieldType != BASE_ITEM_SMALLSHIELD && + nShieldType != BASE_ITEM_TOWERSHIELD) + { + oShield = OBJECT_INVALID; + } + } + + ShifterCheck(oPC); + ClearAllActions(); + + ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oPC); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, ePoly, oPC); + + object oWeaponNewRight = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R,oPC); + object oWeaponNewLeft = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L,oPC); + object oWeaponNewBite = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B,oPC); + object oArmorNew = GetItemInSlot(INVENTORY_SLOT_CARMOUR,oPC); + + //:: Weapon & Armor merge block + object oMergeWeaponSource = OBJECT_INVALID; + object oMergeArmorSource = OBJECT_INVALID; + + //:: Determine Weapon Merge Source + if (bWeapon) + { + if (bMonkGloves) + { + if (GetIsObjectValid(oGlovesOld)) + oMergeWeaponSource = oGlovesOld; + } + else + { + //:: Always attempt to merge melee weapon to creature weapon + oMergeWeaponSource = oWeaponOld; // even if empty, ensures proper state + } + } + else + { + //:: Weapon not requested, but arms-slot allowed monk gloves can merge via armor branch + if (bMonkGloves && bArmsSlotAllowed && GetIsObjectValid(oGlovesOld)) + oMergeWeaponSource = oGlovesOld; + } + + //:: Determine Armor Merge Source + if (bArmor && GetIsObjectValid(oArmorNew)) + { + if (!bMonkGloves) + { + if (bArmsSlotAllowed && GetIsObjectValid(oGlovesOld)) + oMergeArmorSource = oGlovesOld; + + if (GetIsObjectValid(oShield)) IPWildShapeCopyItemProperties(oShield, oArmorNew); + if (GetIsObjectValid(oHelmetOld)) IPWildShapeCopyItemProperties(oHelmetOld, oArmorNew); + if (GetIsObjectValid(oArmorOld)) IPWildShapeCopyItemProperties(oArmorOld, oArmorNew); + } + else + { + if (GetIsObjectValid(oShield)) IPWildShapeCopyItemProperties(oShield, oArmorNew); + if (GetIsObjectValid(oHelmetOld)) IPWildShapeCopyItemProperties(oHelmetOld, oArmorNew); + if (GetIsObjectValid(oArmorOld)) IPWildShapeCopyItemProperties(oArmorOld, oArmorNew); + } + } + else if (bArmor && !GetIsObjectValid(oArmorNew) && DEBUG) + { + DoDebug("LycanthropePoly: MergeA set, but oArmorNew invalid."); + } + + //:: Apply Weapon Merge + if (GetIsObjectValid(oMergeWeaponSource) || bWeapon) + { + //:: Always attempt to merge weapon properties even if source is OBJECT_INVALID + if (GetIsObjectValid(oWeaponNewLeft)) IPWildShapeCopyItemProperties(oMergeWeaponSource, oWeaponNewLeft, TRUE); + if (GetIsObjectValid(oWeaponNewRight)) IPWildShapeCopyItemProperties(oMergeWeaponSource, oWeaponNewRight, TRUE); + if (GetIsObjectValid(oWeaponNewBite)) IPWildShapeCopyItemProperties(oMergeWeaponSource, oWeaponNewBite, TRUE); + } + + //:: Apply Armor Merge + if (GetIsObjectValid(oMergeArmorSource)) + { + if (GetIsObjectValid(oArmorNew)) IPWildShapeCopyItemProperties(oMergeArmorSource, oArmorNew); + } + + //:: General item merge block + if (bItems && GetIsObjectValid(oArmorNew)) + { + if (GetIsObjectValid(oRing1Old)) IPWildShapeCopyItemProperties(oRing1Old, oArmorNew); + if (GetIsObjectValid(oRing2Old)) IPWildShapeCopyItemProperties(oRing2Old, oArmorNew); + if (GetIsObjectValid(oAmuletOld)) IPWildShapeCopyItemProperties(oAmuletOld, oArmorNew); + if (GetIsObjectValid(oCloakOld)) IPWildShapeCopyItemProperties(oCloakOld, oArmorNew); + if (GetIsObjectValid(oBootsOld)) IPWildShapeCopyItemProperties(oBootsOld, oArmorNew); + if (GetIsObjectValid(oBeltOld)) IPWildShapeCopyItemProperties(oBeltOld, oArmorNew); + } +} +//::////////////////End Werewolf////////////////// + + +/* //::////////////////Begin Werewolf////////////////// +const string PRC_PNP_SHIFTING = "PRC_Shift"; + +void LycanthropePoly(object oPC, int nPoly) +{ + effect eVis = EffectVisualEffect(VFX_IMP_POLYMORPH); + effect ePoly = SupernaturalEffect(EffectPolymorph(nPoly)); + + int bMonkGloves = GetLocalInt(oPC, "WEARING_MONK_GLOVES"); + int bArmsSlotAllowed = GetPRCSwitch(PRC_WILDSHAPE_ALLOWS_ARMS_SLOT); + + int bWeapon = StringToInt(Get2DACache("polymorph","MergeW",nPoly)) == 1; + int bArmor = StringToInt(Get2DACache("polymorph","MergeA",nPoly)) == 1; + int bItems = StringToInt(Get2DACache("polymorph","MergeI",nPoly)) == 1; + + object oWeaponOld = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC); + object oArmorOld = GetItemInSlot(INVENTORY_SLOT_CHEST,oPC); + object oRing1Old = GetItemInSlot(INVENTORY_SLOT_LEFTRING,oPC); + object oRing2Old = GetItemInSlot(INVENTORY_SLOT_RIGHTRING,oPC); + object oAmuletOld = GetItemInSlot(INVENTORY_SLOT_NECK,oPC); + object oCloakOld = GetItemInSlot(INVENTORY_SLOT_CLOAK,oPC); + object oBootsOld = GetItemInSlot(INVENTORY_SLOT_BOOTS,oPC); + object oBeltOld = GetItemInSlot(INVENTORY_SLOT_BELT,oPC); + object oHelmetOld = GetItemInSlot(INVENTORY_SLOT_HEAD,oPC); + object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND,oPC); + object oGlovesOld = GetItemInSlot(INVENTORY_SLOT_ARMS,oPC); + + if (GetIsObjectValid(oShield)) + { + int nShieldType = GetBaseItemType(oShield); + if (nShieldType != BASE_ITEM_LARGESHIELD && + nShieldType != BASE_ITEM_SMALLSHIELD && + nShieldType != BASE_ITEM_TOWERSHIELD) + { + oShield = OBJECT_INVALID; + } + } + + ShifterCheck(oPC); + ClearAllActions(); + + ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oPC); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, ePoly, oPC); + + object oWeaponNewRight = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R,oPC); + object oWeaponNewLeft = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L,oPC); + object oWeaponNewBite = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B,oPC); + object oArmorNew = GetItemInSlot(INVENTORY_SLOT_CARMOUR,oPC); + + //:: Weapon & Armor merge block + object oMergeWeaponSource = OBJECT_INVALID; + object oMergeArmorSource = OBJECT_INVALID; + + // ---- Determine Weapon Merge Source ---- + if (bWeapon) + { + if (bMonkGloves) + { + if (GetIsObjectValid(oGlovesOld)) + oMergeWeaponSource = oGlovesOld; + } + else + { + if (GetIsObjectValid(oWeaponOld)) + oMergeWeaponSource = oWeaponOld; + } + } + else + { + if (bMonkGloves && bArmsSlotAllowed && GetIsObjectValid(oGlovesOld)) + oMergeWeaponSource = oGlovesOld; + } + + // ---- Determine Armor Merge Source ---- + if (bArmor && GetIsObjectValid(oArmorNew)) + { + if (!bMonkGloves) + { + if (bArmsSlotAllowed && GetIsObjectValid(oGlovesOld)) + oMergeArmorSource = oGlovesOld; + + if (GetIsObjectValid(oShield)) IPWildShapeCopyItemProperties(oShield, oArmorNew); + if (GetIsObjectValid(oHelmetOld)) IPWildShapeCopyItemProperties(oHelmetOld, oArmorNew); + if (GetIsObjectValid(oArmorOld)) IPWildShapeCopyItemProperties(oArmorOld, oArmorNew); + } + else + { + if (GetIsObjectValid(oShield)) IPWildShapeCopyItemProperties(oShield, oArmorNew); + if (GetIsObjectValid(oHelmetOld)) IPWildShapeCopyItemProperties(oHelmetOld, oArmorNew); + if (GetIsObjectValid(oArmorOld)) IPWildShapeCopyItemProperties(oArmorOld, oArmorNew); + } + } + else if (bArmor && !GetIsObjectValid(oArmorNew) && DEBUG) + { + DoDebug("LycanthropePoly: MergeA set, but oArmorNew invalid."); + } + + // ---- Apply Weapon Merge ---- + if (GetIsObjectValid(oMergeWeaponSource)) + { + if (GetIsObjectValid(oWeaponNewLeft)) IPWildShapeCopyItemProperties(oMergeWeaponSource, oWeaponNewLeft, TRUE); + if (GetIsObjectValid(oWeaponNewRight)) IPWildShapeCopyItemProperties(oMergeWeaponSource, oWeaponNewRight, TRUE); + if (GetIsObjectValid(oWeaponNewBite)) IPWildShapeCopyItemProperties(oMergeWeaponSource, oWeaponNewBite, TRUE); + } + + // ---- Apply Armor Merge ---- + if (GetIsObjectValid(oMergeArmorSource)) + { + if (GetIsObjectValid(oArmorNew)) IPWildShapeCopyItemProperties(oMergeArmorSource, oArmorNew); + } + + //:: General item merge block + if (bItems && GetIsObjectValid(oArmorNew)) + { + if (GetIsObjectValid(oRing1Old)) IPWildShapeCopyItemProperties(oRing1Old, oArmorNew); + if (GetIsObjectValid(oRing2Old)) IPWildShapeCopyItemProperties(oRing2Old, oArmorNew); + if (GetIsObjectValid(oAmuletOld)) IPWildShapeCopyItemProperties(oAmuletOld, oArmorNew); + if (GetIsObjectValid(oCloakOld)) IPWildShapeCopyItemProperties(oCloakOld, oArmorNew); + if (GetIsObjectValid(oBootsOld)) IPWildShapeCopyItemProperties(oBootsOld, oArmorNew); + if (GetIsObjectValid(oBeltOld)) IPWildShapeCopyItemProperties(oBeltOld, oArmorNew); + } +} +//::////////////////End Werewolf////////////////// */ + + +/* //::////////////////Begin Werewolf////////////////// +const string PRC_PNP_SHIFTING = "PRC_Shift"; + +void LycanthropePoly(object oPC, int nPoly) +{ + effect eVis = EffectVisualEffect(VFX_IMP_POLYMORPH); + effect ePoly = SupernaturalEffect(EffectPolymorph(nPoly)); + + int bMonkGloves = GetLocalInt(oPC, "WEARING_MONK_GLOVES"); + int bArmsSlotAllowed = GetPRCSwitch(PRC_WILDSHAPE_ALLOWS_ARMS_SLOT); + + int bWeapon = StringToInt(Get2DACache("polymorph","MergeW",nPoly)) == 1; + int bArmor = StringToInt(Get2DACache("polymorph","MergeA",nPoly)) == 1; + int bItems = StringToInt(Get2DACache("polymorph","MergeI",nPoly)) == 1; + + object oWeaponOld = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC); + object oArmorOld = GetItemInSlot(INVENTORY_SLOT_CHEST,oPC); + object oRing1Old = GetItemInSlot(INVENTORY_SLOT_LEFTRING,oPC); + object oRing2Old = GetItemInSlot(INVENTORY_SLOT_RIGHTRING,oPC); + object oAmuletOld = GetItemInSlot(INVENTORY_SLOT_NECK,oPC); + object oCloakOld = GetItemInSlot(INVENTORY_SLOT_CLOAK,oPC); + object oBootsOld = GetItemInSlot(INVENTORY_SLOT_BOOTS,oPC); + object oBeltOld = GetItemInSlot(INVENTORY_SLOT_BELT,oPC); + object oHelmetOld = GetItemInSlot(INVENTORY_SLOT_HEAD,oPC); + object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND,oPC); + object oGlovesOld = GetItemInSlot(INVENTORY_SLOT_ARMS,oPC); + + if (GetIsObjectValid(oShield)) + { + int nShieldType = GetBaseItemType(oShield); + if (nShieldType != BASE_ITEM_LARGESHIELD && + nShieldType != BASE_ITEM_SMALLSHIELD && + nShieldType != BASE_ITEM_TOWERSHIELD) + { + oShield = OBJECT_INVALID; + } + } + + ShifterCheck(oPC); + ClearAllActions(); + + ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oPC); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, ePoly, oPC); + + object oWeaponNewRight = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R,oPC); + object oWeaponNewLeft = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L,oPC); + object oWeaponNewBite = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B,oPC); + object oArmorNew = GetItemInSlot(INVENTORY_SLOT_CARMOUR,oPC); + + //:: Weapon merge block + if (bWeapon) + { + object oMergeSource = OBJECT_INVALID; + + // Priority: monk gloves override if worn and arms-slot not allowed + if (bMonkGloves && !bArmsSlotAllowed) + { + if (GetIsObjectValid(oGlovesOld)) + oMergeSource = oGlovesOld; + } + else + { + // Otherwise use main-hand weapon if it exists + if (GetIsObjectValid(oWeaponOld)) + oMergeSource = oWeaponOld; + } + + // Apply merge to creature weapons if we have a source + if (GetIsObjectValid(oMergeSource)) + { + if (GetIsObjectValid(oWeaponNewLeft)) IPWildShapeCopyItemProperties(oMergeSource, oWeaponNewLeft, TRUE); + if (GetIsObjectValid(oWeaponNewRight)) IPWildShapeCopyItemProperties(oMergeSource, oWeaponNewRight, TRUE); + if (GetIsObjectValid(oWeaponNewBite)) IPWildShapeCopyItemProperties(oMergeSource, oWeaponNewBite, TRUE); + } + } + + //:: Armor merge block + if (bArmor && GetIsObjectValid(oArmorNew)) + { + if (GetIsObjectValid(oShield)) IPWildShapeCopyItemProperties(oShield, oArmorNew); + if (GetIsObjectValid(oHelmetOld)) IPWildShapeCopyItemProperties(oHelmetOld, oArmorNew); + if (GetIsObjectValid(oArmorOld)) IPWildShapeCopyItemProperties(oArmorOld, oArmorNew); + + // Arms-slot allowed -> apply gloves & bracers to creature weapons + if (bArmsSlotAllowed && GetIsObjectValid(oGlovesOld)) + { + if (DEBUG) DoDebug("LycanthropePoly: Arms-slot allowed -> applying gloves/bracers to creature weapons from armor branch."); + if (GetIsObjectValid(oWeaponNewLeft)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewLeft, TRUE); + if (GetIsObjectValid(oWeaponNewRight)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewRight, TRUE); + if (GetIsObjectValid(oWeaponNewBite)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewBite, TRUE); + } + } + else if (bArmor && !GetIsObjectValid(oArmorNew) && DEBUG) + { + DoDebug("LycanthropePoly: MergeA set, but oArmorNew invalid."); + } + + //:: General item merge block + if (bItems && GetIsObjectValid(oArmorNew)) + { + if (GetIsObjectValid(oRing1Old)) IPWildShapeCopyItemProperties(oRing1Old, oArmorNew); + if (GetIsObjectValid(oRing2Old)) IPWildShapeCopyItemProperties(oRing2Old, oArmorNew); + if (GetIsObjectValid(oAmuletOld)) IPWildShapeCopyItemProperties(oAmuletOld, oArmorNew); + if (GetIsObjectValid(oCloakOld)) IPWildShapeCopyItemProperties(oCloakOld, oArmorNew); + if (GetIsObjectValid(oBootsOld)) IPWildShapeCopyItemProperties(oBootsOld, oArmorNew); + if (GetIsObjectValid(oBeltOld)) IPWildShapeCopyItemProperties(oBeltOld, oArmorNew); + } +} +//::////////////////End Werewolf////////////////// + */ + +/* //::////////////////Begin Werewolf////////////////// +const string PRC_PNP_SHIFTING = "PRC_Shift"; + +void LycanthropePoly(object oPC, int nPoly) +{ + effect eVis = EffectVisualEffect(VFX_IMP_POLYMORPH); + effect ePoly = SupernaturalEffect(EffectPolymorph(nPoly)); + + int bMonkGloves = GetLocalInt(oPC, "WEARING_MONK_GLOVES"); + int bArmsSlotAllowed = GetPRCSwitch(PRC_WILDSHAPE_ALLOWS_ARMS_SLOT); + + int bWeapon = StringToInt(Get2DACache("polymorph","MergeW",nPoly)) == 1; + int bArmor = StringToInt(Get2DACache("polymorph","MergeA",nPoly)) == 1; + int bItems = StringToInt(Get2DACache("polymorph","MergeI",nPoly)) == 1; + + object oWeaponOld = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC); + object oArmorOld = GetItemInSlot(INVENTORY_SLOT_CHEST,oPC); + object oRing1Old = GetItemInSlot(INVENTORY_SLOT_LEFTRING,oPC); + object oRing2Old = GetItemInSlot(INVENTORY_SLOT_RIGHTRING,oPC); + object oAmuletOld = GetItemInSlot(INVENTORY_SLOT_NECK,oPC); + object oCloakOld = GetItemInSlot(INVENTORY_SLOT_CLOAK,oPC); + object oBootsOld = GetItemInSlot(INVENTORY_SLOT_BOOTS,oPC); + object oBeltOld = GetItemInSlot(INVENTORY_SLOT_BELT,oPC); + object oHelmetOld = GetItemInSlot(INVENTORY_SLOT_HEAD,oPC); + object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND,oPC); + object oGlovesOld = GetItemInSlot(INVENTORY_SLOT_ARMS,oPC); + + if (GetIsObjectValid(oShield)) + { + int nShieldType = GetBaseItemType(oShield); + if (nShieldType != BASE_ITEM_LARGESHIELD && + nShieldType != BASE_ITEM_SMALLSHIELD && + nShieldType != BASE_ITEM_TOWERSHIELD) + { + oShield = OBJECT_INVALID; + } + } + + ShifterCheck(oPC); + ClearAllActions(); + + ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oPC); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, ePoly, oPC); + + object oWeaponNewRight = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R,oPC); + object oWeaponNewLeft = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L,oPC); + object oWeaponNewBite = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B,oPC); + object oArmorNew = GetItemInSlot(INVENTORY_SLOT_CARMOUR,oPC); + + //:: Weapon merge block + //:: Only blocked if monk gloves are equipped AND arms-slot merge is NOT allowed + if (bWeapon && !bMonkGloves) + { + if (GetIsObjectValid(oWeaponOld)) + { + if (GetIsObjectValid(oWeaponNewLeft)) IPWildShapeCopyItemProperties(oWeaponOld, oWeaponNewLeft, TRUE); + if (GetIsObjectValid(oWeaponNewRight)) IPWildShapeCopyItemProperties(oWeaponOld, oWeaponNewRight, TRUE); + if (GetIsObjectValid(oWeaponNewBite)) IPWildShapeCopyItemProperties(oWeaponOld, oWeaponNewBite, TRUE); + } + } + else if (bWeapon && bMonkGloves && !bArmsSlotAllowed) + { + if (DEBUG) DoDebug("LycanthropePoly: Monk gloves overriding weapon merge (arms slot NOT allowed)."); + if (GetIsObjectValid(oGlovesOld)) + { + if (GetIsObjectValid(oWeaponNewLeft)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewLeft, TRUE); + if (GetIsObjectValid(oWeaponNewRight)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewRight, TRUE); + if (GetIsObjectValid(oWeaponNewBite)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewBite, TRUE); + } + } + + + //:: Armor merge block + //:: Apply armor and gloves (if arms-slot allowed) + + if (bArmor && GetIsObjectValid(oArmorNew)) + { + if (GetIsObjectValid(oShield)) IPWildShapeCopyItemProperties(oShield, oArmorNew); + if (GetIsObjectValid(oHelmetOld)) IPWildShapeCopyItemProperties(oHelmetOld, oArmorNew); + if (GetIsObjectValid(oArmorOld)) IPWildShapeCopyItemProperties(oArmorOld, oArmorNew); + + if (bArmsSlotAllowed && bMonkGloves && GetIsObjectValid(oGlovesOld)) + { + if (DEBUG) DoDebug("LycanthropePoly: Arms-slot allowed -> applying gloves to creature weapons from armor branch."); + if (GetIsObjectValid(oWeaponNewLeft)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewLeft, TRUE); + if (GetIsObjectValid(oWeaponNewRight)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewRight, TRUE); + if (GetIsObjectValid(oWeaponNewBite)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewBite, TRUE); + } + } + else if (bArmor && !GetIsObjectValid(oArmorNew) && DEBUG) + { + DoDebug("LycanthropePoly: MergeA set, but oArmorNew invalid."); + } + + //:: General item merge block + if (bItems && GetIsObjectValid(oArmorNew)) + { + if (GetIsObjectValid(oRing1Old)) IPWildShapeCopyItemProperties(oRing1Old, oArmorNew); + if (GetIsObjectValid(oRing2Old)) IPWildShapeCopyItemProperties(oRing2Old, oArmorNew); + if (GetIsObjectValid(oAmuletOld)) IPWildShapeCopyItemProperties(oAmuletOld, oArmorNew); + if (GetIsObjectValid(oCloakOld)) IPWildShapeCopyItemProperties(oCloakOld, oArmorNew); + if (GetIsObjectValid(oBootsOld)) IPWildShapeCopyItemProperties(oBootsOld, oArmorNew); + if (GetIsObjectValid(oBeltOld)) IPWildShapeCopyItemProperties(oBeltOld, oArmorNew); + } +} +//::////////////////End Werewolf////////////////// */ + + +/* //::////////////////Begin Werewolf////////////////// +const string PRC_PNP_SHIFTING = "PRC_Shift"; + +void LycanthropePoly(object oPC, int nPoly) +{ + effect eVis = EffectVisualEffect(VFX_IMP_POLYMORPH); + effect ePoly = SupernaturalEffect(EffectPolymorph(nPoly)); + + int bMonkGloves = GetLocalInt(oPC, "WEARING_MONK_GLOVES"); + int bArmsSlotAllowed = GetPRCSwitch(PRC_WILDSHAPE_ALLOWS_ARMS_SLOT); + + int bWeapon = StringToInt(Get2DACache("polymorph","MergeW",nPoly)) == 1; + int bArmor = StringToInt(Get2DACache("polymorph","MergeA",nPoly)) == 1; + int bItems = StringToInt(Get2DACache("polymorph","MergeI",nPoly)) == 1; + + object oWeaponOld = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC); + object oArmorOld = GetItemInSlot(INVENTORY_SLOT_CHEST,oPC); + object oRing1Old = GetItemInSlot(INVENTORY_SLOT_LEFTRING,oPC); + object oRing2Old = GetItemInSlot(INVENTORY_SLOT_RIGHTRING,oPC); + object oAmuletOld = GetItemInSlot(INVENTORY_SLOT_NECK,oPC); + object oCloakOld = GetItemInSlot(INVENTORY_SLOT_CLOAK,oPC); + object oBootsOld = GetItemInSlot(INVENTORY_SLOT_BOOTS,oPC); + object oBeltOld = GetItemInSlot(INVENTORY_SLOT_BELT,oPC); + object oHelmetOld = GetItemInSlot(INVENTORY_SLOT_HEAD,oPC); + object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND,oPC); + object oGlovesOld = GetItemInSlot(INVENTORY_SLOT_ARMS,oPC); + + if (GetIsObjectValid(oShield)) + { + int nShieldType = GetBaseItemType(oShield); + if (nShieldType != BASE_ITEM_LARGESHIELD && + nShieldType != BASE_ITEM_SMALLSHIELD && + nShieldType != BASE_ITEM_TOWERSHIELD) + { + oShield = OBJECT_INVALID; + } + } + + ShifterCheck(oPC); + ClearAllActions(); + + ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oPC); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, ePoly, oPC); + + object oWeaponNewRight = GetItemInSlot(INVENTORY_SLOT_CWEAPON_R,oPC); + object oWeaponNewLeft = GetItemInSlot(INVENTORY_SLOT_CWEAPON_L,oPC); + object oWeaponNewBite = GetItemInSlot(INVENTORY_SLOT_CWEAPON_B,oPC); + object oArmorNew = GetItemInSlot(INVENTORY_SLOT_CARMOUR,oPC); + + //:: Weapon merge block + //:: Only blocked if monk gloves are equipped AND arms-slot merge is NOT allowed + if (bWeapon && !bMonkGloves) + { + if (GetIsObjectValid(oWeaponOld)) + { + if (GetIsObjectValid(oWeaponNewLeft)) IPWildShapeCopyItemProperties(oWeaponOld, oWeaponNewLeft, TRUE); + if (GetIsObjectValid(oWeaponNewRight)) IPWildShapeCopyItemProperties(oWeaponOld, oWeaponNewRight, TRUE); + if (GetIsObjectValid(oWeaponNewBite)) IPWildShapeCopyItemProperties(oWeaponOld, oWeaponNewBite, TRUE); + } + } + else if (bWeapon && bMonkGloves && !bArmsSlotAllowed) + { + if (DEBUG) DoDebug("LycanthropePoly: Monk gloves overriding weapon merge (arms slot NOT allowed)."); + if (GetIsObjectValid(oGlovesOld)) + { + if (GetIsObjectValid(oWeaponNewLeft)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewLeft, TRUE); + if (GetIsObjectValid(oWeaponNewRight)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewRight, TRUE); + if (GetIsObjectValid(oWeaponNewBite)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewBite, TRUE); + } + } + + + //:: Armor merge block + //:: Apply armor and gloves (if arms-slot allowed) + + if (bArmor && GetIsObjectValid(oArmorNew)) + { + if (GetIsObjectValid(oShield)) IPWildShapeCopyItemProperties(oShield, oArmorNew); + if (GetIsObjectValid(oHelmetOld)) IPWildShapeCopyItemProperties(oHelmetOld, oArmorNew); + if (GetIsObjectValid(oArmorOld)) IPWildShapeCopyItemProperties(oArmorOld, oArmorNew); + + if (bArmsSlotAllowed && bMonkGloves && GetIsObjectValid(oGlovesOld)) + { + if (DEBUG) DoDebug("LycanthropePoly: Arms-slot allowed -> applying gloves to creature weapons from armor branch."); + if (GetIsObjectValid(oWeaponNewLeft)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewLeft, TRUE); + if (GetIsObjectValid(oWeaponNewRight)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewRight, TRUE); + if (GetIsObjectValid(oWeaponNewBite)) IPWildShapeCopyItemProperties(oGlovesOld, oWeaponNewBite, TRUE); + } + } + else if (bArmor && !GetIsObjectValid(oArmorNew) && DEBUG) + { + DoDebug("LycanthropePoly: MergeA set, but oArmorNew invalid."); + } + + //:: General item merge block + if (bItems && GetIsObjectValid(oArmorNew)) + { + if (GetIsObjectValid(oRing1Old)) IPWildShapeCopyItemProperties(oRing1Old, oArmorNew); + if (GetIsObjectValid(oRing2Old)) IPWildShapeCopyItemProperties(oRing2Old, oArmorNew); + if (GetIsObjectValid(oAmuletOld)) IPWildShapeCopyItemProperties(oAmuletOld, oArmorNew); + if (GetIsObjectValid(oCloakOld)) IPWildShapeCopyItemProperties(oCloakOld, oArmorNew); + if (GetIsObjectValid(oBootsOld)) IPWildShapeCopyItemProperties(oBootsOld, oArmorNew); + if (GetIsObjectValid(oBeltOld)) IPWildShapeCopyItemProperties(oBeltOld, oArmorNew); + } +} +//::////////////////End Werewolf////////////////// */ + + +/* ////////////////Begin Werewolf////////////////// void LycanthropePoly(object oPC, int nPoly) { @@ -84,7 +661,7 @@ void LycanthropePoly(object oPC, int nPoly) } -////////////////End Werewolf////////////////// +////////////////End Werewolf////////////////// */ void ShifterCheck(object oPC) { @@ -246,4 +823,6 @@ void DoTail(object oPC, int nTailType) SetCreatureTailType(nTailType, oPC); //override any stored default appearance SetPersistantLocalInt(oPC, "AppearanceStoredTail", nTailType); -} \ No newline at end of file +} + +//::void main (){} \ No newline at end of file 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_add_spl_pen.nss b/src/include/prc_add_spl_pen.nss index e339ebd..d7ca3d6 100644 --- a/src/include/prc_add_spl_pen.nss +++ b/src/include/prc_add_spl_pen.nss @@ -54,11 +54,11 @@ int SPGetPenetrAOE(object oCaster = OBJECT_SELF, int nCasterLvl = 0); /* Includes */ ////////////////////////////////////////////////// -//#include "prc_inc_spells" +#include "prc_inc_spells" //#include "prc_alterations" //#include "prcsp_archmaginc" //#include "prc_inc_racial" - +#include "inc_2dacache" ////////////////////////////////////////////////// /* Function definitions */ 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_craft_inc.nss b/src/include/prc_craft_inc.nss index 8d0c398..ee87ad8 100644 --- a/src/include/prc_craft_inc.nss +++ b/src/include/prc_craft_inc.nss @@ -444,6 +444,7 @@ int Get2DALineFromItemprop(string sFile, itemproperty ip, object oItem) } break; } + case ITEM_PROPERTY_ECHOBLADE: return 46; break; } } return -1; @@ -1529,7 +1530,12 @@ void ApplyItemProps(object oItem, string sFile, int nLine) break; //no more itemprops, no gaps, assuming no errors } if(sFile != "craft_weapon" && sFile != "craft_armour") - SetName(oItem, GetStringByStrRef(StringToInt(Get2DACache(sFile, "Name", nLine)))); + { + SetName(oItem, GetStringByStrRef(StringToInt(Get2DACache(sFile, "Name", nLine)))); + string sDescRef = Get2DACache(sFile, "CraftedDescription", nLine); + if(sDescRef != "") + SetDescription(oItem, GetStringByStrRef(StringToInt(sDescRef))); + } } //Partly ripped off the lexicon :P @@ -1620,7 +1626,8 @@ string GetCrafting2DA(object oItem) (nBase == BASE_ITEM_BOOTS) || (nBase == BASE_ITEM_GLOVES) || (nBase == BASE_ITEM_BRACER) || - (nBase == BASE_ITEM_CLOAK)) + (nBase == BASE_ITEM_CLOAK) || + (nBase == BASE_ITEM_CRAFTED_VIAL)) ) return "craft_wondrous"; @@ -1657,19 +1664,28 @@ int GetCraftingFeat(object oItem) if(nBase == BASE_ITEM_RING) return FEAT_FORGE_RING; //routing bioware feats through this convo + if((nBase == BASE_ITEM_CRAFTED_SCEPTER) || + (nBase == BASE_ITEM_CRAFTED_SCEPTER) + ) + return FEAT_CRAFT_SCEPTER; + if((nBase == BASE_ITEM_MAGICROD) || (nBase == BASE_ITEM_CRAFTED_ROD) ) return FEAT_CRAFT_ROD; + if((nBase == BASE_ITEM_MAGICSTAFF) || (nBase == BASE_ITEM_CRAFTED_STAFF) ) return FEAT_CRAFT_STAFF; + if((nBase == BASE_ITEM_MAGICWAND) || (nBase == BASE_ITEM_BLANK_WAND) ) return FEAT_CRAFT_WAND; + if(nBase == BASE_ITEM_BLANK_POTION) return FEAT_BREW_POTION; + if(nBase == BASE_ITEM_BLANK_SCROLL) return FEAT_SCRIBE_SCROLL; if(((nBase == BASE_ITEM_HELMET) || @@ -1678,7 +1694,8 @@ int GetCraftingFeat(object oItem) (nBase == BASE_ITEM_BOOTS) || (nBase == BASE_ITEM_GLOVES) || (nBase == BASE_ITEM_BRACER) || - (nBase == BASE_ITEM_CLOAK)) + (nBase == BASE_ITEM_CLOAK) || + (nBase == BASE_ITEM_CRAFTED_VIAL)) ) return FEAT_CRAFT_WONDROUS; diff --git a/src/include/prc_effect_inc.nss b/src/include/prc_effect_inc.nss index 6186f71..a41f7d9 100644 --- a/src/include/prc_effect_inc.nss +++ b/src/include/prc_effect_inc.nss @@ -75,6 +75,14 @@ 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 Gaze Immunity effect + */ +effect EffectGazeImmune(); + /** * Dazzles the target: -1 Attack, Search, Spot, and VFX * @@ -82,6 +90,9 @@ void PRCRemoveSpellEffects(int nSpell_ID, object oCaster, object oTarget); */ effect EffectDazzle(); +//ebonfowl: adding this function to check if a target is already shaken +int GetIsShaken(object oTarget); + /** * Shaken effect: -2 to attack, all skills and saving throws * @@ -170,14 +181,11 @@ effect EffectAbilityBasedSkillIncrease(int iAbility, int iIncrease = 1); */ effect EffectAbilityBasedSkillDecrease(int iAbility, int iDecrease = 1); -//ebonfowl: adding this function to check if a target is already shaken -int GetIsShaken(object oTarget); - ////////////////////////////////////////////////// /* Include section */ ////////////////////////////////////////////////// - #include "prc_inc_castlvl" // get prc_racial_const, prc_inc_nwscript, prc_inc_newip +#include "inc_epicspelldef" ////////////////////////////////////////////////// /* Internal functions */ @@ -261,6 +269,8 @@ object GetObjectToApplyNewEffect(string sTag, object oPC, int nStripEffects = TR SetCreatureAppearanceType(oWP, APPEARANCE_TYPE_INVISIBLE_HUMAN_MALE); ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectVisualEffect(VFX_DUR_CUTSCENE_INVISIBILITY), oWP); ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectCutsceneGhost(), oWP); + AssignCommand(oWP, ActionUseSkill(SKILL_HIDE, oWP)); + } //remove previous effects if(nStripEffects) @@ -583,7 +593,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 +650,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 +707,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 +725,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 +746,47 @@ effect EffectImmunityMiscAll(){ return eReturn; } +//:: Immunity to all gaze attacks +effect EffectGazeImmune() +{ + + 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; +} + +//:: Immunity to all perification attacks +effect EffectPetrificationImmune() +{ + effect eReturn = EffectSpellImmunity(SPELLABILITY_TOUCH_PETRIFY); + eReturn = EffectSpellImmunity(SPELLABILITY_BREATH_PETRIFY); + eReturn = EffectSpellImmunity(SPELL_FLESH_TO_STONE); + eReturn = EffectSpellImmunity(SPELL_STONEHOLD); + eReturn = EffectSpellImmunity(SPELL_EPIC_A_STONE); + eReturn = EffectSpellImmunity(POWER_CRYSTALLIZE); + eReturn = EffectSpellImmunity(MELD_BASILISK_MASK); + eReturn = EffectSpellImmunity(SPELLABILITY_GAZE_PETRIFY); + + eReturn = TagEffect(eReturn, "PRCPetrificationImmune"); + + return eReturn; +} + int GetIsShaken(object oTarget) { effect eEffect = GetFirstEffect(oTarget); @@ -747,4 +802,13 @@ int GetIsShaken(object oTarget) eEffect = GetNextEffect(oTarget); } return FALSE; -} \ No newline at end of file +} + +// Forward declarations for size change effects +// Implementations are in prc_inc_size +effect EffectSizeChange(object oTarget, int nObjectType, int bEnlarge, int nChanges); +void DelayedSetVisualTransform(int nExpectedGeneration, object oTarget, int nTransform, float fValue); +void DelaySetVisualTransform(float fDelay, object oTarget, string sGenerationName, int nTransform, float fValue); + +//:: 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 a666b3e..6fd8c94 100644 --- a/src/include/prc_feat_const.nss +++ b/src/include/prc_feat_const.nss @@ -4,6 +4,11 @@ //:: PRC Options Conversation const int FEAT_OPTIONS_CONVERSATION = 2285; +//;; Builder Feats +const int FEAT_ARCHETYPAL_FORM = 2918; +const int FEAT_INTRINSIC_ARMOR = 25990; +const int FEAT_INTRINSIC_WEAPON = 25991; + //:: Missing Bioware Feats const int FEAT_EPIC_PLANAR_TURNING = 854; @@ -152,6 +157,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 +203,31 @@ 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; + + //:: Lost Empires of Faerun feats +const int FEAT_CRAFT_SCEPTER = 25962; +const int FEAT_MAGICAL_ARTISAN_CRAFT_SCEPTER = 25963; + //:: Racial Feats const int FEAT_WEMIC_JUMP_8 = 4518; const int FEAT_URDINNIR_STONESKIN = 4644; @@ -782,6 +815,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; @@ -1286,6 +1322,7 @@ const int FEAT_SOMATIC_WEAPONRY = 5186; // Forgotten Realms Campaign Setting const int FEAT_INSCRIBE_RUNE = 2462; +const int EPIC_FEAT_INSCRIBE_EPIC_RUNES = 2549; // Miniature Handbook const int FEAT_SHIELDMATE = 3258; @@ -1538,18 +1575,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 = 26002; +//:: 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; @@ -3189,6 +3227,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; @@ -3695,6 +3735,9 @@ const int FEAT_EPIC_ARTIFICER = 4072; //////////////// END INFUSIONS ///////////////// //////////////////////////////////////////////////*/ +//:: Monk +const int FEAT_MONK_ABUNDANT_STEP = 2351; + //Justice of Weald and Woe const int FEAT_LUCKY_SHOT = 24021; @@ -3933,6 +3976,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 +6248,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; @@ -6210,6 +6287,7 @@ const int FEAT_DSONG_SPELLCASTING_NONE = 19592; const int FEAT_OLLAM_SPELLCASTING_NONE = 19593; //:: PRC8 Hidden Talent Feats +const int FEAT_HIDDEN_TALENT = 25900; const int FEAT_HIDDEN_TALENT_BIOFEEDBACK = 25901; const int FEAT_HIDDEN_TALENT_BITE_WOLF = 25902; const int FEAT_HIDDEN_TALENT_BOLT = 25903; diff --git a/src/include/prc_getbest_inc.nss b/src/include/prc_getbest_inc.nss index a17eb0a..a0efeca 100644 --- a/src/include/prc_getbest_inc.nss +++ b/src/include/prc_getbest_inc.nss @@ -400,5 +400,4 @@ int GetBestAvailableSpell(object oTarget) if(nBestSpell == 99999) nBestSpell = GetBestL1Spell(oTarget, nBestSpell); if(nBestSpell == 99999) nBestSpell = GetBestL0Spell(oTarget, nBestSpell); return nBestSpell; -} - +} \ No newline at end of file diff --git a/src/include/prc_inc_breath.nss b/src/include/prc_inc_breath.nss index 0d439f9..094029c 100644 --- a/src/include/prc_inc_breath.nss +++ b/src/include/prc_inc_breath.nss @@ -124,6 +124,8 @@ void ApplyBreath(struct breath BreathUsed, location lTargetArea, int bLinger = F ////////////////////////////////////////////////// #include "prc_alterations" +#include "prcsp_archmaginc" +#include "prc_inc_spells" ////////////////////////////////////////////////// /* Internal functions */ diff --git a/src/include/prc_inc_castlvl.nss b/src/include/prc_inc_castlvl.nss index 172ee03..0c1a8be 100644 --- a/src/include/prc_inc_castlvl.nss +++ b/src/include/prc_inc_castlvl.nss @@ -1148,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); @@ -3822,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); */ @@ -3856,7 +3859,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_ARCHIVIST, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_ARCHIVIST, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); if(GetHasFeat(FEAT_BFZ_SPELLCASTING_ARCHIVIST, oCaster)) nDivine += (GetLevelByClass(CLASS_TYPE_BFZ, oCaster) + 1) / 2; @@ -4148,7 +4154,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); @@ -4187,7 +4196,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_CLERIC, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_CLERIC, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); if(GetHasFeat(FEAT_BFZ_SPELLCASTING_CLERIC, oCaster)) nDivine += (GetLevelByClass(CLASS_TYPE_BFZ, oCaster) + 1) / 2; @@ -4258,7 +4270,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); @@ -4300,9 +4315,12 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); /* if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_DRUID, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); */ + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_DRUID, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); - if(GetHasFeat(FEAT_BFZ_SPELLCASTING_DRUID, oCaster)) +/* if(GetHasFeat(FEAT_BFZ_SPELLCASTING_DRUID, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_BFZ, oCaster + 1) / 2 */ // if(GetHasFeat(FEAT_BRIMSTONE_SPEAKER_SPELLCASTING_DRUID, oCaster)) @@ -4370,10 +4388,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); @@ -4409,7 +4430,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_FAVOURED_SOUL, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_FAVOURED_SOUL, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); if(GetHasFeat(FEAT_BFZ_SPELLCASTING_FAVOURED_SOUL, oCaster)) nDivine += (GetLevelByClass(CLASS_TYPE_BFZ, oCaster) + 1) / 2; @@ -4479,6 +4503,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); */ @@ -4519,7 +4546,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); */ /* if(GetHasFeat(FEAT_BFZ_SPELLCASTING_HEALER, oCaster)) - nDivine += (GetLevelByClass(CLASS_TYPE_BFZ, oCaster) + 1) / 2; */ + nDivine += (GetLevelByClass(CLASS_TYPE_BFZ, oCaster) + 1) / 2; */ + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_HEALER, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); if(GetHasFeat(FEAT_BRIMSTONE_SPEAKER_SPELLCASTING_HEALER, oCaster)) nDivine += (GetLevelByClass(CLASS_TYPE_BRIMSTONE_SPEAKER, oCaster) + 1) / 2; @@ -4586,6 +4616,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); @@ -4623,7 +4656,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_JUSTICEWW, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_JOWAW, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); if(GetHasFeat(FEAT_BFZ_SPELLCASTING_JUSTICEWW, oCaster)) nDivine += (GetLevelByClass(CLASS_TYPE_BFZ, oCaster) + 1) / 2; @@ -4724,6 +4760,9 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_SWIFT_WING_SPELLCASTING_KNIGHT_CHALICE, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_KOTC, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); /* if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_KNIGHT_CHALICE, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); @@ -4796,6 +4835,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); */ @@ -4828,6 +4870,9 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_SWIFT_WING_SPELLCASTING_KNIGHT_MIDDLECIRCLE, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_KOTMC, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); /* if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_KNIGHT_MIDDLECIRCLE, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); @@ -4901,7 +4946,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); */ @@ -4938,6 +4986,9 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_SWIFT_WING_SPELLCASTING_NENTYAR_HUNTER, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_NENTYAR_HUNTER, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); /* if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_NENTYAR_HUNTER, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); */ @@ -5140,6 +5191,9 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_SWIFT_WING_SPELLCASTING_PALADIN, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_PALADIN, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); /* if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_PALADIN, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); @@ -5212,7 +5266,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); @@ -5245,7 +5302,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_RANGER, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_RANGER, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); if(GetHasFeat(FEAT_BFZ_SPELLCASTING_RANGER, oCaster)) nDivine += (GetLevelByClass(CLASS_TYPE_BFZ, oCaster) + 1) / 2; @@ -5316,7 +5376,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); @@ -5355,7 +5418,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_OASHAMAN, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_OASHAMAN, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); if(GetHasFeat(FEAT_BFZ_SPELLCASTING_OASHAMAN, oCaster)) nDivine += (GetLevelByClass(CLASS_TYPE_BFZ, oCaster) + 1) / 2; @@ -5529,6 +5595,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); @@ -5566,7 +5635,10 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_SOHEI, oCaster)) - nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_SOHEI, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); if(GetHasFeat(FEAT_BFZ_SPELLCASTING_SOHEI, oCaster)) nDivine += (GetLevelByClass(CLASS_TYPE_BFZ, oCaster) + 1) / 2; @@ -5636,6 +5708,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); */ @@ -5668,6 +5743,9 @@ int GetDivinePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) if(GetHasFeat(FEAT_SWIFT_WING_SPELLCASTING_SOL, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_SWIFT_WING, oCaster); + + if(GetHasFeat(FEAT_VERDANT_LORD_SPELLCASTING_SOL, oCaster)) + nDivine += GetLevelByClass(CLASS_TYPE_VERDANT_LORD, oCaster); /* if(GetHasFeat(FEAT_TENEBROUS_APOSTATE_SPELLCASTING_SOL, oCaster)) nDivine += GetLevelByClass(CLASS_TYPE_TENEBROUS_APOSTATE, oCaster); diff --git a/src/include/prc_inc_chat_pow.nss b/src/include/prc_inc_chat_pow.nss index aced79a..f1c1b64 100644 --- a/src/include/prc_inc_chat_pow.nss +++ b/src/include/prc_inc_chat_pow.nss @@ -16,6 +16,7 @@ Command summary: */ #include "prc_inc_chat" +#include "inc_persist_loca" const string CMD_POWER_ATTACK = "pow-erattack"; diff --git a/src/include/prc_inc_clsfunc.nss b/src/include/prc_inc_clsfunc.nss index 4d1ffcb..9bd42ed 100644 --- a/src/include/prc_inc_clsfunc.nss +++ b/src/include/prc_inc_clsfunc.nss @@ -380,6 +380,7 @@ int Vile_Feat(int iTypeWeap) case BASE_ITEM_BASTARDSWORD: return GetHasFeat(FEAT_VILE_MARTIAL_BASTARDSWORD); case BASE_ITEM_BATTLEAXE: return GetHasFeat(FEAT_VILE_MARTIAL_BATTLEAXE); case BASE_ITEM_CLUB: return GetHasFeat(FEAT_VILE_MARTIAL_CLUB); + case BASE_ITEM_CRAFTED_SCEPTER: return GetHasFeat(FEAT_VILE_MARTIAL_CLUB); case BASE_ITEM_DAGGER: return GetHasFeat(FEAT_VILE_MARTIAL_DAGGER); case BASE_ITEM_DART: return GetHasFeat(FEAT_VILE_MARTIAL_DART); case BASE_ITEM_DIREMACE: return GetHasFeat(FEAT_VILE_MARTIAL_DIREMACE); @@ -426,7 +427,7 @@ int Vile_Feat(int iTypeWeap) GetHasFeat(FEAT_VILE_MARTIAL_RAPIER) || GetHasFeat(FEAT_VILE_MARTIAL_ELVEN_THINBLADE)); - case BASE_ITEM_ELVEN_COURTBLADE: return GetHasFeat(FEAT_VILE_MARTIAL_GREATSWORD || + case BASE_ITEM_ELVEN_COURTBLADE: return (GetHasFeat(FEAT_VILE_MARTIAL_GREATSWORD) || GetHasFeat(FEAT_VILE_MARTIAL_ELVEN_COURTBLADE)); case BASE_ITEM_DOUBLE_SCIMITAR: return GetHasFeat(FEAT_VILE_MARTIAL_DBL_SCIMITAR); @@ -461,6 +462,7 @@ int GetSanctifedMartialFeat(int iTypeWeap) case BASE_ITEM_BASTARDSWORD: return FEAT_SANCTIFY_MARTIAL_BASTARDSWORD; case BASE_ITEM_BATTLEAXE: return FEAT_SANCTIFY_MARTIAL_BATTLEAXE; case BASE_ITEM_CLUB: return FEAT_SANCTIFY_MARTIAL_CLUB; + case BASE_ITEM_CRAFTED_SCEPTER: return FEAT_SANCTIFY_MARTIAL_CLUB; case BASE_ITEM_DAGGER: return FEAT_SANCTIFY_MARTIAL_DAGGER; case BASE_ITEM_DART: return FEAT_SANCTIFY_MARTIAL_DART; case BASE_ITEM_DIREMACE: return FEAT_SANCTIFY_MARTIAL_DIREMACE; @@ -535,6 +537,7 @@ int Sanctify_Feat(int iTypeWeap) case BASE_ITEM_BASTARDSWORD: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_BASTARDSWORD); case BASE_ITEM_BATTLEAXE: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_BATTLEAXE); case BASE_ITEM_CLUB: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_CLUB); + case BASE_ITEM_CRAFTED_SCEPTER: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_CLUB); case BASE_ITEM_DAGGER: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_DAGGER); case BASE_ITEM_DART: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_DART); case BASE_ITEM_DIREMACE: return GetHasFeat(FEAT_SANCTIFY_MARTIAL_DIREMACE); diff --git a/src/include/prc_inc_combat.nss b/src/include/prc_inc_combat.nss index 3cdbab5..6b143b5 100644 --- a/src/include/prc_inc_combat.nss +++ b/src/include/prc_inc_combat.nss @@ -1082,7 +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_MAGICSTAFF: return TRUE; case BASE_ITEM_ELVEN_COURTBLADE: return TRUE; case BASE_ITEM_MAUL: return TRUE; case BASE_ITEM_FALCHION: return TRUE; @@ -1141,8 +1141,8 @@ int GetIsSimpleWeaponType(int iWeaponType) case BASE_ITEM_CSLSHPRCWEAP: return 1; case BASE_ITEM_GLOVES: return 1; case BASE_ITEM_BRACER: return 1; - - case BASE_ITEM_CLUB: return 2; + case BASE_ITEM_CRAFTED_SCEPTER: return 1; + case BASE_ITEM_DAGGER: return 2; case BASE_ITEM_LIGHTMACE: return 2; case BASE_ITEM_SICKLE: return 2; @@ -1206,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 ); } @@ -1278,6 +1279,19 @@ struct WeaponFeat GetAllFeatsOfWeaponType(int iWeaponType) sFeat.VileMartialStrike = FEAT_VILE_MARTIAL_CLUB; break; } + case BASE_ITEM_CRAFTED_SCEPTER: { + sFeat.Focus = FEAT_WEAPON_FOCUS_CLUB; + sFeat.Specialization = FEAT_WEAPON_SPECIALIZATION_CLUB; + sFeat.EpicFocus = FEAT_EPIC_WEAPON_FOCUS_CLUB; + sFeat.EpicSpecialization = FEAT_EPIC_WEAPON_SPECIALIZATION_CLUB; + sFeat.ImprovedCritical = FEAT_IMPROVED_CRITICAL_CLUB; + sFeat.OverwhelmingCritical = FEAT_EPIC_OVERWHELMING_CRITICAL_CLUB; + sFeat.DevastatingCritical = FEAT_EPIC_DEVASTATING_CRITICAL_CLUB; + sFeat.WeaponOfChoice = FEAT_WEAPON_OF_CHOICE_CLUB; + sFeat.SanctifyMartialStrike = FEAT_SANCTIFY_MARTIAL_CLUB; + sFeat.VileMartialStrike = FEAT_VILE_MARTIAL_CLUB; + break; + } case BASE_ITEM_DAGGER: { sFeat.Focus = FEAT_WEAPON_FOCUS_DAGGER; sFeat.Specialization = FEAT_WEAPON_SPECIALIZATION_DAGGER; @@ -8089,7 +8103,12 @@ void AttackLoopLogic(object oDefender, object oAttacker, if (DEBUG) DoDebug("entered AttackLoopLogic: bFirstAttack = " + IntToString(bFirstAttack) + ", cleave = " + IntToString(bIsCleaveAttack) + ", current action = " + GetActionName(iAction)); if (DEBUG) DoDebug("AttackLoopLogic: iMainAttacks = " + IntToString(iMainAttacks) + ", iOffHandAttacks = " + IntToString(iOffHandAttacks) + ", iBonusAttacks = " + IntToString(iBonusAttacks)); - int bIsRangedAttack = sAttackVars.bIsRangedWeapon || sAttackVars.iTouchAttackType == TOUCH_ATTACK_RANGED_SPELL || sAttackVars.iTouchAttackType == TOUCH_ATTACK_RANGED; + //int bIsRangedAttack = sAttackVars.bIsRangedWeapon || sAttackVars.iTouchAttackType == TOUCH_ATTACK_RANGED_SPELL || sAttackVars.iTouchAttackType == TOUCH_ATTACK_RANGED; + + int bIsRangedAttack = sAttackVars.bIsRangedWeapon || + sAttackVars.iTouchAttackType == TOUCH_ATTACK_RANGED_SPELL || + sAttackVars.iTouchAttackType == TOUCH_ATTACK_RANGED || + GetLocalInt(oAttacker, "WhirlingBlade"); // check for valid target etc., but only if it is not a cleave or circle kick (in this case we checked all of this before) if (!bIsCleaveAttack) diff --git a/src/include/prc_inc_combmove.nss b/src/include/prc_inc_combmove.nss index b81aab4..180925b 100644 --- a/src/include/prc_inc_combmove.nss +++ b/src/include/prc_inc_combmove.nss @@ -273,6 +273,7 @@ void TigerBlooded(object oInitiator, object oTarget); #include "prc_inc_combat" #include "prc_inc_sp_tch" +#include "prc_feat_const" ////////////////////////////////////////////////// /* Internal functions */ @@ -1140,6 +1141,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 @@ -1318,7 +1322,29 @@ int DoTrip(object oPC, object oTarget, int nExtraBonus, int nGenerateAoO = TRUE, DelayCommand(0.0, PerformAttack(oTarget, oPC, eNone, 0.0, 0, 0, 0, "Improved Trip Free Attack Hit", "Improved Trip Free Attack Miss")); } } - else // If you fail, enemy gets a counter trip attempt, using Strength + else // If you fail, enemy gets a counter trip attempt, using Strength + { + if(!nCounterTrip) + { + nTargetStat = GetAbilityModifier(ABILITY_STRENGTH, oTarget) + GetCombatMoveCheckBonus(oTarget, COMBAT_MOVE_TRIP, FALSE, TRUE); + FloatingTextStringOnCreature("You have failed on your Trip attempt",oPC, FALSE); + // Roll counter trip attempt + nTargetCheck = nTargetStat + nTargetBonus + d20(); + nPCCheck = nPCStat + nPCBonus + d20(); + // If counters aren't allowed, don't knock em down + // Its down here to allow the text message to go through + SendMessageToPC(oPC, "Enemy Counter Trip Check: "+IntToString(nPCCheck)+" vs "+IntToString(nTargetCheck)); + + SetLocalInt(oPC, "TripDifference", nTargetCheck - nPCCheck); + DelayCommand(2.0, DeleteLocalInt(oPC, "TripDifference")); + } + if (nTargetCheck >= nPCCheck && nCounterTrip) + { + // Knock em down + ApplyEffectToObject(DURATION_TYPE_TEMPORARY, ExtraordinaryEffect(EffectKnockdown()), oPC, 6.0); + } + } +/* else // If you fail, enemy gets a counter trip attempt, using Strength { nTargetStat = GetAbilityModifier(ABILITY_STRENGTH, oTarget) + GetCombatMoveCheckBonus(oTarget, COMBAT_MOVE_TRIP, FALSE, TRUE); FloatingTextStringOnCreature("You have failed on your Trip attempt",oPC, FALSE); @@ -1335,7 +1361,7 @@ int DoTrip(object oPC, object oTarget, int nExtraBonus, int nGenerateAoO = TRUE, } SetLocalInt(oPC, "TripDifference", nTargetCheck - nPCCheck); DelayCommand(2.0, DeleteLocalInt(oPC, "TripDifference")); - } + } */ } else FloatingTextStringOnCreature("You have failed on your Trip attempt",oPC, FALSE); @@ -1938,10 +1964,21 @@ void TigerBlooded(object oInitiator, object oTarget) int DoDisarm(object oPC, object oTarget, int nExtraBonus = 0, int nGenerateAoO = TRUE, int nCounter = TRUE) { object oTargetWep = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oTarget); + + int bNoDisarm = GetHasFeat(FEAT_INTRINSIC_WEAPON, oTarget); + + string sName = GetName(oTarget); + + if(bNoDisarm) + { + FloatingTextStringOnCreature(sName+" is wielding an intrinsic weapon", oPC, FALSE); + AssignCommand(oPC, ActionAttack(oTarget)); + return FALSE; + } if (!GetIsObjectValid(oTargetWep) || GetPlotFlag(oTargetWep) || (!GetIsCreatureDisarmable(oTarget) && !GetPRCSwitch(PRC_PNP_DISARM)) || GetLocalInt(oTarget, "TigerFangDisarm")) { - FloatingTextStringOnCreature("Target is not a legal target", oPC, FALSE); + FloatingTextStringOnCreature(sName+" is not a legal target", oPC, FALSE); AssignCommand(oPC, ActionAttack(oTarget)); return FALSE; } @@ -2312,7 +2349,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..df73451 100644 --- a/src/include/prc_inc_core.nss +++ b/src/include/prc_inc_core.nss @@ -133,6 +133,7 @@ const int METAMAGIC_QUICKEN_LEVEL = 4; #include "prc_inc_damage" #include "prc_inc_sb_const" // Spell Book Constants #include "x0_i0_position" +#include "inc_newspellbook" /* access to prc_inc_nwscript via prc_inc_damage @@ -410,6 +411,8 @@ int PRCGetSpellLevelForClass(int nSpell, int nClass) sSpellLevel = Get2DACache("spells", "Cleric", nSpell); else if (nClass == CLASS_TYPE_BARD) sSpellLevel = Get2DACache("spells", "Bard", nSpell); + else if (nClass == CLASS_TYPE_ASSASSIN) + sSpellLevel = Get2DACache("spells", "Assassin", nSpell); else if (nClass == CLASS_TYPE_CULTIST_SHATTERED_PEAK) sSpellLevel = Get2DACache("spells", "Cultist", nSpell); else if (nClass == CLASS_TYPE_NENTYAR_HUNTER) @@ -462,7 +465,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 +608,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 +618,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_descrptr.nss b/src/include/prc_inc_descrptr.nss index bbee255..d814a6c 100644 --- a/src/include/prc_inc_descrptr.nss +++ b/src/include/prc_inc_descrptr.nss @@ -169,8 +169,8 @@ int GetSubschoolFlags(int nSpellID); /* Includes */ ////////////////////////////////////////////////// -#include "inc_2dacache" // already has access via inc_utility -//#include "inc_utility" +#include "inc_2dacache" +#include "inc_utility" ////////////////////////////////////////////////// /* Function definitions */ diff --git a/src/include/prc_inc_factotum.nss b/src/include/prc_inc_factotum.nss index 46be9c4..eee86b1 100644 --- a/src/include/prc_inc_factotum.nss +++ b/src/include/prc_inc_factotum.nss @@ -49,6 +49,8 @@ const int BRILLIANCE_SLOT_3 = 3919; ////////////////////////////////////////////////// /* Function definitions */ ////////////////////////////////////////////////// +void TriggerInspiration(object oPC, int nCombat); + void PrepareArcDilSpell(object oPC, int nSpell) { @@ -188,7 +190,8 @@ void SetInspiration(object oPC) for(i = FEAT_FONT_INSPIRATION_1; i <= FEAT_FONT_INSPIRATION_10; i++) if(GetHasFeat(i, oPC)) nFont++; - nInspiration += nFont * (1 + nFont + 1) / 2; + //nInspiration += nFont * (1 + nFont + 1) / 2; + nInspiration += nFont * (nFont + 1) / 2; SetLocalInt(oPC, "InspirationPool", nInspiration); FloatingTextStringOnCreature("Encounter begins with "+IntToString(nInspiration)+" inspiration", oPC, FALSE); } @@ -201,6 +204,8 @@ void ClearInspiration(object oPC) int ExpendInspiration(object oPC, int nCost) { + if (nCost <= 0) return FALSE; + int nInspiration = GetLocalInt(oPC, "InspirationPool"); if (nInspiration >= nCost) { @@ -261,6 +266,21 @@ void FactotumTriggerAbil(object oPC, int nAbil) IPSafeAddItemProperty(oSkin, ipIP, 60.0, X2_IP_ADDPROP_POLICY_KEEP_EXISTING, FALSE, FALSE); } +void TriggerInspiration(object oPC, int nCombat) +{ + SetLocalInt(oPC, "InspirationHBRunning", TRUE); + DelayCommand(0.249, DeleteLocalInt(oPC, "InspirationHBRunning")); + int nCurrent = GetIsInCombat(oPC); + // We just entered combat + if (nCurrent == TRUE && nCombat == FALSE) + SetInspiration(oPC); + else if (nCurrent == FALSE && nCombat == TRUE) // Just left combat + ClearInspiration(oPC); + + DelayCommand(0.25, TriggerInspiration(oPC, nCurrent)); +} + + /*void AddCunningBrillianceAbility(object oPC, int nAbil) { if (DEBUG) DoDebug("AddCunningBrillianceAbility "+IntToString(nAbil)); diff --git a/src/include/prc_inc_fork.nss b/src/include/prc_inc_fork.nss index b6b672e..3b82eeb 100644 --- a/src/include/prc_inc_fork.nss +++ b/src/include/prc_inc_fork.nss @@ -23,11 +23,14 @@ const int FEAT_TYPE_IMPROVED_CRITICAL = 5; const int FEAT_TYPE_OVERWHELMING_CRITICAL = 6; const int FEAT_TYPE_DEVASTATING_CRITICAL = 7; const int FEAT_TYPE_WEAPON_OF_CHOICE = 8; +const int FEAT_TYPE_WEAPON_PROFICIENCY = 9; ////////////////////////////////////////////////// /* Function prototypes */ ////////////////////////////////////////////////// + int GetProficiencyFeatOfWeaponType(int iWeaponType); + /** * Returns the appropriate weapon feat given a weapon type. * @@ -210,10 +213,86 @@ int GetFeatOfWeaponType(int iWeaponType, int iFeatType) case FEAT_TYPE_OVERWHELMING_CRITICAL: return GetOverwhelmingCriticalFeatOfWeaponType(iWeaponType); case FEAT_TYPE_DEVASTATING_CRITICAL: return GetDevastatingCriticalFeatOfWeaponType(iWeaponType); case FEAT_TYPE_WEAPON_OF_CHOICE: return GetWeaponOfChoiceFeatOfWeaponType(iWeaponType); + case FEAT_TYPE_WEAPON_PROFICIENCY: return GetProficiencyFeatOfWeaponType(iWeaponType); } return -1; } +int GetProficiencyFeatOfWeaponType(int iWeaponType) +{ + switch(iWeaponType) + { + case BASE_ITEM_CBLUDGWEAPON: + case BASE_ITEM_CPIERCWEAPON: + case BASE_ITEM_CSLASHWEAPON: + case BASE_ITEM_CSLSHPRCWEAP: return FEAT_WEAPON_PROFICIENCY_CREATURE; + case BASE_ITEM_INVALID: return FEAT_IMPROVED_UNARMED_STRIKE; + + case BASE_ITEM_BASTARDSWORD: return FEAT_WEAPON_PROFICIENCY_BASTARD_SWORD; + case BASE_ITEM_BATTLEAXE: return FEAT_WEAPON_PROFICIENCY_BATTLEAXE; + case BASE_ITEM_CLUB: return FEAT_WEAPON_PROFICIENCY_CLUB; + case BASE_ITEM_CRAFTED_SCEPTER: return FEAT_WEAPON_PROFICIENCY_CLUB; + case BASE_ITEM_DAGGER: return FEAT_WEAPON_PROFICIENCY_DAGGER; + case BASE_ITEM_DART: return FEAT_WEAPON_PROFICIENCY_DART; + case BASE_ITEM_DIREMACE: return FEAT_WEAPON_PROFICIENCY_DIRE_MACE; + case BASE_ITEM_DOUBLEAXE: return FEAT_WEAPON_PROFICIENCY_DOUBLE_AXE; + case BASE_ITEM_DWARVENWARAXE: return FEAT_WEAPON_PROFICIENCY_DWARVEN_WARAXE; + case BASE_ITEM_GREATAXE: return FEAT_WEAPON_PROFICIENCY_GREATAXE; + case BASE_ITEM_GREATSWORD: return FEAT_WEAPON_PROFICIENCY_GREATSWORD; + case BASE_ITEM_HALBERD: return FEAT_WEAPON_PROFICIENCY_HALBERD; + case BASE_ITEM_HANDAXE: return FEAT_WEAPON_PROFICIENCY_HANDAXE; + case BASE_ITEM_HEAVYCROSSBOW: return FEAT_WEAPON_PROFICIENCY_HEAVY_XBOW; + case BASE_ITEM_HEAVYFLAIL: return FEAT_WEAPON_PROFICIENCY_HEAVY_FLAIL; + case BASE_ITEM_KAMA: return FEAT_WEAPON_PROFICIENCY_KAMA; + case BASE_ITEM_KATANA: return FEAT_WEAPON_PROFICIENCY_KATANA; + case BASE_ITEM_KUKRI: return FEAT_WEAPON_PROFICIENCY_KUKRI; + case BASE_ITEM_LIGHTCROSSBOW: return FEAT_WEAPON_PROFICIENCY_LIGHT_XBOW; + case BASE_ITEM_LIGHTFLAIL: return FEAT_WEAPON_PROFICIENCY_LIGHT_FLAIL; + case BASE_ITEM_LIGHTHAMMER: return FEAT_WEAPON_PROFICIENCY_LIGHT_HAMMER; + case BASE_ITEM_LIGHTMACE: return FEAT_WEAPON_PROFICIENCY_LIGHT_MACE; + case BASE_ITEM_LONGBOW: return FEAT_WEAPON_PROFICIENCY_LONGBOW; + case BASE_ITEM_LONGSWORD: return FEAT_WEAPON_PROFICIENCY_LONGSWORD; + case BASE_ITEM_MORNINGSTAR: return FEAT_WEAPON_PROFICIENCY_MORNINGSTAR; + case BASE_ITEM_QUARTERSTAFF: return FEAT_WEAPON_PROFICIENCY_QUARTERSTAFF; + case BASE_ITEM_MAGICSTAFF: return FEAT_WEAPON_PROFICIENCY_QUARTERSTAFF; + case BASE_ITEM_RAPIER: return FEAT_WEAPON_PROFICIENCY_RAPIER; + case BASE_ITEM_SCIMITAR: return FEAT_WEAPON_PROFICIENCY_SCIMITAR; + case BASE_ITEM_SCYTHE: return FEAT_WEAPON_PROFICIENCY_SCYTHE; + case BASE_ITEM_SHORTBOW: return FEAT_WEAPON_PROFICIENCY_SHORTBOW; + case BASE_ITEM_SHORTSPEAR: return FEAT_WEAPON_PROFICIENCY_SHORTSPEAR; + case BASE_ITEM_SHORTSWORD: return FEAT_WEAPON_PROFICIENCY_SHORTSWORD; + case BASE_ITEM_SHURIKEN: return FEAT_WEAPON_PROFICIENCY_SHURIKEN; + case BASE_ITEM_SICKLE: return FEAT_WEAPON_PROFICIENCY_SICKLE; + case BASE_ITEM_SLING: return FEAT_WEAPON_PROFICIENCY_SLING; + case BASE_ITEM_THROWINGAXE: return FEAT_WEAPON_PROFICIENCY_THROWING_AXE; + case BASE_ITEM_TRIDENT: return FEAT_WEAPON_PROFICIENCY_TRIDENT; + case BASE_ITEM_TWOBLADEDSWORD: return FEAT_WEAPON_PROFICIENCY_TWO_BLADED_SWORD; + case BASE_ITEM_WARHAMMER: return FEAT_WEAPON_PROFICIENCY_WARHAMMER; + case BASE_ITEM_WHIP: return FEAT_WEAPON_PROFICIENCY_WHIP; + + //:: new item types + case BASE_ITEM_DOUBLE_SCIMITAR: return FEAT_WEAPON_PROFICIENCY_DOUBLE_SCIMITAR; + case BASE_ITEM_EAGLE_CLAW: return FEAT_WEAPON_PROFICIENCY_EAGLE_CLAW; + case BASE_ITEM_ELVEN_COURTBLADE: return FEAT_WEAPON_PROFICIENCY_ELVEN_COURTBLADE; + case BASE_ITEM_ELVEN_LIGHTBLADE: return FEAT_WEAPON_PROFICIENCY_ELVEN_LIGHTBLADE; + case BASE_ITEM_ELVEN_THINBLADE: return FEAT_WEAPON_PROFICIENCY_ELVEN_THINBLADE; + case BASE_ITEM_FALCHION: return FEAT_WEAPON_PROFICIENCY_FALCHION; + case BASE_ITEM_GOAD: return FEAT_WEAPON_PROFICIENCY_GOAD; + case BASE_ITEM_HEAVY_MACE: return FEAT_WEAPON_PROFICIENCY_HEAVY_MACE; + case BASE_ITEM_HEAVY_PICK: return FEAT_WEAPON_PROFICIENCY_HEAVY_PICK; + case BASE_ITEM_KATAR: return FEAT_WEAPON_PROFICIENCY_KATAR; + case BASE_ITEM_LIGHT_LANCE: return FEAT_WEAPON_PROFICIENCY_LIGHT_LANCE; + case BASE_ITEM_LIGHT_PICK: return FEAT_WEAPON_PROFICIENCY_LIGHT_PICK; + case BASE_ITEM_MAUL: return FEAT_WEAPON_PROFICIENCY_MAUL; + case BASE_ITEM_NUNCHAKU: return FEAT_WEAPON_PROFICIENCY_NUNCHAKU; + case BASE_ITEM_SAI: return FEAT_WEAPON_PROFICIENCY_SAI; + case BASE_ITEM_SAP: return FEAT_WEAPON_PROFICIENCY_SAP; + } + + return -1; +} + + int GetFocusFeatOfWeaponType(int iWeaponType) { switch(iWeaponType) @@ -226,6 +305,7 @@ int GetFocusFeatOfWeaponType(int iWeaponType) case BASE_ITEM_BASTARDSWORD: return FEAT_WEAPON_FOCUS_BASTARD_SWORD; case BASE_ITEM_BATTLEAXE: return FEAT_WEAPON_FOCUS_BATTLE_AXE; case BASE_ITEM_CLUB: return FEAT_WEAPON_FOCUS_CLUB; + case BASE_ITEM_CRAFTED_SCEPTER: return FEAT_WEAPON_FOCUS_CLUB; case BASE_ITEM_DAGGER: return FEAT_WEAPON_FOCUS_DAGGER; case BASE_ITEM_DART: return FEAT_WEAPON_FOCUS_DART; case BASE_ITEM_DIREMACE: return FEAT_WEAPON_FOCUS_DIRE_MACE; @@ -297,6 +377,7 @@ int GetSpecializationFeatOfWeaponType(int iWeaponType) case BASE_ITEM_BASTARDSWORD: return FEAT_WEAPON_SPECIALIZATION_BASTARD_SWORD; case BASE_ITEM_BATTLEAXE: return FEAT_WEAPON_SPECIALIZATION_BATTLE_AXE; case BASE_ITEM_CLUB: return FEAT_WEAPON_SPECIALIZATION_CLUB; + case BASE_ITEM_CRAFTED_SCEPTER: return FEAT_WEAPON_SPECIALIZATION_CLUB; case BASE_ITEM_DAGGER: return FEAT_WEAPON_SPECIALIZATION_DAGGER; case BASE_ITEM_DART: return FEAT_WEAPON_SPECIALIZATION_DART; case BASE_ITEM_DIREMACE: return FEAT_WEAPON_SPECIALIZATION_DIRE_MACE; @@ -368,6 +449,7 @@ int GetEpicFocusFeatOfWeaponType(int iWeaponType) case BASE_ITEM_BASTARDSWORD: return FEAT_EPIC_WEAPON_FOCUS_BASTARDSWORD; case BASE_ITEM_BATTLEAXE: return FEAT_EPIC_WEAPON_FOCUS_BATTLEAXE; case BASE_ITEM_CLUB: return FEAT_EPIC_WEAPON_FOCUS_CLUB; + case BASE_ITEM_CRAFTED_SCEPTER: return FEAT_EPIC_WEAPON_FOCUS_CLUB; case BASE_ITEM_DAGGER: return FEAT_EPIC_WEAPON_FOCUS_DAGGER; case BASE_ITEM_DART: return FEAT_EPIC_WEAPON_FOCUS_DART; case BASE_ITEM_DIREMACE: return FEAT_EPIC_WEAPON_FOCUS_DIREMACE; @@ -439,6 +521,7 @@ int GetEpicSpecializationFeatOfWeaponType(int iWeaponType) case BASE_ITEM_BASTARDSWORD: return FEAT_EPIC_WEAPON_SPECIALIZATION_BASTARDSWORD; case BASE_ITEM_BATTLEAXE: return FEAT_EPIC_WEAPON_SPECIALIZATION_BATTLEAXE; case BASE_ITEM_CLUB: return FEAT_EPIC_WEAPON_SPECIALIZATION_CLUB; + case BASE_ITEM_CRAFTED_SCEPTER: return FEAT_EPIC_WEAPON_SPECIALIZATION_CLUB; case BASE_ITEM_DAGGER: return FEAT_EPIC_WEAPON_SPECIALIZATION_DAGGER; case BASE_ITEM_DART: return FEAT_EPIC_WEAPON_SPECIALIZATION_DART; case BASE_ITEM_DIREMACE: return FEAT_EPIC_WEAPON_SPECIALIZATION_DIREMACE; @@ -510,6 +593,7 @@ int GetImprovedCriticalFeatOfWeaponType(int iWeaponType) case BASE_ITEM_BASTARDSWORD: return FEAT_IMPROVED_CRITICAL_BASTARD_SWORD; case BASE_ITEM_BATTLEAXE: return FEAT_IMPROVED_CRITICAL_BATTLE_AXE; case BASE_ITEM_CLUB: return FEAT_IMPROVED_CRITICAL_CLUB; + case BASE_ITEM_CRAFTED_SCEPTER: return FEAT_IMPROVED_CRITICAL_CLUB; case BASE_ITEM_DAGGER: return FEAT_IMPROVED_CRITICAL_DAGGER; case BASE_ITEM_DART: return FEAT_IMPROVED_CRITICAL_DART; case BASE_ITEM_DIREMACE: return FEAT_IMPROVED_CRITICAL_DIRE_MACE; @@ -581,6 +665,7 @@ int GetOverwhelmingCriticalFeatOfWeaponType(int iWeaponType) case BASE_ITEM_BASTARDSWORD: return FEAT_EPIC_OVERWHELMING_CRITICAL_BASTARDSWORD; case BASE_ITEM_BATTLEAXE: return FEAT_EPIC_OVERWHELMING_CRITICAL_BATTLEAXE; case BASE_ITEM_CLUB: return FEAT_EPIC_OVERWHELMING_CRITICAL_CLUB; + case BASE_ITEM_CRAFTED_SCEPTER: return FEAT_EPIC_OVERWHELMING_CRITICAL_CLUB; case BASE_ITEM_DAGGER: return FEAT_EPIC_OVERWHELMING_CRITICAL_DAGGER; case BASE_ITEM_DART: return FEAT_EPIC_OVERWHELMING_CRITICAL_DART; case BASE_ITEM_DIREMACE: return FEAT_EPIC_OVERWHELMING_CRITICAL_DIREMACE; @@ -652,6 +737,7 @@ int GetDevastatingCriticalFeatOfWeaponType(int iWeaponType) case BASE_ITEM_BASTARDSWORD: return FEAT_EPIC_DEVASTATING_CRITICAL_BASTARDSWORD; case BASE_ITEM_BATTLEAXE: return FEAT_EPIC_DEVASTATING_CRITICAL_BATTLEAXE; case BASE_ITEM_CLUB: return FEAT_EPIC_DEVASTATING_CRITICAL_CLUB; + case BASE_ITEM_CRAFTED_SCEPTER: return FEAT_EPIC_DEVASTATING_CRITICAL_CLUB; case BASE_ITEM_DAGGER: return FEAT_EPIC_DEVASTATING_CRITICAL_DAGGER; case BASE_ITEM_DART: return FEAT_EPIC_DEVASTATING_CRITICAL_DART; case BASE_ITEM_DIREMACE: return FEAT_EPIC_DEVASTATING_CRITICAL_DIREMACE; @@ -718,6 +804,7 @@ int GetWeaponOfChoiceFeatOfWeaponType(int iWeaponType) case BASE_ITEM_BASTARDSWORD: return FEAT_WEAPON_OF_CHOICE_BASTARDSWORD; case BASE_ITEM_BATTLEAXE: return FEAT_WEAPON_OF_CHOICE_BATTLEAXE; case BASE_ITEM_CLUB: return FEAT_WEAPON_OF_CHOICE_CLUB; + case BASE_ITEM_CRAFTED_SCEPTER: return FEAT_WEAPON_OF_CHOICE_CLUB; case BASE_ITEM_DAGGER: return FEAT_WEAPON_OF_CHOICE_DAGGER; case BASE_ITEM_DIREMACE: return FEAT_WEAPON_OF_CHOICE_DIREMACE; case BASE_ITEM_DOUBLEAXE: return FEAT_WEAPON_OF_CHOICE_DOUBLEAXE; @@ -795,7 +882,7 @@ int GetWeaponSize(object oWeapon) case BASE_ITEM_GREATAXE: case BASE_ITEM_HEAVYFLAIL: case BASE_ITEM_QUARTERSTAFF: - case BASE_ITEM_MAGICSTAFF: + //case BASE_ITEM_MAGICSTAFF: case BASE_ITEM_SCYTHE: case BASE_ITEM_SHORTSPEAR: case BASE_ITEM_ELVEN_COURTBLADE: @@ -832,7 +919,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_MAGICSTAFF: case BASE_ITEM_SCYTHE: case BASE_ITEM_SHORTSPEAR: case BASE_ITEM_ELVEN_COURTBLADE: @@ -844,4 +931,6 @@ int PRCLargeWeaponCheck(int iBaseType, int nSize) } } return sTest != "" && sTest != IntToString(nSize); -} \ No newline at end of file +} + +//::void main(){} \ No newline at end of file diff --git a/src/include/prc_inc_function.nss b/src/include/prc_inc_function.nss index 93f7aa1..a3b24e5 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; @@ -429,7 +431,7 @@ void EvalPRCFeats(object oPC) ExecuteScript("moi_events", oPC); if (GetIsBinder(oPC)) - ExecuteScript("bnd_events", oPC); + ExecuteScript("bnd_events", oPC); // check if character with crafting feat has appropriate base item in her inventory // x - moved from prc_onhb_indiv.nss @@ -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_hextor.nss b/src/include/prc_inc_hextor.nss index affe360..531ba9a 100644 --- a/src/include/prc_inc_hextor.nss +++ b/src/include/prc_inc_hextor.nss @@ -1,4 +1,6 @@ #include "prc_feat_const" +#include "inc_item_props" +#include "prc_inc_spells" const string BRUTAL_STRIKE_MODE_VAR = "PRC_BRUTAL_STRIKE_MODE"; diff --git a/src/include/prc_inc_itmrstr.nss b/src/include/prc_inc_itmrstr.nss index 156f530..dd852f0 100644 --- a/src/include/prc_inc_itmrstr.nss +++ b/src/include/prc_inc_itmrstr.nss @@ -36,6 +36,8 @@ void CheckForPnPHolyAvenger(object oItem); #include "inc_utility" #include "prc_inc_newip" +#include "prc_inc_castlvl" +#include "inc_newspellbook" ////////////////////////////////////////////////// diff --git a/src/include/prc_inc_json.nss b/src/include/prc_inc_json.nss new file mode 100644 index 0000000..f15c675 --- /dev/null +++ b/src/include/prc_inc_json.nss @@ -0,0 +1,1993 @@ +//::////////////////////////////////////////////// +//:: ;-. ,-. ,-. ,-. +//:: | ) | ) / ( ) +//:: |-' |-< | ;-: +//:: | | \ \ ( ) +//:: ' ' ' `-' `-' +//::////////////////////////////////////////////// +//:: +/* + Library for json related functions. + +*/ +//:: +//::////////////////////////////////////////////// +//:: Script: prc_inc_json.nss +//:: Author: Jaysyn +//:: Created: 2025-08-14 12:52:32 +//::////////////////////////////////////////////// +#include "nw_inc_gff" +#include "inc_debug" +#include "prc_inc_racial" +#include "prc_inc_nwscript" +#include "prc_inc_spells" +#include "prc_inc_util" +#include "prc_inc_fork" +#include "prc_inc_natweap" + +//:: Get a random General feat. +void ApplyParagonBonusFeat(object oCreature, int iFeat); + +//::---------------------------------------------| +//:: Helper functions | +//::---------------------------------------------| +int GetHealerCompanionBonus(int nHealerLvl) +{ + // No bonus before 12th level + if (nHealerLvl < 12) + return 0; + + int nBonus = 0; + + // Non-epic improvements: 12, 15, 18, 21 (every 3 levels) + if (nHealerLvl >= 12) + { + int nPreEpicIntervals = ( (nHealerLvl < 21) ? (nHealerLvl - 12) : (21 - 12) ) / 3; + nBonus += 2 + (nPreEpicIntervals * 2); + } + + // Epic improvements: 24, 28, 32, 36... (every 4 levels) + if (nHealerLvl >= 24) + { + int nEpicIntervals = (nHealerLvl - 24) / 4; + // First epic improvement is +2 at 24 + nBonus += 2 + (nEpicIntervals * 2); + } + + return nBonus; +} + +/* int GetHealerCompanionBonus(int nHealerLvl) +{ + if (nHealerLvl < 12) + return 0; + + // Shift so that 12–14 yields interval 0 + int nIntervals = (nHealerLvl - 12) / 3; + + return 2 + (nIntervals * 2); +} */ + + +//:: Function to calculate the maximum possible hitpoints for oCreature +int GetMaxPossibleHP(object oCreature) +{ + int nMaxHP = 0; // Stores the total maximum hitpoints + int i = 1; // Initialize position for class index + int nConb = GetAbilityModifier(ABILITY_CONSTITUTION, oCreature); + int nRacial = MyPRCGetRacialType(oCreature); + int nSize = PRCGetCreatureSize(oCreature); + + // Loop through each class position the creature may have, checking each class in turn + while (TRUE) + { + // Get the class ID at position i + int nClassID = GetClassByPosition(i, oCreature); + + // If class is invalid (no more classes to check), break out of loop + if (nClassID == CLASS_TYPE_INVALID) + break; + + // Get the number of levels in this class + int nClassLevels = GetLevelByClass(nClassID, oCreature); + + // Get the row index of the class in classes.2da by using class ID as the row index + int nHitDie = StringToInt(Get2DAString("classes", "HitDie", nClassID)); + + // Add maximum HP for this class (Hit Die * number of levels in this class) + nMaxHP += nClassLevels * nHitDie; + + // Move to the next class position + i++; + } + + if(nRacial == RACIAL_TYPE_CONSTRUCT || nRacial == RACIAL_TYPE_UNDEAD) + { + nConb = 0; + } + + nMaxHP += nConb * GetHitDice(oCreature); + + if(nRacial == RACIAL_TYPE_CONSTRUCT) + { + if(nSize == CREATURE_SIZE_FINE) nMaxHP += 0; + if(nSize == CREATURE_SIZE_DIMINUTIVE) nMaxHP += 0; + if(nSize == CREATURE_SIZE_TINY) nMaxHP += 0; + if(nSize == CREATURE_SIZE_SMALL) nMaxHP += 10; + if(nSize == CREATURE_SIZE_MEDIUM) nMaxHP += 20; + if(nSize == CREATURE_SIZE_LARGE) nMaxHP += 30; + if(nSize == CREATURE_SIZE_HUGE) nMaxHP += 40; + if(nSize == CREATURE_SIZE_GARGANTUAN) nMaxHP += 60; + } + + return nMaxHP; +} + +// Returns how many feats a creature should gain when its HD increases +int CalculateFeatsFromHD(int nOriginalHD, int nNewHD) +{ + // HD increase + int nHDIncrease = nNewHD - nOriginalHD; + + if (nHDIncrease <= 0) + return 0; // No new feats if HD did not increase + + // D&D 3E: 1 feat per 3 HD + int nBonusFeats = nHDIncrease / 3; + + return nBonusFeats; +} + +// Returns how many stat boosts a creature needs based on its HD +int GetStatBoostsFromHD(int nCreatureHD, int nModiferCap) +{ + // Make sure we don't get negative boosts + int nBoosts = (40 - nCreatureHD) / 4; + if (nBoosts < 0) + { + nBoosts = 0; + } + return nBoosts; +} + +// Struct to hold size modifiers +struct SizeModifiers +{ + int strMod; + int dexMod; + int conMod; + int naturalAC; + int attackBonus; + int dexSkillMod; +}; + +//:: Returns ability mod for score +int GetAbilityModFromValue(int nAbilityValue) +{ + int nMod = (nAbilityValue - 10) / 2; + + // Adjust if below 10 and odd + if (nAbilityValue < 10 && (nAbilityValue % 2) != 0) + { + nMod = nMod - 1; + } + return nMod; +} + +//:: Get a random General feat. +void PickParagonBonusFeat(object oCreature) +{ +//:: Paragon creatures get a +15 to all ability scores, +//:: so can always meet feat pre-reqs. + +//:: Detect spellcasting classes (FOR FUTURE USE) + int i; + for (i = 1; i <= 8; i++) + { + if (GetIsArcaneClass(GetClassByPosition(i, oCreature))) + { + SetLocalInt(oCreature, "ParagonArcaneCaster", 0); + } + if (GetIsDivineClass(GetClassByPosition(i, oCreature))) + { + SetLocalInt(oCreature, "ParagonDivineCaster", 0); + } + } + switch (Random(18)) + { + //:: Dodge -> Mobility -> Spring Attack + case 0: + { + int iDodge = GetHasFeat(FEAT_DODGE, oCreature); + int iMobility = GetHasFeat(FEAT_MOBILITY, oCreature); + int iSpringAttack = GetHasFeat(FEAT_SPRING_ATTACK, oCreature); + + //:: Grant only the first missing feat in the chain + if (iDodge == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_DODGE); + } + else if (iMobility == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_MOBILITY); + } + else if (iSpringAttack == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_SPRING_ATTACK); + } + } + break; + //:: Power Attack -> Cleave -> Imp Power Attack -> Great Cleave + case 1: + { + int iPower = GetHasFeat(FEAT_POWER_ATTACK, oCreature); + int iCleave = GetHasFeat(FEAT_CLEAVE, oCreature); + int iImpPower = GetHasFeat(FEAT_IMPROVED_POWER_ATTACK, oCreature); + int iGrCleave = GetHasFeat(FEAT_GREAT_CLEAVE, oCreature); + + //:: Grant only the first missing feat in the chain + if (iPower == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_POWER_ATTACK); + } + else if (iCleave == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_CLEAVE); + } + else if (iImpPower == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_POWER_ATTACK); + } + else if (iGrCleave == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_GREAT_CLEAVE); + } + } + break; + //:: Expertise -> Imp Expertise -> Whirlwind Attack -> Imp Whirlwind Attack + case 2: + { + int iEx = GetHasFeat(FEAT_EXPERTISE, oCreature); + int iImpEx = GetHasFeat(FEAT_IMPROVED_EXPERTISE, oCreature); + int iWhirl = GetHasFeat(FEAT_WHIRLWIND_ATTACK, oCreature); + int iImpWhirl = GetHasFeat(FEAT_IMPROVED_WHIRLWIND, oCreature); + + //:: Grant only the first missing feat in the chain + if (iEx == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_EXPERTISE); + } + else if (iImpEx == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_EXPERTISE); + } + else if (iWhirl == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_WHIRLWIND_ATTACK); + } + else if (iImpWhirl == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_WHIRLWIND); + } + } + break; + //:: Disarm -> Expertise -> Improved Disarm -> Imp Expertise + case 3: + { + int iDisarm = GetHasFeat(FEAT_DISARM, oCreature); + int iEx = GetHasFeat(FEAT_EXPERTISE, oCreature); + int iImpDisarm = GetHasFeat(FEAT_IMPROVED_DISARM, oCreature); + int iImpEx = GetHasFeat(FEAT_IMPROVED_EXPERTISE, oCreature); + + //:: Grant only the first missing feat in the chain + if (iDisarm == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_DISARM); + } + else if (iEx == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_EXPERTISE); + } + else if (iImpDisarm == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_DISARM); + } + else if (iImpEx == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_EXPERTISE); + } + } + break; + //:: Toughness + case 4: + { + ApplyParagonBonusFeat(oCreature, FEAT_TOUGHNESS); + } + break; + //:: Great Fortitude + case 5: + { + ApplyParagonBonusFeat(oCreature, FEAT_GREAT_FORTITUDE); + } + break; + //:: Lightining Reflexes + case 6: + { + ApplyParagonBonusFeat(oCreature, FEAT_LIGHTNING_REFLEXES); + } + break; + //:: Iron Will -> Unnatural Will + case 7: + { + int iIronWill = GetHasFeat(FEAT_IRON_WILL, oCreature); + int iUnnaturalWill = GetHasFeat(FEAT_UNNATURAL_WILL, oCreature); + + //:: Grant only the first missing feat in the chain + if (iIronWill == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_IRON_WILL); + } + else if (iUnnaturalWill == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_UNNATURAL_WILL); + } + } + break; + //:: Blind-Fight + case 8: + { + ApplyParagonBonusFeat(oCreature, FEAT_BLIND_FIGHT); + } + break; + //:: Improved Initiative + case 9: + { + ApplyParagonBonusFeat(oCreature, FEAT_IMPROVED_INITIATIVE); + } + break; + //:: Alertness + case 10: + { + ApplyParagonBonusFeat(oCreature, FEAT_ALERTNESS); + } + break; + //:: Blooded + case 11: + { + ApplyParagonBonusFeat(oCreature, FEAT_BLOODED); + } + break; + //:: Side-step Charge + case 12: + { + ApplyParagonBonusFeat(oCreature, FEAT_SIDESTEP_CHARGE); + } + break; + //:: Thug + case 13: + { + ApplyParagonBonusFeat(oCreature, FEAT_THUG); + } + break; + //:: Dive for Cover + case 14: + { + ApplyParagonBonusFeat(oCreature, FEAT_DIVE_FOR_COVER); + } + break; + //:: Endurance -> Strong Stomach + case 15: + { + int iEndurance = GetHasFeat(FEAT_ENDURANCE, oCreature); + int iStrStomach = GetHasFeat(FEAT_STRONG_STOMACH, oCreature); + + //:: Grant only the first missing feat in the chain + if (iEndurance == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_ENDURANCE); + } + else if (iStrStomach == 0) + { + ApplyParagonBonusFeat(oCreature, FEAT_STRONG_STOMACH); + } + } + break; + //:: Resist Disease + case 16: + { + ApplyParagonBonusFeat(oCreature, FEAT_RESIST_DISEASE); + } + break; + //:: Resist Poison + case 17: + { + ApplyParagonBonusFeat(oCreature, FEAT_RESIST_POISON); + } + break; + } +} + +//:: Check & apply the feat using EffectBonusFeat if it +//:: doesn't exist on the creature already +void ApplyParagonBonusFeat(object oCreature, int iFeat) +{ + // If the creature does not already have the feat, apply it + if (!GetHasFeat(iFeat, oCreature)) + { + effect eFeat = EffectBonusFeat(iFeat); + effect eLink = UnyieldingEffect(eFeat); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, eLink, oCreature); + } + else + { + DelayCommand(0.0f, PickParagonBonusFeat(oCreature)); + } +} + +//:: Apply Paragon effects to a non-PC creature +void ApplyParagonEffects(object oCreature, int nBaseHD, int nBaseCR) +{ +//:: Declare major variables + int nNewCR; + + effect eParagon; + +//:: Set maximum hit points for each HD + int nParagonHP = (GetMaxPossibleHP(oCreature) + (nBaseHD * GetAbilityModifier(ABILITY_CONSTITUTION, oCreature))); + SetCurrentHitPoints(oCreature, nParagonHP); + +//:: Tripling the speed for all movement types + eParagon = EffectLinkEffects(eParagon, EffectMovementSpeedIncrease(300)); + +//:: +25 luck bonus on all attack rolls + eParagon = EffectLinkEffects(eParagon, EffectAttackIncrease(25)); + +//:: +20 luck bonus on damage rolls for melee and thrown ranged attacks + eParagon = EffectLinkEffects(eParagon, EffectDamageIncrease(20)); + +//:: AC Bonuses: +12 insight, +12 luck + eParagon = EffectLinkEffects(eParagon, EffectACIncrease(12, AC_DODGE_BONUS)); + eParagon = EffectLinkEffects(eParagon, EffectACIncrease(12, AC_DEFLECTION_BONUS)); + +//:: Boost caster & SLA level by 15 + SetLocalInt(oCreature, PRC_CASTERLEVEL_ADJUSTMENT, 15); + +//:: Fire and cold resistance 10, or keep the higher existing resistance if applicable + eParagon = EffectLinkEffects(eParagon, EffectDamageResistance(DAMAGE_TYPE_FIRE, 10)); + eParagon = EffectLinkEffects(eParagon, EffectDamageResistance(DAMAGE_TYPE_COLD, 10)); + +//:: Damage Reduction 20/epic or retain existing DR if higher + eParagon = EffectLinkEffects(eParagon, EffectDamageReduction(20, DAMAGE_POWER_ENERGY)); + +//:: Spell Resistance equal to CR +10, or retain existing SR if higher + int iExSR = GetSpellResistance(oCreature); + int nSpellResistance; + + if (iExSR < nBaseCR + 10) + { + nSpellResistance = nBaseCR + 10; + } + else + { + nSpellResistance = 0; + } + + eParagon = EffectLinkEffects(eParagon, EffectSpellResistanceIncrease(nSpellResistance)); + +//:: Fast Healing 20 + eParagon = EffectLinkEffects(eParagon, EffectRegenerate(20, 6.0f)); + +//:: Saving Throws: +10 insight bonus on all saving throws + eParagon = EffectLinkEffects(eParagon, EffectSavingThrowIncrease(SAVING_THROW_ALL, 10)); + +//:: Skills: +10 competence bonus to all skill checks + int nSkillID = 0; + + while (TRUE) + { + //:: Get & check skill + string sSkillLabel = Get2DACache("skills", "Label", nSkillID); + + //:: Break when out of skills + if (sSkillLabel == "") + break; + + //:: Apply the skill increase effect for the current skill + eParagon = EffectLinkEffects(eParagon, EffectSkillIncrease(nSkillID, 10)); + + + //:: Move to the next skill ID + nSkillID++; + } + +//:: Two free general feats. + PickParagonBonusFeat(oCreature); + PickParagonBonusFeat(oCreature); + + eParagon = UnyieldingEffect(eParagon); + + ApplyEffectToObject(DURATION_TYPE_PERMANENT, eParagon, oCreature); +} + +// Build and return all effects for the Celestial Template +effect CelestialTemplateEffects(int nHD) +{ + int nResist; + int nDRAmount; + int nDRBypass; + + // ------------------------- + // Elemental Resistances + // ------------------------- + // 1–7 HD = 5 + // 8+ HD = 10 + if (nHD >= 8) + { + nResist = 10; + } + else + { + nResist = 5; + } + + // ------------------------- + // Damage Reduction + // ------------------------- + // 1–3 HD = none + // 4–11 HD = 5/magic + // 12+ HD = 10/magic + if (nHD >= 12) + { + nDRAmount = 10; + nDRBypass = DAMAGE_POWER_PLUS_ONE; // DR 10/magic + } + else if (nHD >= 4) + { + nDRAmount = 5; + nDRBypass = DAMAGE_POWER_PLUS_ONE; // DR 5/magic + } + else + { + nDRAmount = 0; + nDRBypass = 0; // no DR + } + + // ------------------------- + // Build Effects + // ------------------------- + effect eEffects; + effect eRes; + + // Acid + eRes = EffectDamageResistance(DAMAGE_TYPE_ACID, nResist, 0); + eEffects = eRes; + + // Cold + eRes = EffectDamageResistance(DAMAGE_TYPE_COLD, nResist, 0); + eEffects = EffectLinkEffects(eEffects, eRes); + + // Electricity + eRes = EffectDamageResistance(DAMAGE_TYPE_ELECTRICAL, nResist, 0); + eEffects = EffectLinkEffects(eEffects, eRes); + + // DR if any + if (nDRAmount > 0) + { + effect eDR = EffectDamageReduction(nDRAmount, nDRBypass, 0); + eEffects = EffectLinkEffects(eEffects, eDR); + } + + eEffects = UnyieldingEffect(eEffects); + + return eEffects; +} + + + +void ReallyEquipItemInSlot(object oNPC, object oItem, int nSlot) +{ + if (GetItemInSlot(nSlot) != oItem) + { + //ClearAllActions(); + AssignCommand(oNPC, ActionEquipItem(oItem, nSlot)); + DelayCommand(0.5, ReallyEquipItemInSlot(oNPC, oItem, nSlot)); + } +} + +// Get the size of a JSON array +int GetJsonArraySize(json jArray) +{ + int iSize = 0; + while (JsonArrayGet(jArray, iSize) != JsonNull()) + { + iSize++; + } + return iSize; +} + +int CheckForWeapon(object oCreature) +{ + if (GetIsWeapon(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCreature)) == 1 || GetIsWeapon(GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oCreature)) == 1) + { + // oCreature has a weapon in at least one hand + return TRUE; + } + else + { + // oCreature doesn't have a weapon in either hand + return FALSE; + } +} + +//:: Adds Psuedonatural resistances & DR. +void ApplyPseudonaturalEffects(object oCreature) +{ + if(!GetIsObjectValid(oCreature)) return; + + int nHD = GetHitDice(oCreature); + if(DEBUG) DoDebug("prc_inc_json >> ApplyPseudonaturalEffects: nHD is: "+IntToString(nHD)+"."); + // ------------------------- + // Spell Resistance + // SR = 10 + HD (max 25) + // ------------------------- + int nSR = 10 + nHD; + if(nSR > 25) nSR = 25; + + effect eSR = EffectSpellResistanceIncrease(nSR); + eSR = TagEffect(eSR, "PSEUDO_SR"); + eSR = EffectLinkEffects(eSR, UnyieldingEffect(eSR)); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, eSR, oCreature); + + // ------------------------- + // Acid/Electricity Resistance + // Reference Table: + // HD 1–3 : Resist 5 + // HD 4–7 : Resist 5 + // HD 8–11 : Resist 10 + // HD >=12 : Resist 15 + // ------------------------- + int nResist; + + if(nHD <= 7) nResist = 5; + else if(nHD <=11) nResist = 10; + else nResist = 15; + + effect eResAcid = EffectDamageResistance(DAMAGE_TYPE_ACID, nResist); + eResAcid = TagEffect(eResAcid, "PSEUDO_RES_ACID"); + eResAcid = EffectLinkEffects(eResAcid, UnyieldingEffect(eResAcid)); + + effect eResElec = EffectDamageResistance(DAMAGE_TYPE_ELECTRICAL, nResist); + eResElec = TagEffect(eResElec, "PSEUDO_RES_ELEC"); + eResElec = EffectLinkEffects(eResElec, UnyieldingEffect(eResElec)); + + ApplyEffectToObject(DURATION_TYPE_PERMANENT, eResAcid, oCreature); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, eResElec, oCreature); + + // ------------------------- + // Damage Reduction + // Reference Table: + // HD 1–3 : none + // HD 4–7 : DR 5 / magic + // HD 8–11 : DR 5 / magic + // HD >=12 : DR 10 / magic + // ------------------------- + + int nDR; + if(nHD <= 3) { nDR = 0; } + else if(nHD <= 11) { nDR = 5; } + else { nDR = 10; } + + effect eDR = EffectDamageReduction(nDR, DAMAGE_POWER_PLUS_ONE, 0, FALSE); + eDR = TagEffect(eDR, "PSEUDO_DR_MAGIC"); + eDR = EffectLinkEffects(eDR, UnyieldingEffect(eDR)); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, eDR, oCreature); +} + + +//::---------------------------------------------| +//:: JSON functions | +//::---------------------------------------------| + +//:: Returns the Constitution value from a GFF creature UTC +int json_GetCONValue(json jCreature) +{ + int nCon = 0; // default if missing + + // Check if the Con field exists + if (GffGetFieldExists(jCreature, "Con")) + { + nCon = JsonGetInt(GffGetByte(jCreature, "Con")); + } + + return nCon; +} + +//:: Returns the Challenge Rating from a GFF creature UTC +float json_GetChallengeRating(json jCreature) +{ + float fCR = 0.25; // default if missing + + if (GffGetFieldExists(jCreature, "ChallengeRating")) + { + json jCR = GffGetFloat(jCreature, "ChallengeRating"); + if (jCR != JsonNull()) + { + fCR = JsonGetFloat(jCR); + } + } + + return fCR; +} + +//:: Returns the integer value of a VarTable entry named sVarName, or 0 if not found. +int json_GetLocalIntFromVarTable(json jCreature, string sVarName) +{ + json jVarTable = GffGetList(jCreature, "VarTable"); + if (jVarTable == JsonNull()) + return 0; + + int nCount = JsonGetLength(jVarTable); + int i; + for (i = 0; i < nCount; i++) + { + json jEntry = JsonArrayGet(jVarTable, i); + if (jEntry == JsonNull()) continue; + + // Get the Name field using GFF functions + json jName = GffGetString(jEntry, "Name"); + if (jName == JsonNull()) continue; + string sName = JsonGetString(jName); + + if (sName == sVarName) + { + // Get the Type field to verify it's an integer + json jType = GffGetDword(jEntry, "Type"); + if (jType != JsonNull()) + { + int nType = JsonGetInt(jType); + if (nType == 1) // Type 1 = integer + { + // Get the Value field using GFF functions + json jValue = GffGetInt(jEntry, "Value"); + if (jValue == JsonNull()) return 0; + return JsonGetInt(jValue); + } + } + } + } + + return 0; +} + +//:: Returns the string value of a VarTable entry named sVarName, or "" if not found. +string json_GetLocalStringFromVarTable(json jCreature, string sVarName) +{ + json jVarTable = GffGetList(jCreature, "VarTable"); + if (jVarTable == JsonNull()) + return ""; + + int nCount = JsonGetLength(jVarTable); + int i; + for (i = 0; i < nCount; i++) + { + json jEntry = JsonArrayGet(jVarTable, i); + if (jEntry == JsonNull()) continue; + + // Get the Name field using GFF functions + json jName = GffGetString(jEntry, "Name"); + if (jName == JsonNull()) continue; + string sName = JsonGetString(jName); + + if (sName == sVarName) + { + // Get the Type field to verify it's a string + json jType = GffGetDword(jEntry, "Type"); + if (jType != JsonNull()) + { + int nType = JsonGetInt(jType); + if (nType == 3) // Type 3 = string + { + // Get the Value field using GFF functions + json jValue = GffGetString(jEntry, "Value"); + if (jValue == JsonNull()) return ""; + return JsonGetString(jValue); + } + } + } + } + + return ""; +} + +//:: Returns the total Hit Dice from a JSON'd creature GFF. +int json_GetCreatureHD(json jCreature) +{ + int nHD = 0; + + json jClasses = GffGetList(jCreature, "ClassList"); + if (jClasses == JsonNull()) + return 0; + + int nCount = JsonGetLength(jClasses); + int i; + for (i = 0; i < nCount; i = i + 1) + { + json jClass = JsonArrayGet(jClasses, i); + if (jClass == JsonNull()) + continue; + + json jLevel = GffGetShort(jClass, "ClassLevel"); // Use GffGetShort, not GffGetField + if (jLevel != JsonNull()) + { + int nLevel = JsonGetInt(jLevel); + nHD += nLevel; + } + } + + if (nHD <= 0) nHD = 1; + return nHD; +} + +json json_RecalcMaxHP(json jCreature, int iHitDieValue) +{ + int iHD = json_GetCreatureHD(jCreature); + + //:: Retrieve the RacialType field + json jRacialTypeField = JsonObjectGet(jCreature, "Race"); + int nRacialType = JsonGetInt(jRacialTypeField); + + //:: Retrieve the CreatureSize from the creature appearance field + json jAppearanceField = JsonObjectGet(jCreature, "Appearance_Type"); + int nAppearance = JsonGetInt(jAppearanceField); + + int nSize = StringToInt(Get2DAString("appearance", "SizeCategory", nAppearance)); + + //CEP adds other sizes, take them into account too + if(nSize == 20) + nSize = CREATURE_SIZE_DIMINUTIVE; + else if(nSize == 21) + nSize = CREATURE_SIZE_FINE; + else if(nSize == 22) + nSize = CREATURE_SIZE_GARGANTUAN; + else if(nSize == 23) + nSize = CREATURE_SIZE_COLOSSAL; + + int iNewMaxHP = (iHitDieValue * iHD); + + if(nRacialType == RACIAL_TYPE_CONSTRUCT) + { + if(nSize == CREATURE_SIZE_FINE) iNewMaxHP += 0; + if(nSize == CREATURE_SIZE_DIMINUTIVE) iNewMaxHP += 0; + if(nSize == CREATURE_SIZE_TINY) iNewMaxHP += 0; + if(nSize == CREATURE_SIZE_SMALL) iNewMaxHP += 10; + if(nSize == CREATURE_SIZE_MEDIUM) iNewMaxHP += 20; + if(nSize == CREATURE_SIZE_LARGE) iNewMaxHP += 30; + if(nSize == CREATURE_SIZE_HUGE) iNewMaxHP += 40; + if(nSize == CREATURE_SIZE_GARGANTUAN) iNewMaxHP += 60; + } + + if(DEBUG) DoDebug("prc_inc_json >> json_RecalcMaxHP | New MaxHP is: "+IntToString(iNewMaxHP)+ "."); + + jCreature = GffReplaceShort(jCreature, "MaxHitPoints", iNewMaxHP); + jCreature = GffReplaceShort(jCreature, "CurrentHitPoints", iNewMaxHP); + jCreature = GffReplaceShort(jCreature, "HitPoints", iNewMaxHP); + +/* SendMessageToPC(GetFirstPC(), "HD = " + IntToString(iHD)); + SendMessageToPC(GetFirstPC(), "HitDieValue = " + IntToString(iHitDieValue)); + SendMessageToPC(GetFirstPC(), "CON = " + IntToString(iCON)); + SendMessageToPC(GetFirstPC(), "Mod = " + IntToString(iMod)); + SendMessageToPC(GetFirstPC(), "New HP = " + IntToString(iNewMaxHP)); */ + + return jCreature; +} + +//:: Reads ABILITY_TO_INCREASE from creature's VarTable and applies stat boosts based on increased HD +json json_ApplyAbilityBoostFromHD(json jCreature, int nOriginalHD) +{ + if (jCreature == JsonNull()) + return jCreature; + + // Get the ability to increase from VarTable + int nAbilityToIncrease = json_GetLocalIntFromVarTable(jCreature, "ABILITY_TO_INCREASE"); + if (nAbilityToIncrease < 0 || nAbilityToIncrease > 5) + { + DoDebug("json_ApplyAbilityBoostFromHD: Invalid ABILITY_TO_INCREASE value: " + IntToString(nAbilityToIncrease)); + return jCreature; // Invalid ability index + } + + // Calculate total current HD from ClassList + json jClassList = GffGetList(jCreature, "ClassList"); + if (jClassList == JsonNull()) + { + DoDebug("json_ApplyAbilityBoostFromHD: Failed to get ClassList"); + return jCreature; + } + + int nCurrentTotalHD = 0; + int nClassCount = JsonGetLength(jClassList); + int i; + + for (i = 0; i < nClassCount; i++) + { + json jClass = JsonArrayGet(jClassList, i); + if (jClass != JsonNull()) + { + json jClassLevel = GffGetShort(jClass, "ClassLevel"); + if (jClassLevel != JsonNull()) + { + nCurrentTotalHD += JsonGetInt(jClassLevel); + } + } + } + + if(DEBUG) DoDebug("prc_inc_json >> json_ApplyAbilityBoostFromHD: nCurrentTotalHD = "+IntToString(nCurrentTotalHD)+"."); + + if (nCurrentTotalHD <= 0) + { + DoDebug("json_ApplyAbilityBoostFromHD: No valid Hit Dice found"); + return jCreature; + } + + // Calculate stat boosts based on crossing level thresholds + // Characters get stat boosts at levels 4, 8, 12, 16, 20, etc. + int nOriginalBoosts = nOriginalHD / 4; // How many boosts they already had + int nCurrentBoosts = nCurrentTotalHD / 4; // How many they should have now + int nBoosts = nCurrentBoosts - nOriginalBoosts; // Additional boosts to apply + + if (nBoosts <= 0) + { + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: No boosts needed (Original boosts: " + IntToString(nOriginalBoosts) + ", Current boosts: " + IntToString(nCurrentBoosts) + ")"); + return jCreature; + } + + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Applying " + IntToString(nBoosts) + " boosts to ability " + IntToString(nAbilityToIncrease) + " for HD increase from " + IntToString(nOriginalHD) + " to " + IntToString(nCurrentTotalHD)); + + // Determine which ability to boost and apply the increases + string sAbilityField; + switch (nAbilityToIncrease) + { + case 0: sAbilityField = "Str"; break; + case 1: sAbilityField = "Dex"; break; + case 2: sAbilityField = "Con"; break; + case 3: sAbilityField = "Int"; break; + case 4: sAbilityField = "Wis"; break; + case 5: sAbilityField = "Cha"; break; + default: + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Unknown ability index: " + IntToString(nAbilityToIncrease)); + return jCreature; + } + + // Get current ability score + json jCurrentAbility = GffGetByte(jCreature, sAbilityField); + if (jCurrentAbility == JsonNull()) + { + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Failed to get " + sAbilityField + " score"); + return jCreature; + } + + int nCurrentScore = JsonGetInt(jCurrentAbility); + int nNewScore = nCurrentScore + nBoosts; + + // Clamp to valid byte range + if (nNewScore < 1) nNewScore = 1; + if (nNewScore > 250) nNewScore = 250; + + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Increasing " + sAbilityField + " from " + IntToString(nCurrentScore) + " to " + IntToString(nNewScore)); + + // Apply the ability score increase + jCreature = GffReplaceByte(jCreature, sAbilityField, nNewScore); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Failed to update " + sAbilityField); + return JsonNull(); + } + + if(DEBUG) DoDebug("json_ApplyAbilityBoostFromHD: Successfully applied ability boosts"); + return jCreature; +} + +//:: Adjust a skill by its ID +json json_AdjustCreatureSkillByID(json jCreature, int nSkillID, int nMod) +{ + // Get the SkillList + json jSkillList = GffGetList(jCreature, "SkillList"); + if (jSkillList == JsonNull()) + { + if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to get SkillList"); + return jCreature; + } + + // Check if we have enough skills in the list + int nSkillCount = JsonGetLength(jSkillList); + if (nSkillID >= nSkillCount) + { + if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Skill ID " + IntToString(nSkillID) + " exceeds skill list length " + IntToString(nSkillCount)); + return jCreature; + } + + // Get the skill struct at the correct index + json jSkill = JsonArrayGet(jSkillList, nSkillID); + if (jSkill == JsonNull()) + { + if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to get skill at index " + IntToString(nSkillID)); + return jCreature; + } + + // Get current rank + json jRank = GffGetByte(jSkill, "Rank"); + if (jRank == JsonNull()) + { + if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to get Rank for skill ID " + IntToString(nSkillID)); + return jCreature; + } + + int nCurrentRank = JsonGetInt(jRank); + int nNewRank = nCurrentRank + nMod; + + // Clamp to valid range + if (nNewRank < 0) nNewRank = 0; + if (nNewRank > 127) nNewRank = 127; + + // Update the rank in the skill struct + jSkill = GffReplaceByte(jSkill, "Rank", nNewRank); + if (jSkill == JsonNull()) + { + if(DEBUG) DoDebug("json_AdjustCreatureSkillByID: Failed to replace Rank for skill ID " + IntToString(nSkillID)); + return JsonNull(); + } + + // Replace the skill in the array + jSkillList = JsonArraySet(jSkillList, nSkillID, jSkill); + + // Replace the SkillList in the creature + jCreature = GffReplaceList(jCreature, "SkillList", jSkillList); + + return jCreature; +} + +//:: Reads FutureFeat1..FutureFeatN from the template's VarTable and appends them to FeatList if missing. +json json_AddFeatsFromCreatureVars(json jCreature, int nOriginalHD) +{ + if (jCreature == JsonNull()) + return jCreature; + + // Calculate current total HD + int nCurrentHD = json_GetCreatureHD(jCreature); + int nAddedHD = nCurrentHD - nOriginalHD; + + if (nAddedHD <= 0) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: No additional HD to process (Current: " + IntToString(nCurrentHD) + ", Original: " + IntToString(nOriginalHD) + ")"); + return jCreature; + } + + // Calculate how many feats the creature should get based on added HD + // Characters get a feat at levels 1, 3, 6, 9, 12, 15, 18, etc. + // For added levels, we need to check what feat levels they cross + int nOriginalFeats = (nOriginalHD + 2) / 3; // Feats from original HD + int nCurrentFeats = (nCurrentHD + 2) / 3; // Feats from current HD + int nNumFeats = nCurrentFeats - nOriginalFeats; // Additional feats earned + + if (nNumFeats <= 0) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: No additional feats earned from " + IntToString(nAddedHD) + " added HD"); + return jCreature; + } + + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Processing " + IntToString(nNumFeats) + " feats for " + IntToString(nAddedHD) + " added HD (Original: " + IntToString(nOriginalHD) + ", Current: " + IntToString(nCurrentHD) + ")"); + + // Get or create FeatList + json jFeatArray = GffGetList(jCreature, "FeatList"); + if (jFeatArray == JsonNull()) + jFeatArray = JsonArray(); + + int nOriginalFeatCount = JsonGetLength(jFeatArray); + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Original feat count: " + IntToString(nOriginalFeatCount)); + + int nAdded = 0; + int i = 0; + int nMaxIterations = 100; // Safety valve + int nIterations = 0; + + while (nAdded < nNumFeats && nIterations < nMaxIterations) + { + nIterations++; + string sVarName = "FutureFeat" + IntToString(i); + + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Checking " + sVarName); + + int nFeat = json_GetLocalIntFromVarTable(jCreature, sVarName); + + if (nFeat <= 0) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: " + sVarName + " not found or invalid"); + i++; + continue; + } + + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Found " + sVarName + " = " + IntToString(nFeat)); + + // Check if feat already exists + int bHasFeat = FALSE; + int nFeatCount = JsonGetLength(jFeatArray); + int j; + + for (j = 0; j < nFeatCount; j++) + { + json jFeatStruct = JsonArrayGet(jFeatArray, j); + if (jFeatStruct != JsonNull()) + { + json jFeatValue = GffGetWord(jFeatStruct, "Feat"); + if (jFeatValue != JsonNull() && JsonGetInt(jFeatValue) == nFeat) + { + bHasFeat = TRUE; + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Feat " + IntToString(nFeat) + " already exists"); + break; + } + } + } + + // Insert if missing + if (!bHasFeat) + { + json jNewFeat = JsonObject(); + jNewFeat = JsonObjectSet(jNewFeat, "__struct_id", JsonInt(1)); + jNewFeat = GffAddWord(jNewFeat, "Feat", nFeat); + + if (jNewFeat == JsonNull()) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Failed to create feat struct for feat " + IntToString(nFeat)); + break; + } + + jFeatArray = JsonArrayInsert(jFeatArray, jNewFeat); + nAdded++; + + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Added feat " + IntToString(nFeat) + " (" + IntToString(nAdded) + "/" + IntToString(nNumFeats) + ")"); + } + + i++; + + // Safety break if we've checked too many variables + if (i > 100) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Safety break - checked too many FutureFeat variables"); + break; + } + } + + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Completed. Added " + IntToString(nAdded) + " feats in " + IntToString(nIterations) + " iterations"); + + // Save back the modified FeatList only if we added something + if (nAdded > 0) + { + jCreature = GffReplaceList(jCreature, "FeatList", jFeatArray); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("json_AddFeatsFromCreatureVars: Failed to replace FeatList"); + return JsonNull(); + } + } + + return jCreature; +} + +//:: Get the size of a JSON array +int json_GetArraySize(json jArray) +{ + int iSize = 0; + while (JsonArrayGet(jArray, iSize) != JsonNull()) + { + iSize++; + } + return iSize; +} + +//:: Directly updates oCreature's Base Natural AC if iNewAC is higher. +//:: +json json_UpdateBaseAC(json jCreature, int iNewAC) +{ + //json jBaseAC = GffGetByte(jCreature, "Creature/value/NaturalAC/value"); + json jBaseAC = GffGetByte(jCreature, "NaturalAC"); + + if (jBaseAC == JsonNull()) + { + return JsonNull(); + } + else if (JsonGetInt(jBaseAC) > iNewAC) + { + return jCreature; + } + else + { + jCreature = GffReplaceByte(jCreature, "NaturalAC", iNewAC); + + return jCreature; + } +} + +//:: Increases jCreature's Natural AC by iAddAC. +//:: +json json_IncreaseBaseAC(json jCreature, int iAddAC) +{ + json jBaseAC = GffGetByte(jCreature, "NaturalAC"); + + if (jBaseAC == JsonNull()) + { + return JsonNull(); + } + else + { + int nBaseAC = JsonGetInt(jBaseAC); // convert JSON number -> int + int nNewAC = nBaseAC + iAddAC; + + jCreature = GffReplaceByte(jCreature, "NaturalAC", nNewAC); + return jCreature; + } +} + +//:: Directly modifies jCreature's Challenge Rating. +//:: This is useful for most XP calculations. +json json_UpdateCR(json jCreature, int nBaseCR, int nCRMod) +{ + int nNewCR; + +//:: Add CRMod to current CR + nNewCR = nBaseCR + nCRMod; + +//:: Modify Challenge Rating + jCreature = GffReplaceFloat(jCreature, "ChallengeRating", IntToFloat(nNewCR)); + + return jCreature; +} + +//:: Directly modifies ability scores in a creature's JSON GFF. +//:: +json json_UpdateTemplateStats(json jCreature, int iModStr = 0, int iModDex = 0, int iModCon = 0, int iModInt = 0, int iModWis = 0, int iModCha = 0) +{ + int iCurrent; + + // STR + if (!GffGetFieldExists(jCreature, "Str", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Str", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Str")); + jCreature = GffReplaceByte(jCreature, "Str", iCurrent + iModStr); + + // DEX + if (!GffGetFieldExists(jCreature, "Dex", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Dex", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Dex")); + jCreature = GffReplaceByte(jCreature, "Dex", iCurrent + iModDex); + + // CON + if (!GffGetFieldExists(jCreature, "Con", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Con", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Con")); + jCreature = GffReplaceByte(jCreature, "Con", iCurrent + iModCon); + + // INT + if (!GffGetFieldExists(jCreature, "Int", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Int", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Int")); + jCreature = GffReplaceByte(jCreature, "Int", iCurrent + iModInt); + + // WIS + if (!GffGetFieldExists(jCreature, "Wis", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Wis", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Wis")); + jCreature = GffReplaceByte(jCreature, "Wis", iCurrent + iModWis); + + // CHA + if (!GffGetFieldExists(jCreature, "Cha", GFF_FIELD_TYPE_BYTE)) + jCreature = GffAddByte(jCreature, "Cha", 10); + iCurrent = JsonGetInt(GffGetByte(jCreature, "Cha")); + jCreature = GffReplaceByte(jCreature, "Cha", iCurrent + iModCha); + + return jCreature; +} + +//:: Directly modifies oCreature's ability scores. +//:: +json json_UpdateCreatureStats(json jCreature, object oBaseCreature, int iModStr = 0, int iModDex = 0, int iModCon = 0, int iModInt = 0, int iModWis = 0, int iModCha = 0) +{ +//:: Retrieve and modify ability scores + int iCurrentStr = GetAbilityScore(oBaseCreature, ABILITY_STRENGTH); + int iCurrentDex = GetAbilityScore(oBaseCreature, ABILITY_DEXTERITY); + int iCurrentCon = GetAbilityScore(oBaseCreature, ABILITY_CONSTITUTION); + int iCurrentInt = GetAbilityScore(oBaseCreature, ABILITY_INTELLIGENCE); + int iCurrentWis = GetAbilityScore(oBaseCreature, ABILITY_WISDOM); + int iCurrentCha = GetAbilityScore(oBaseCreature, ABILITY_CHARISMA); + + jCreature = GffReplaceByte(jCreature, "Str", iCurrentStr + iModStr); + jCreature = GffReplaceByte(jCreature, "Dex", iCurrentDex + iModDex); + jCreature = GffReplaceByte(jCreature, "Con", iCurrentCon + iModCon); + jCreature = GffReplaceByte(jCreature, "Int", iCurrentInt + iModInt); + jCreature = GffReplaceByte(jCreature, "Wis", iCurrentWis + iModWis); + jCreature = GffReplaceByte(jCreature, "Cha", iCurrentCha + iModCha); + + return jCreature; +} + +//:: Increases a creature's Hit Dice in its JSON GFF data by nAmount +json json_AddHitDice(json jCreature, int nAmount) +{ + if (jCreature == JsonNull() || nAmount <= 0) + return jCreature; + + // Get the ClassList + json jClasses = GffGetList(jCreature, "ClassList"); + if (jClasses == JsonNull() || JsonGetLength(jClasses) == 0) + return jCreature; + + // Grab the first class entry + json jFirstClass = JsonArrayGet(jClasses, 0); + + json jCurrentLevel = GffGetShort(jFirstClass, "ClassLevel"); + int nCurrentLevel = JsonGetInt(jCurrentLevel); + int nNewLevel = nCurrentLevel + nAmount; + + // Replace ClassLevel only + jFirstClass = GffReplaceShort(jFirstClass, "ClassLevel", nNewLevel); + + // Put modified class back into the array + jClasses = JsonArraySet(jClasses, 0, jFirstClass); + + // Replace ClassList in the creature JSON + jCreature = GffReplaceList(jCreature, "ClassList", jClasses); + + return jCreature; +} + +//:: Adjusts a creature's size by nSizeChange (-4 to +4) and updates ability scores accordingly. +json json_AdjustCreatureSize(json jCreature, int nSizeDelta, int nIncorporeal = FALSE) +{ + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Entering function. nSizeDelta=" + IntToString(nSizeDelta)); + + if (jCreature == JsonNull() || nSizeDelta == 0) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Exiting: jCreature is null or nSizeDelta is 0"); + return jCreature; + } + + // Get Appearance_Type using GFF functions + json jAppearanceType = GffGetWord(jCreature, "Appearance_Type"); + if (jAppearanceType == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to get Appearance_Type"); + return jCreature; + } + + int nAppearance = JsonGetInt(jAppearanceType); + int nCurrentSize = StringToInt(Get2DAString("appearances", "Size", nAppearance)); + + // Default to Medium (4) if invalid + if (nCurrentSize < 0 || nCurrentSize > 8) nCurrentSize = 4; + + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Appearance_Type =" + IntToString(nAppearance) + ", Size =" + IntToString(nCurrentSize)); + + int nSteps = nSizeDelta; + + // Calculate modifiers based on size change + int strMod = nSteps * 4; + int dexMod = nSteps * -1; + int conMod = nSteps * 2; + int naturalAC = nSteps * 1; + int dexSkillMod = nSteps * -2; + + if(nIncorporeal) + { + strMod = 0; + } + + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Applying stat modifiers: STR=" + IntToString(strMod) + + " DEX=" + IntToString(dexMod) + + " CON=" + IntToString(conMod)); + + // Update ability scores using GFF functions with error checking + json jStr = GffGetByte(jCreature, "Str"); + if (jStr != JsonNull()) + { + int nNewStr = JsonGetInt(jStr) + strMod; + if (nNewStr < 1) nNewStr = 1; + if (nNewStr > 255) nNewStr = 255; + + jCreature = GffReplaceByte(jCreature, "Str", nNewStr); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update Str"); + return JsonNull(); + } + } + + json jDex = GffGetByte(jCreature, "Dex"); + if (jDex != JsonNull()) + { + int nNewDex = JsonGetInt(jDex) + dexMod; + if (nNewDex < 1) nNewDex = 1; + if (nNewDex > 255) nNewDex = 255; + + jCreature = GffReplaceByte(jCreature, "Dex", nNewDex); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update Dex"); + return JsonNull(); + } + } + + json jCon = GffGetByte(jCreature, "Con"); + if (jCon != JsonNull()) + { + int nNewCon = JsonGetInt(jCon) + conMod; + if (nNewCon < 1) nNewCon = 1; + if (nNewCon > 255) nNewCon = 255; + + jCreature = GffReplaceByte(jCreature, "Con", nNewCon); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update Con"); + return JsonNull(); + } + } + + // Update Natural AC + json jNaturalAC = GffGetByte(jCreature, "NaturalAC"); + if (jNaturalAC != JsonNull()) + { + int nCurrentNA = JsonGetInt(jNaturalAC); + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Current NaturalAC: " + IntToString(nCurrentNA)); + + int nNewNA = nCurrentNA + naturalAC; + if (nNewNA < 0) nNewNA = 0; + if (nNewNA > 255) nNewNA = 255; + + jCreature = GffReplaceByte(jCreature, "NaturalAC", nNewNA); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed to update NaturalAC"); + return JsonNull(); + } + } + + // Adjust all Dexterity-based skills by finding them in skills.2da + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Adjusting DEX-based skills"); + + int nSkillID = 0; + while (TRUE) + { + string sKeyAbility = Get2DAString("skills", "KeyAbility", nSkillID); + + // Break when we've reached the end of skills + if (sKeyAbility == "") + break; + + // If this skill uses Dexterity, adjust it + if (sKeyAbility == "DEX") + { + string sSkillLabel = Get2DAString("skills", "Label", nSkillID); + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Adjusting DEX skill: " + sSkillLabel + " (ID: " + IntToString(nSkillID) + ")"); + + jCreature = json_AdjustCreatureSkillByID(jCreature, nSkillID, dexSkillMod); + if (jCreature == JsonNull()) + { + if(DEBUG) DoDebug("prc_inc_json >> json_AdjustCreatureSize: Failed adjusting skill ID " + IntToString(nSkillID)); + return JsonNull(); + } + } + + nSkillID++; + } + + if(DEBUG) DoDebug("json_AdjustCreatureSize completed successfully"); + return jCreature; +} + +//:: Changes jCreature's creature type. +json json_ModifyRacialType(json jCreature, int nNewRacialType) +{ + if(DEBUG)DoDebug("prc_inc_json >> json_ModifyRacialType: Entering function"); + + // Retrieve the RacialType field + json jRacialTypeField = JsonObjectGet(jCreature, "Race"); + + if (JsonGetType(jRacialTypeField) == JSON_TYPE_NULL) + { + DoDebug("prc_inc_json >> json_ModifyRacialType: JsonGetType error 1: " + JsonGetError(jRacialTypeField)); + //SpeakString("JsonGetType error 1: " + JsonGetError(jRacialTypeField)); + return JsonNull(); + } + + // Retrieve the value to modify + json jRacialTypeValue = JsonObjectGet(jRacialTypeField, "value"); + + if (JsonGetType(jRacialTypeValue) != JSON_TYPE_INTEGER) + { + DoDebug("prc_inc_json >> json_ModifyRacialType: JsonGetType error 2: " + JsonGetError(jRacialTypeValue)); + //SpeakString("JsonGetType error 2: " + JsonGetError(jRacialTypeValue)); + return JsonNull(); + } + + jCreature = GffReplaceByte(jCreature, "Race", nNewRacialType); + + // Return the new creature object + return jCreature; +} + +//:: Updates CR for Celestial template +json json_UpdateCelestialCR(json jCreature, int nBaseCR, int nHD) +{ + int nNewCR; + + //:: Calculate CR based on HD + if (nHD <= 3) + { + nNewCR = nBaseCR; + } + else if (nHD <= 7) + { + nNewCR = nBaseCR + 1; + } + else + { + nNewCR = nBaseCR + 2; + } + + //:: Modify Challenge Rating + jCreature = GffReplaceFloat(jCreature, "ChallengeRating", IntToFloat(nNewCR)); + return jCreature; +} + +//:: Adds Celestial SLA's to creature +json json_AddCelestialPowers(json jCreature) +{ + // Get the existing SpecAbilityList + json jSpecAbilityList = GffGetList(jCreature, "SpecAbilityList"); + if (jSpecAbilityList == JsonNull()) + { + jSpecAbilityList = JsonArray(); + } + + //:: Add Smite Evil 1x / day + json jSpecAbility = JsonObject(); + jSpecAbility = GffAddWord(jSpecAbility, "Spell", SPELLABILITY_SMITE_EVIL); + jSpecAbility = GffAddByte(jSpecAbility, "SpellCasterLevel", json_GetCreatureHD(jCreature)); + jSpecAbility = GffAddByte(jSpecAbility, "SpellFlags", 1); + jSpecAbilityList = JsonArrayInsert(jSpecAbilityList, jSpecAbility); + + //:: Add the list to the creature + jCreature = GffAddList(jCreature, "SpecAbilityList", jSpecAbilityList); + return jCreature; +} + +//:: Apply Celestial template to a creature JSON template +json json_MakeCelestial(json jCreature, int nBaseHD, int nBaseCR) +{ + if (jCreature == JsonNull()) + return JsonNull(); + + //:: Get current HD for scaling + int nHD = json_GetCreatureHD(jCreature); + if (nHD <= 0) + { + DoDebug("prc_inc_json >> json_MakeCelestial: Invalid HD"); + return JsonNull(); + } + + //:: Get current CR + float fCR = json_GetChallengeRating(jCreature); + + //:: Update CR using Celestial formula + jCreature = json_UpdateCelestialCR(jCreature, FloatToInt(fCR), nHD); + if (jCreature == JsonNull()) + { + DoDebug("prc_inc_json >> json_MakeCelestial: json_UpdateCelestialCR failed"); + return JsonNull(); + } + + //:: Ensure Intelligence is at least 4 + json jInt = GffGetByte(jCreature, "Int"); + if (jInt != JsonNull() && JsonGetInt(jInt) < 4) + { + jCreature = GffReplaceByte(jCreature, "Int", 4); + } + + //:: Add celestial Smite Evil + jCreature = json_AddCelestialPowers(jCreature); + if (jCreature == JsonNull()) + { + DoDebug("prc_inc_json >> json_MakeCelestial: json_AddCelestialPowers failed"); + return JsonNull(); + } + + //:: Change creature type if animal/beast/vermin to magical beast + int nRacialType = JsonGetInt(GffGetByte(jCreature, "Race")); + if (nRacialType == RACIAL_TYPE_ANIMAL || nRacialType == RACIAL_TYPE_VERMIN || nRacialType == RACIAL_TYPE_BEAST) + { + jCreature = json_ModifyRacialType(jCreature, RACIAL_TYPE_MAGICAL_BEAST); + } + + //:: Update creature CR + jCreature = json_UpdateCelestialCR(jCreature, nBaseCR, nHD); + if (jCreature == JsonNull()) + { + DoDebug("prc_inc_json >> json_MakeCelestial: json_UpdateCelestialCR failed"); + return JsonNull(); + } + + return jCreature; +} + +//:: Spawns a Celestial Companion from a template +object MakeCelestialCompanionFromTemplate(string sResref, location lSpawnLoc, int nHealerLvl) +{ + int nBonus = GetHealerCompanionBonus(nHealerLvl); + + json jCelestial = TemplateToJson(sResref, RESTYPE_UTC); + if (jCelestial == JSON_NULL) + { + DoDebug("prc_inc_json >> MakeCelestialCompanionFromTemplate: TemplateToJson failed — bad resref or resource missing."); + return OBJECT_INVALID; + } + + //:: Get local vars to transfer over. + int iMinHD = json_GetLocalIntFromVarTable(jCelestial, "iMinHD"); + int iMaxHD = json_GetLocalIntFromVarTable(jCelestial, "iMaxHD"); + int nOriginalHD = json_GetLocalIntFromVarTable(jCelestial, "nOriginalHD"); + int iClass2 = json_GetLocalIntFromVarTable(jCelestial, "Class2"); + int iClass2Package = json_GetLocalIntFromVarTable(jCelestial, "Class2Package"); + int iClass2Start = json_GetLocalIntFromVarTable(jCelestial, "Class2Start"); + int iBaseCL = json_GetLocalIntFromVarTable(jCelestial, "iBaseCL"); + int iMagicUse = json_GetLocalIntFromVarTable(jCelestial, "X2_L_BEH_MAGIC"); + string sAI = json_GetLocalStringFromVarTable(jCelestial, "X2_SPECIAL_COMBAT_AI_SCRIPT"); + + //:: Get the original Challenge Rating + int nBaseCR = FloatToInt(json_GetChallengeRating(jCelestial)); + + //:: Apply celestial template modifications + jCelestial = json_MakeCelestial(jCelestial, nBonus, nBaseCR); + if (jCelestial == JSON_NULL) + { + DoDebug("prc_inc_json >> MakeCelestialCompanionFromTemplate failed — json_MakeCelestial returned invalid JSON."); + return OBJECT_INVALID; + } + + //:: Apply +2 Natural AC bonus per 3 Healer levels + jCelestial = json_IncreaseBaseAC(jCelestial, nBonus); + if (jCelestial == JSON_NULL) + { + DoDebug("prc_inc_json >> MakeCelestialCompanionFromTemplate failed — json_IncreaseBaseAC returned invalid JSON."); + return OBJECT_INVALID; + } + + //:: +2 STR, DEX & INT per 3 Healer levels + jCelestial = json_UpdateTemplateStats(jCelestial, nBonus, nBonus, 0, nBonus, 0, 0); + if (jCelestial == JSON_NULL) + { + DoDebug("prc_inc_json >> MakeCelestialCompanionFromTemplate failed — json_UpdateTemplateStats returned invalid JSON."); + return OBJECT_INVALID; + } + + //:: The Companion always has Improved Evasion if the healer qualifies, + //:: but adding it this way gives the base creature more utility for builders. + if (nHealerLvl > 7) + { + //:: Add Improved Evasion feat directly to FeatList + json jFeatList = GffGetList(jCelestial, "FeatList"); + if (jFeatList == JsonNull()) + jFeatList = JsonArray(); + + //:: Check if creature already has Improved Evasion + int bHasFeat = FALSE; + int nFeatCount = JsonGetLength(jFeatList); + int j; + + for (j = 0; j < nFeatCount; j++) + { + json jFeatStruct = JsonArrayGet(jFeatList, j); + if (jFeatStruct != JsonNull()) + { + json jFeatValue = GffGetWord(jFeatStruct, "Feat"); + if (jFeatValue != JsonNull() && JsonGetInt(jFeatValue) == FEAT_IMPROVED_EVASION) + { + bHasFeat = TRUE; + break; + } + } + } + + //:: Add feat only if not already present + if (!bHasFeat) + { + json jNewFeat = JsonObject(); + jNewFeat = JsonObjectSet(jNewFeat, "__struct_id", JsonInt(1)); + jNewFeat = GffAddWord(jNewFeat, "Feat", FEAT_IMPROVED_EVASION); + + jFeatList = JsonArrayInsert(jFeatList, jNewFeat); + jCelestial = GffReplaceList(jCelestial, "FeatList", jFeatList); + } + } + + //:: Spawn the creature + object oCelestial = JsonToObject(jCelestial, lSpawnLoc); + + //:: Set variables for LevelUpSummon() + SetLocalInt(oCelestial, "TEMPLATE_CELESTIAL", 1); + SetLocalInt(oCelestial, "iMinHD", iMinHD); + SetLocalInt(oCelestial, "iMaxHD", iMaxHD); + SetLocalInt(oCelestial, "nOriginalHD", nOriginalHD); + SetLocalInt(oCelestial, "Class2", iClass2); + SetLocalInt(oCelestial, "Class2Package", iClass2Package); + SetLocalInt(oCelestial, "Class2Start", iClass2Start); + SetLocalInt(oCelestial, "iBaseCL", iBaseCL); + SetLocalInt(oCelestial, "X2_L_BEH_MAGIC", iMagicUse); + SetLocalString(oCelestial, "X2_SPECIAL_COMBAT_AI_SCRIPT", sAI); + + return oCelestial; +} + +//:: Spawns a Celestial creature from a template +object MakeCelestialCreatureFromTemplate(string sResref, location lSpawnLoc) +{ + json jCelestial = TemplateToJson(sResref, RESTYPE_UTC); + if (jCelestial == JSON_NULL) + { + DoDebug("prc_inc_json >> MakeCelestialCreatureFromTemplate: TemplateToJson failed — bad resref or resource missing."); + return OBJECT_INVALID; + } + + //:: Get current HD + int nCurrentHD = json_GetCreatureHD(jCelestial); + if (nCurrentHD <= 0) + { + DoDebug("make_celestial >> MakeCelestialCreatureFromTemplate failed — template missing HD data."); + return OBJECT_INVALID; + } + + //:: Get current CR + int nBaseCR = 1; + nBaseCR = FloatToInt(json_GetChallengeRating(jCelestial)); + if (nBaseCR <= 0) + { + DoDebug("make_celestial >> MakeCelestialCreatureFromTemplate failed — template missing CR data."); + return OBJECT_INVALID; + } + + //:: Get local vars to transfer over. + int iMinHD = json_GetLocalIntFromVarTable(jCelestial, "iMinHD"); + int iMaxHD = json_GetLocalIntFromVarTable(jCelestial, "iMaxHD"); + int nOriginalHD = json_GetLocalIntFromVarTable(jCelestial, "nOriginalHD"); + int iClass2 = json_GetLocalIntFromVarTable(jCelestial, "Class2"); + int iClass2Package = json_GetLocalIntFromVarTable(jCelestial, "Class2Package"); + int iClass2Start = json_GetLocalIntFromVarTable(jCelestial, "Class2Start"); + int iBaseCL = json_GetLocalIntFromVarTable(jCelestial, "iBaseCL"); + int iMagicUse = json_GetLocalIntFromVarTable(jCelestial, "X2_L_BEH_MAGIC"); + string sAI = json_GetLocalStringFromVarTable(jCelestial, "X2_SPECIAL_COMBAT_AI_SCRIPT"); + + //:: Apply celestial template modifications + jCelestial = json_MakeCelestial(jCelestial, nCurrentHD, nBaseCR); + if (jCelestial == JSON_NULL) + { + DoDebug("make_celestial >> MakeCelestialCreatureFromTemplate failed — MakeCelestial returned invalid JSON."); + return OBJECT_INVALID; + } + + //:: Spawn the creature + object oCelestial = JsonToObject(jCelestial, lSpawnLoc); + + //:: Set variables + SetLocalInt(oCelestial, "TEMPLATE_CELESTIAL", 1); + SetLocalInt(oCelestial, "iMinHD", iMinHD); + SetLocalInt(oCelestial, "iMaxHD", iMaxHD); + SetLocalInt(oCelestial, "nOriginalHD", nOriginalHD); + SetLocalInt(oCelestial, "Class2", iClass2); + SetLocalInt(oCelestial, "Class2Package", iClass2Package); + SetLocalInt(oCelestial, "Class2Start", iClass2Start); + SetLocalInt(oCelestial, "Class2Start", iClass2Start); + SetLocalInt(oCelestial, "iBaseCL", iBaseCL); + SetLocalInt(oCelestial, "X2_L_BEH_MAGIC", iMagicUse); + SetLocalString(oCelestial, "X2_SPECIAL_COMBAT_AI_SCRIPT", sAI); + + return oCelestial; +} + +//:: Adds Paragon SLA's to jCreature. +//:: +json json_AddParagonPowers(json jCreature) +{ + // Get the existing SpecAbilityList (if it exists) + json jSpecAbilityList = GffGetList(jCreature, "SpecAbilityList"); + + // Create the SpecAbilityList if it doesn't exist + if (jSpecAbilityList == JsonNull()) + { + jSpecAbilityList = JsonArray(); + } + +//:: Greater Dispelling 3x / Day + int i; + for (i = 0; i < 3; i++) + { + json jSpecAbility = JsonObject(); + jSpecAbility = GffAddWord(jSpecAbility, "Spell", 67); + jSpecAbility = GffAddByte(jSpecAbility, "SpellCasterLevel", 15); + jSpecAbility = GffAddByte(jSpecAbility, "SpellFlags", 1); + + // Manually add to the array + jSpecAbilityList = JsonArrayInsert(jSpecAbilityList, jSpecAbility); + } + +//:: Add Haste 3x / Day + for (i = 0; i < 3; i++) + { + json jSpecAbility = JsonObject(); + jSpecAbility = GffAddWord(jSpecAbility, "Spell", 78); + jSpecAbility = GffAddByte(jSpecAbility, "SpellCasterLevel", 15); + jSpecAbility = GffAddByte(jSpecAbility, "SpellFlags", 1); + + // Manually add to the array + jSpecAbilityList = JsonArrayInsert(jSpecAbilityList, jSpecAbility); + } + +//:: See Invisiblity 3x / Day + for (i = 0; i < 3; i++) + { + json jSpecAbility = JsonObject(); + jSpecAbility = GffAddWord(jSpecAbility, "Spell", 157); + jSpecAbility = GffAddByte(jSpecAbility, "SpellCasterLevel", 15); + jSpecAbility = GffAddByte(jSpecAbility, "SpellFlags", 1); + + // Manually add to the array + jSpecAbilityList = JsonArrayInsert(jSpecAbilityList, jSpecAbility); + } + +//:: Add the list to the creature + jCreature = GffAddList(jCreature, "SpecAbilityList", jSpecAbilityList); + + return jCreature; +} + +//:: Directly modifies jCreature's Challenge Rating. +//:: This is useful for most XP calculations. +//:: +json json_UpdateParagonCR(json jCreature, int nBaseCR, int nBaseHD) +{ + int nNewCR; + +//:: Calculate additional CR by HD + if(nBaseHD <= 6) + { + nNewCR = nBaseCR + 18; + } + else if(nBaseHD <= 16) + { + nNewCR = nBaseCR + 15; + } + else + {nNewCR = nBaseCR + 12;} + +//:: Modify Challenge Rating + jCreature = GffReplaceFloat(jCreature, "ChallengeRating"/* /value" */, IntToFloat(nNewCR)); + + return jCreature; +} + +//:: Adds Psuedonatural SLA's to jCreature. +//:: +json json_AddPsuedonaturalPowers(json jCreature) +{ + // Get the existing SpecAbilityList (if it exists) + json jSpecAbilityList = GffGetList(jCreature, "SpecAbilityList"); + + // Create the SpecAbilityList if it doesn't exist + if (jSpecAbilityList == JsonNull()) + { + jSpecAbilityList = JsonArray(); + } + +//:: True Strike 1x / Day + int i; + for (i = 0; i < 1; i++) + { + json jSpecAbility = JsonObject(); + jSpecAbility = GffAddWord(jSpecAbility, "Spell", 415); + jSpecAbility = GffAddByte(jSpecAbility, "SpellCasterLevel", 15); + jSpecAbility = GffAddByte(jSpecAbility, "SpellFlags", 1); + + // Manually add to the array + jSpecAbilityList = JsonArrayInsert(jSpecAbilityList, jSpecAbility); + } + +//:: Add the list to the creature + jCreature = GffAddList(jCreature, "SpecAbilityList", jSpecAbilityList); + + return jCreature; +} + +//:: Directly modifies jCreature's Challenge Rating. +//:: This is useful for most XP calculations. +//:: +json json_UpdatePsuedonaturalCR(json jCreature, int nBaseCR, int nBaseHD) +{ + int nNewCR; + +//:: Calculate additional CR by HD + if (nBaseHD >= 4 && nBaseHD <= 11) + { + nNewCR = nBaseCR + 1; + } + else if (nBaseHD >= 12) + { + nNewCR = nBaseCR + 2; + } + +//:: Modify Challenge Rating + jCreature = GffReplaceFloat(jCreature, "ChallengeRating"/* /value" */, IntToFloat(nNewCR)); + + return jCreature; +} + + +//:: Spawns a Psuedonatural creature from a template +object MakePsuedonaturalCreatureFromTemplate(string sResref, location lSpawnLoc) +{ + json jPsuedo = TemplateToJson(sResref, RESTYPE_UTC); + if (jPsuedo == JSON_NULL) + { + DoDebug("prc_inc_json >> SpawnPsuedonaturalCreatureFromTemplate: TemplateToJson failed — bad resref or resource missing."); + return OBJECT_INVALID; + } + + //:: Get current HD + int nCurrentHD = json_GetCreatureHD(jPsuedo); + if (nCurrentHD <= 0) + { + DoDebug("make_psuedonat >> MakePsuedonaturalCreatureFromTemplate failed — template missing HD data."); + return OBJECT_INVALID; + } + + //:: Get current CR + int nBaseCR = 1; + nBaseCR = json_GetCreatureHD(jPsuedo); + if (nBaseCR <= 0) + { + DoDebug("make_psuedonat >> MakePsuedonaturalCreatureFromTemplate failed — template missing CR data."); + return OBJECT_INVALID; + } + + //:: Get local vars to transfer over. + int iMinHD = json_GetLocalIntFromVarTable(jPsuedo, "iMinHD"); + int iMaxHD = json_GetLocalIntFromVarTable(jPsuedo, "iMaxHD"); + int nOriginalHD = json_GetLocalIntFromVarTable(jPsuedo, "nOriginalHD"); + int iClass2 = json_GetLocalIntFromVarTable(jPsuedo, "Class2"); + int iClass2Package = json_GetLocalIntFromVarTable(jPsuedo, "Class2Package"); + int iClass2Start = json_GetLocalIntFromVarTable(jPsuedo, "Class2Start"); + int iMagicUse = json_GetLocalIntFromVarTable(jPsuedo, "X2_L_BEH_MAGIC"); + string sAI = json_GetLocalStringFromVarTable(jPsuedo, "X2_SPECIAL_COMBAT_AI_SCRIPT"); + + //:: Adds True Strike 1x / day to jCreature. + jPsuedo = json_AddPsuedonaturalPowers(jPsuedo); + + //:: Change jCreature's racialtype to outsider + jPsuedo = json_ModifyRacialType(jPsuedo, RACIAL_TYPE_OUTSIDER); + + jPsuedo = json_UpdatePsuedonaturalCR(jPsuedo, nBaseCR, nCurrentHD); + + //:: Spawn the creature + object oPsuedo = JsonToObject(jPsuedo, lSpawnLoc); + + //:: Set variables + SetLocalInt(oPsuedo, "TEMPLATE_PSUEDONATURAL", 1); + SetLocalInt(oPsuedo, "iMinHD", iMinHD); + SetLocalInt(oPsuedo, "iMaxHD", iMaxHD); + SetLocalInt(oPsuedo, "nOriginalHD", nOriginalHD); + SetLocalInt(oPsuedo, "Class2", iClass2); + SetLocalInt(oPsuedo, "Class2Package", iClass2Package); + SetLocalInt(oPsuedo, "Class2Start", iClass2Start); + SetLocalInt(oPsuedo, "X2_L_BEH_MAGIC", iMagicUse); + SetLocalString(oPsuedo, "X2_SPECIAL_COMBAT_AI_SCRIPT", sAI); + + return oPsuedo; + +} + +//:: Test void +//::void main (){} \ No newline at end of file diff --git a/src/include/prc_inc_material.nss b/src/include/prc_inc_material.nss index f69e26d..bb84f86 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) @@ -426,8 +430,9 @@ string GetMaterialName( int iMaterialType, int bLowerCase = FALSE) case IP_MATERIAL_ROPE_GIANT_HAIR: sName = IP_MATERIAL_NAME_ROPE_GIANT_HAIR; break; case IP_MATERIAL_OBSIDIAN: sName = IP_MATERIAL_NAME_OBSIDIAN; break; 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_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_nat_hb.nss b/src/include/prc_inc_nat_hb.nss index c8f7c27..10bc2fb 100644 --- a/src/include/prc_inc_nat_hb.nss +++ b/src/include/prc_inc_nat_hb.nss @@ -1,9 +1,68 @@ +//:: prc_inc_nat_hb +//:: +//:: void main(){} + + void DoNaturalWeaponHB(object oPC = OBJECT_SELF); #include "prc_inc_combat" #include "prc_inc_template" - -object GetProperTarget(object oPC, object oTarget) +/** + * Finds a valid enemy target in melee range when the original target is invalid. + * Now includes input validation, LOS checks, configurable radius, and target priority. + * + * @param oPC The creature seeking a new target + * @param oTarget The original (invalid) target + * @param fRadius Search radius in meters (optional, defaults to melee range) + * @return A valid enemy target or OBJECT_INVALID if none found + */ +object GetProperTarget(object oPC, object oTarget, float fRadius = MELEE_RANGE_METERS) +{ + // Input validation + if(!GetIsObjectValid(oPC)) + { + DoDebug("GetProperTarget(): Invalid oPC parameter"); + return OBJECT_INVALID; + } + + // Use target list system for better target selection + PurgeTargetList(oPC); + + location lPC = GetLocation(oPC); + object oTest = MyFirstObjectInShape(SHAPE_SPHERE, fRadius, lPC, TRUE, OBJECT_TYPE_CREATURE); + + while(GetIsObjectValid(oTest)) + { + // Basic validation checks + if(oTest != oPC && // Not self + GetIsEnemy(oPC, oTest) && // Is enemy + GetIsInMeleeRange(oPC, oTest) && // In melee range + !GetIsDead(oTest) && // Is alive + LineOfSightObject(oPC, oTest)) // Has line of sight + { + // Add to target list with priority based on distance (nearest first) + AddToTargetList(oTest, oPC, INSERTION_BIAS_DISTANCE, FALSE); + } + + oTest = MyNextObjectInShape(SHAPE_SPHERE, fRadius, lPC, TRUE, OBJECT_TYPE_CREATURE); + } + + // Get the highest priority target (nearest enemy) + object oBestTarget = GetTargetListHead(oPC); + PurgeTargetList(oPC); + + if(GetIsObjectValid(oBestTarget)) + { + DoDebug("GetProperTarget(): Selected target " + GetName(oBestTarget) + + " for " + GetName(oPC)); + return oBestTarget; + } + + // No valid target found + DoDebug("GetProperTarget(): No valid target found for " + GetName(oPC)); + return OBJECT_INVALID; +} +/* object GetProperTarget(object oPC, object oTarget) { location lTarget = GetLocation(oPC); // Use the function to get the closest creature as a target @@ -21,7 +80,7 @@ object GetProperTarget(object oPC, object oTarget) } return oTarget; -} +} */ void DoNaturalAttack(object oWeapon) { @@ -289,59 +348,72 @@ void DoOverflowOnhandAttack(int nAttackMod) ); } -void DoNaturalWeaponHB(object oPC = OBJECT_SELF) +/* void DoNaturalWeaponHB(object oPC = OBJECT_SELF) { //not in combat, abort if(!GetIsInCombat(oPC)) return; -// if(DEBUG) DoDebug("entered DoNaturalWeaponHB"); + if(DEBUG) DoDebug("prc_inc_nat_hb: entered DoNaturalWeaponHB"); float fDelay = 0.1 + IntToFloat(Random(10))/100.0; //no natural weapons, abort //in a different form, abort for now fix it later - if(array_exists(oPC, ARRAY_NAT_SEC_WEAP_RESREF) - && !GetIsPolyMorphedOrShifted(oPC)) +if(array_exists(oPC, ARRAY_NAT_SEC_WEAP_RESREF) + && !GetIsPolyMorphedOrShifted(oPC)) +{ + DoDebug("prc_inc_nat_hb >> DoNaturalWeaponHB: creature has natural secondary weapons"); + UpdateSecondaryWeaponSizes(oPC); + int i; + while(i < array_get_size(oPC, ARRAY_NAT_SEC_WEAP_RESREF)) { - // DoDebug("DoNaturalWeaponHB: creature has natural secondary weapons"); - UpdateSecondaryWeaponSizes(oPC); - int i; - while(i < array_get_size(oPC, ARRAY_NAT_SEC_WEAP_RESREF)) + string sResRef = array_get_string(oPC, ARRAY_NAT_SEC_WEAP_RESREF, i); + if(sResRef != "") { - //get the resref to use - string sResRef = array_get_string(oPC, ARRAY_NAT_SEC_WEAP_RESREF, i); - //if null, move to next - if(sResRef != "") + // Get stored weapon object, or create if doesn't exist + object oWeapon = GetLocalObject(oPC, "NAT_SEC_WEAP_" + sResRef); + + if(!GetIsObjectValid(oWeapon)) { - //get the created item - object oWeapon = GetObjectByTag(sResRef); + DoDebug("prc_inc_nat_hb >> DoNaturalWeaponHB: creating and storing creature weapon " + sResRef); + oWeapon = CreateItemOnObject(sResRef, oPC); + if(!GetIsObjectValid(oWeapon)) { - object oLimbo = GetObjectByTag("HEARTOFCHAOS"); - location lLimbo = GetLocation(oLimbo); - if(!GetIsObjectValid(oLimbo)) - lLimbo = GetStartingLocation(); - oWeapon = CreateObject(OBJECT_TYPE_ITEM, sResRef, lLimbo); + DoDebug("prc_inc_nat_hb >> DoNaturalWeaponHB: ERROR - CreateItemOnObject FAILED for " + sResRef); } + else + { + DoDebug("prc_inc_nat_hb >> DoNaturalWeaponHB: SUCCESS - weapon created, tag=" + GetTag(oWeapon) + ", name=" + GetName(oWeapon)); + SetIdentified(oWeapon, TRUE); + SetLocalObject(oPC, "NAT_SEC_WEAP_" + sResRef, oWeapon); + } + } + else + { + DoDebug("prc_inc_nat_hb >> DoNaturalWeaponHB: using stored creature weapon object"); + } - // DoDebug(COLOR_WHITE + "DoNaturalWeaponHB: scheduling a secondary natural attack with "+GetName(oWeapon)+" at delay "+FloatToString(fDelay)); - //do the attack within a delay - /* - // motu99: commented this out; AssignCommand ist not needed, because OBJECT_SELF is oPC - using AssignCommand will only degrade performance - AssignCommand(oPC, DelayCommand(fDelay, DoNaturalAttack(oWeapon))); - */ - + // Double-check validity before scheduling + if(GetIsObjectValid(oWeapon)) + { + DoDebug("prc_inc_nat_hb >> DoNaturalWeaponHB: scheduling a secondary natural attack with "+GetName(oWeapon)+" at delay "+FloatToString(fDelay)); DelayCommand(fDelay, DoNaturalAttack(oWeapon)); - //calculate the delay to use next time fDelay += 2.05; if(fDelay > 6.0) - fDelay -= 6.0; + fDelay -= 6.0; + } + else + { + DoDebug("prc_inc_nat_hb >> DoNaturalWeaponHB: ERROR - weapon object is INVALID, cannot schedule attack"); } - i++; } + i++; } +} + int iMod = 5; // motu99: added check for monk weapon if(GetHasMonkWeaponEquipped(oPC)) iMod = 3; @@ -357,10 +429,10 @@ void DoNaturalWeaponHB(object oPC = OBJECT_SELF) for(i = 0; i < nOverflowAttackCount; i++) { // DoDebug(COLOR_WHITE + "DoNaturalWeaponHB(): scheduling a scripted overflow attack with attack mod "+IntToString(nAttackPenalty)+" at delay "+FloatToString(fDelay)); - /* + // motu99: see comment above why this is commented out - AssignCommand(oPC, DelayCommand(fDelay, DoOverflowOnhandAttack(nAttackPenalty))); - */ + //AssignCommand(oPC, DelayCommand(fDelay, DoOverflowOnhandAttack(nAttackPenalty))); + DelayCommand(fDelay, DoOverflowOnhandAttack(nAttackPenalty)); //calculate the delay to use @@ -399,6 +471,128 @@ void DoNaturalWeaponHB(object oPC = OBJECT_SELF) } } } + */ + +void DoNaturalWeaponHB(object oPC = OBJECT_SELF) +{ + //not in combat, abort + if(!GetIsInCombat(oPC)) + return; + + if(DEBUG) DoDebug("prc_inc_nat_hb: entered DoNaturalWeaponHB"); + + float fDelay = 0.1 + IntToFloat(Random(10))/100.0; + + //no natural weapons, abort + //in a different form, abort for now fix it later + if(array_exists(oPC, ARRAY_NAT_SEC_WEAP_RESREF) + && !GetIsPolyMorphedOrShifted(oPC)) + { + DoDebug("prc_inc_nat_hb >> DoNaturalWeaponHB: creature has natural secondary weapons"); + UpdateSecondaryWeaponSizes(oPC); + int i; + while(i < array_get_size(oPC, ARRAY_NAT_SEC_WEAP_RESREF)) + { + //get the resref to use + string sResRef = array_get_string(oPC, ARRAY_NAT_SEC_WEAP_RESREF, i); + //if null, move to next + if(sResRef != "") + { + //get the created item + object oWeapon = GetObjectByTag(sResRef); + if(!GetIsObjectValid(oWeapon)) + { + object oLimbo = GetObjectByTag("HEARTOFCHAOS"); + location lLimbo = GetLocation(oLimbo); + if(!GetIsObjectValid(oLimbo)) + lLimbo = GetStartingLocation(); + oWeapon = CreateObject(OBJECT_TYPE_ITEM, sResRef, lLimbo); + DoDebug(PRC_TEXT_WHITE + "prc_inc_nat_hb >> DoNaturalWeaponHB: creature weapon object found!!!"); + } + + // Check for enhancements after creating the weapon object + int nEnhance = GetLocalInt(oPC, "PRC_NAT_WEAPON_ENHANCE"); + if(nEnhance > 0) + { + + DoDebug(PRC_TEXT_WHITE + "prc_inc_nat_hb >> DoNaturalWeaponHB: Applying enhancement."); + float fDuration = GetLocalFloat(oPC, "PRC_NAT_WEAPON_ENH_DUR"); + IPSafeAddItemProperty(oWeapon, ItemPropertyEnhancementBonus(nEnhance), fDuration, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING, FALSE, TRUE); + } + + if(DEBUG) DoDebug("prc_inc_nat_hb >> DoNaturalWeaponHB: scheduling a secondary natural attack with "+GetName(oWeapon)+" at delay "+FloatToString(fDelay)); + //do the attack within a delay + // motu99: commented this out; AssignCommand ist not needed, because OBJECT_SELF is oPC - using AssignCommand will only degrade performance + //AssignCommand(oPC, DelayCommand(fDelay, DoNaturalAttack(oWeapon))); + + DelayCommand(fDelay, DoNaturalAttack(oWeapon)); + + //calculate the delay to use next time + fDelay += 2.05; + if(fDelay > 6.0) + fDelay -= 6.0; + } + i++; + } + } + + int iMod = 5; // motu99: added check for monk weapon + if(GetHasMonkWeaponEquipped(oPC)) iMod = 3; + + // check for overflow (main hand) attacks + int nOverflowAttackCount = GetLocalInt(oPC, "OverflowBaseAttackCount"); + if(nOverflowAttackCount) + { + int i; + // the first overflow attack would be the seventh main hand attack, at an AB of -30 + int nAttackPenalty = -6 * iMod; // -30 for normal bab, -18 for monks + // DoDebug("DoNaturalWeaponHB(): number of scripted overflow attacks: "+IntToString(nOverflowAttackCount)); + for(i = 0; i < nOverflowAttackCount; i++) + { + // DoDebug(COLOR_WHITE + "DoNaturalWeaponHB(): scheduling a scripted overflow attack with attack mod "+IntToString(nAttackPenalty)+" at delay "+FloatToString(fDelay)); + + // motu99: see comment above why this is commented out + // AssignCommand(oPC, DelayCommand(fDelay, DoOverflowOnhandAttack(nAttackPenalty))); + + DelayCommand(fDelay, DoOverflowOnhandAttack(nAttackPenalty)); + + //calculate the delay to use + fDelay += 2.05; + if(fDelay > 6.0) + fDelay -= 6.0; + //calculate new attack penalty + nAttackPenalty -= iMod; // motu99: usually -5, for monks -3 (unarmed or kama) + } + } + + // motu99: this is only here for debugging in order to test PerformAttackRound() + // must be deleted after debugging!!! + //if (GetPRCSwitch(PRC_PNP_TRUESEEING)) DelayCommand(0.01, DoOffhandAttackRound()); + + + // check for overflow offhand attacks + int nOffhandAttackCount = GetLocalInt(oPC, "OffhandOverflowAttackCount"); +// if (DEBUG) DoDebug("DoNaturalWeaponHB: number of scripted offhand attacks = "+IntToString(nOffhandAttackCount)); + if(nOffhandAttackCount) + { + int i; + int nAttackPenalty = -2 * iMod; // offhand attacks always come at -5 per additional attack (but for monks we assume -3) + for(i = 0; i < nOffhandAttackCount; i++) + { + // DoDebug(COLOR_WHITE + "DoNaturalWeaponHB(): scheduling a scripted offhand attack with attack mod "+IntToString(nAttackPenalty)+" at delay "+FloatToString(fDelay)); + + DelayCommand(fDelay, DoOffhandAttack(nAttackPenalty)); + + //calculate the delay to use + fDelay += 2.05; + if(fDelay > 6.0) + fDelay -= 6.0; + //calculate new attack penalty + nAttackPenalty -= iMod; + } + } +} + /* * motu99's test functions. Not actually used by PRC scripts diff --git a/src/include/prc_inc_natweap.nss b/src/include/prc_inc_natweap.nss index 8d3668b..78115a5 100644 --- a/src/include/prc_inc_natweap.nss +++ b/src/include/prc_inc_natweap.nss @@ -277,6 +277,32 @@ void ClearNaturalWeapons(object oPC) array_delete(oPC, ARRAY_NAT_PRI_WEAP_ATTACKS); } +/** + * @brief Adds a natural primary weapon to a creature (PC/NPC). + * + * This function manages a creature's natural primary weapons by storing their + * resource references and attack counts in persistent arrays. If the weapon + * being added is the first natural weapon, it may automatically become the + * creature's active primary natural weapon, unless the creature is a Monk or + * Brawler. Optionally, the weapon can be forced to become the active primary + * weapon regardless of class. + * + * @param oPC The creature object to which the natural weapon will be added. + * @param sResRef The resource reference string of the natural weapon. + * @param nCount (Optional) The number of attacks this natural weapon provides. + * Default is 1. + * @param nForceUse (Optional) If TRUE, forces this weapon to become the active + * primary natural weapon regardless of the creature's class. + * Default is FALSE. + * + * @details + * - Creates persistent arrays for weapon references and attack counts if they + * do not already exist. + * - Checks if the weapon is already present to avoid duplicates. + * - Adds the weapon and attack count to the arrays. + * - Sets the primary natural weapon index to this weapon if it is the first + * natural weapon added, unless the creature is a Monk or Brawler. + */ void AddNaturalPrimaryWeapon(object oPC, string sResRef, int nCount = 1, int nForceUse = FALSE) { int nFirstNaturalWeapon = FALSE; diff --git a/src/include/prc_inc_nwscript.nss b/src/include/prc_inc_nwscript.nss index 8ebc821..4671756 100644 --- a/src/include/prc_inc_nwscript.nss +++ b/src/include/prc_inc_nwscript.nss @@ -572,7 +572,10 @@ int GetMaxEssentiaCapacity(object oMeldshaper, int nClass, int nMeld) { int nMax = 1; // Always can invest one int nHD = GetHitDice(oMeldshaper); - if (nHD >= 31) nMax = 5; + if (nHD >= 61) nMax = 8; + else if (nHD >= 51) nMax = 7; + else if (nHD >= 41) nMax = 6; + else if (nHD >= 31) nMax = 5; else if (nHD >= 18) nMax = 4; else if (nHD >= 12) nMax = 3; else if (nHD >= 6) nMax = 2; 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_shifting.nss b/src/include/prc_inc_shifting.nss index 7cac1ed..1e0fadc 100644 --- a/src/include/prc_inc_shifting.nss +++ b/src/include/prc_inc_shifting.nss @@ -1370,7 +1370,9 @@ void _prc_inc_shifting_ShiftIntoTemplateAux(object oShifter, int nShifterType, o if(GetIsObjectValid(oShifterCWpR)) MyDestroyObject(oShifterCWpR); if(GetIsObjectValid(oShifterCWpL)) MyDestroyObject(oShifterCWpL); if(GetIsObjectValid(oShifterCWpB)) MyDestroyObject(oShifterCWpB); - oShifterCWpR = oShifterCWpL = oShifterCWpR = OBJECT_INVALID; + oShifterCWpR = OBJECT_INVALID; + oShifterCWpL = OBJECT_INVALID; + oShifterCWpB = OBJECT_INVALID; // Copy the template's weapons and assign equipping diff --git a/src/include/prc_inc_size.nss b/src/include/prc_inc_size.nss new file mode 100644 index 0000000..286015b --- /dev/null +++ b/src/include/prc_inc_size.nss @@ -0,0 +1,154 @@ +#include "prc_inc_util" +#include "prc_inc_spells" +#include "prc_inc_function" + +// Wrapper function for delayed visual transform with generation tracking +void DelayedSetVisualTransform(int nExpectedGeneration, object oTarget, int nTransform, float fValue) +{ + // Read current generation at execution time, not when scheduled + if (nExpectedGeneration != GetLocalInt(oTarget, "PRC_SIZE_GENERATION")) + { + // Generation has changed, don't apply the transform + return; + } + SetObjectVisualTransform(oTarget, nTransform, fValue); +} + +// Main wrapper function that handles generation tracking +void DelaySetVisualTransform(float fDelay, object oTarget, string sGenerationName, int nTransform, float fValue) +{ + int nExpectedGeneration = GetLocalInt(oTarget, sGenerationName); + DelayCommand(fDelay, DelayedSetVisualTransform(nExpectedGeneration, oTarget, nTransform, fValue)); +} + +/** + * Creates a size change effect that can enlarge or reduce a creature + * + * @param oTarget Object to affect + * @param nObjectType Object type filter (OBJECT_TYPE_CREATURE, etc.) + * @param bEnlarge TRUE to enlarge, FALSE to reduce + * @param nChanges Number of size categories to change (0 = reset to original) + * @return The size change effect + */ + +effect EffectSizeChange(object oTarget, int nObjectType, int bEnlarge, int nChanges) +{ + effect eBlank; + + // Increment generation for any size change + int nGeneration = PRC_NextGeneration(GetLocalInt(oTarget, "PRC_SIZE_GENERATION")); + SetLocalInt(oTarget, "PRC_SIZE_GENERATION", nGeneration); + + // Store original size if not already stored - READ ACTUAL CURRENT SCALE + if(GetLocalFloat(oTarget, "PRC_ORIGINAL_SIZE") == 0.0f) + { + float fCurrentScale = GetObjectVisualTransform(oTarget, OBJECT_VISUAL_TRANSFORM_SCALE); + SetLocalFloat(oTarget, "PRC_ORIGINAL_SIZE", fCurrentScale); + } + + // Reset to original size + if(nChanges == 0) + { + float fOriginalSize = GetLocalFloat(oTarget, "PRC_ORIGINAL_SIZE"); + DelaySetVisualTransform(0.0f, oTarget, "PRC_SIZE_GENERATION", OBJECT_VISUAL_TRANSFORM_SCALE, fOriginalSize); + // DON'T delete PRC_ORIGINAL_SIZE here - keep it for future casts + return eBlank; + } + + // Get the original scale + float fOriginalScale = GetLocalFloat(oTarget, "PRC_ORIGINAL_SIZE"); + + // Calculate scale factor relative to original size + float fScale = fOriginalScale; + if(bEnlarge) + fScale = fOriginalScale * pow(1.5f, IntToFloat(nChanges)); + else + fScale = fOriginalScale * pow(0.5f, IntToFloat(nChanges)); + + // Create the effect link with sanctuary VFX + effect eReturn = EffectLinkEffects(EffectVisualEffect(VFX_DUR_SANCTUARY), + EffectVisualEffect(VFX_DUR_CESSATE_POSITIVE)); + + if(bEnlarge) + { + eReturn = EffectLinkEffects(eReturn, EffectAttackDecrease(nChanges)); + eReturn = EffectLinkEffects(eReturn, EffectAbilityDecrease(ABILITY_DEXTERITY, 2 * nChanges)); + eReturn = EffectLinkEffects(eReturn, EffectAbilityIncrease(ABILITY_STRENGTH, 2 * nChanges)); + eReturn = EffectLinkEffects(eReturn, EffectACDecrease(nChanges)); + eReturn = TagEffect(eReturn, "PRC_SIZE_INCREASE"); + } + else + { + eReturn = EffectLinkEffects(eReturn, EffectAttackIncrease(nChanges)); + eReturn = EffectLinkEffects(eReturn, EffectAbilityIncrease(ABILITY_DEXTERITY, 2 * nChanges)); + eReturn = EffectLinkEffects(eReturn, EffectAbilityDecrease(ABILITY_STRENGTH, 2 * nChanges)); + eReturn = EffectLinkEffects(eReturn, EffectACIncrease(nChanges)); + eReturn = TagEffect(eReturn, "PRC_SIZE_DECREASE"); + } + + // Apply visual transform using wrapper + DelaySetVisualTransform(0.0f, oTarget, "PRC_SIZE_GENERATION", OBJECT_VISUAL_TRANSFORM_SCALE, fScale); + + return eReturn; +} + + + + +/* effect EffectSizeChange(object oTarget, int nObjectType, int bEnlarge, int nChanges) +{ + effect eBlank; + + // Increment generation for any size change + int nGeneration = PRC_NextGeneration(GetLocalInt(oTarget, "PRC_SIZE_GENERATION")); + SetLocalInt(oTarget, "PRC_SIZE_GENERATION", nGeneration); + + // Store original size if not already stored (fixed check) + if(GetLocalFloat(oTarget, "PRC_ORIGINAL_SIZE") == 0.0f) + { + SetLocalFloat(oTarget, "PRC_ORIGINAL_SIZE", 1.0f); + } + + // Reset to original size + if(nChanges == 0) + { + float fOriginalSize = GetLocalFloat(oTarget, "PRC_ORIGINAL_SIZE"); + DelaySetVisualTransform(0.0f, oTarget, "PRC_SIZE_GENERATION", OBJECT_VISUAL_TRANSFORM_SCALE, fOriginalSize); + DeleteLocalFloat(oTarget, "PRC_ORIGINAL_SIZE"); + return eBlank; + } + + // Calculate scale factor using pow() for multi-step changes + float fScale = 1.0f; + if(bEnlarge) + fScale = pow(1.5f, IntToFloat(nChanges)); // 1.5, 2.25, 3.375... + else + fScale = pow(0.5f, IntToFloat(nChanges)); // 0.5, 0.25, 0.125... + + // Create the effect link based on enlarge/reduce + effect eReturn; + if(bEnlarge) + { + eReturn = EffectLinkEffects(EffectAttackDecrease(nChanges), + EffectAbilityDecrease(ABILITY_DEXTERITY, 2 * nChanges)); + eReturn = EffectLinkEffects(eReturn, EffectAbilityIncrease(ABILITY_STRENGTH, 2 * nChanges)); + eReturn = EffectLinkEffects(eReturn, EffectACDecrease(nChanges)); + eReturn = TagEffect(eReturn, "PRC_SIZE_INCREASE"); + } + else + { + eReturn = EffectLinkEffects(EffectAttackIncrease(nChanges), + EffectAbilityIncrease(ABILITY_DEXTERITY, 2 * nChanges)); + eReturn = EffectLinkEffects(eReturn, EffectAbilityDecrease(ABILITY_STRENGTH, 2 * nChanges)); + eReturn = EffectLinkEffects(eReturn, EffectACIncrease(nChanges)); + eReturn = TagEffect(eReturn, "PRC_SIZE_DECREASE"); + } + + // Apply visual transform using wrapper + DelaySetVisualTransform(0.0f, oTarget, "PRC_SIZE_GENERATION", OBJECT_VISUAL_TRANSFORM_SCALE, fScale); + + return eReturn; +} + */ + +//:: void main(){} \ No newline at end of file diff --git a/src/include/prc_inc_skills.nss b/src/include/prc_inc_skills.nss index bbca299..1bfc354 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 @@ -363,8 +363,11 @@ int PRCIsFlying(object oCreature) bFlying = TRUE; } if(!bFlying - && ((nWings > 0 && nWings < 79) || nWings == 90))//CEP and Project Q wing models - bFlying = TRUE; + && ((nWings > 0 && nWings < 79) + || (nWings > 1959 && nWings < 1962) + || (nWings > 1962 && nWings < 1966) + || nWings == 90))//CEP and Project Q wing models + bFlying = TRUE; if (GetHasSpellEffect(MOVE_SH_BALANCE_SKY, oCreature)) bFlying = TRUE; @@ -374,6 +377,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 98e812d..1c7ad71 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,6 +381,36 @@ 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; @@ -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); @@ -974,11 +1011,16 @@ int PRCMySavingThrow(int nSavingThrow, object oTarget, int nDC, int nSaveType = // Plague Resistant gives a +4 bonus on disease saves if(GetHasFeat(FEAT_PLAGUE_RESISTANT, oTarget)) nDC -= 4; + // Racial +2 vs disease saves + if(GetHasFeat(FEAT_RACE_HARDINESS_VS_DISEASE, oTarget)) + nDC -= 2; // +4/+2 bonus on saves against disease, done here if(GetLevelByClass(CLASS_TYPE_DREAD_NECROMANCER, oTarget) > 13) nDC -= 4; else if(GetLevelByClass(CLASS_TYPE_DREAD_NECROMANCER, oTarget) > 3) nDC -= 2; + + } else if(nSaveType == SAVING_THROW_TYPE_POISON) { @@ -2223,6 +2265,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 +2389,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..e2a3dfe 100644 --- a/src/include/prc_inc_switch.nss +++ b/src/include/prc_inc_switch.nss @@ -70,11 +70,13 @@ 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!!! */ - const string PRC_VERSION = "PRC 3.9.0"; + const string PRC_VERSION = "PRC8 4.76"; /* This variable MUST be updated every time 'assemble_spellbooks.bat' is run!!! */ @@ -89,11 +91,11 @@ * This allows material components in NWN through the materialcomp.2da * Just put the SpellID and UTC resref in, MINUS the .utc on the end. * This also requires the names of the items, formatted like so ("" included): "Object Name" - * + * * Set switch to 2 to activate this * Deducts gold instead of requiring material components * Put the gold value in the Cost column - + * Set switch to 3 to activate both at the same time * WARNING: This will slow spellcasting down due to 2da reads and inventory loops */ @@ -127,7 +129,7 @@ const string PRC_BIOWARE_NEUTRALIZE_POISON = "PRC_BIOWARE_NEUTRALIZE_P /** Remove the cap PRC added to this spell */ const string PRC_BIOWARE_REMOVE_DISEASE = "PRC_BIOWARE_REMOVE_DISEASE"; -/** +/** * This replaces the 3.0 Spell Focus bonuses with the 3.5 edition ones */ const string PRC_35_SPELL_FOCUS = "PRC_35_SPELL_FOCUS"; @@ -261,8 +263,8 @@ const string PRC_165_DEATH_IMMUNITY = "PRC_165_DEATH_IMMUNITY"; /* * PRC_ACTIVATE_MAX_SPELL_DC_CAP: activate a max cap on DC casted by creature/player - * PRC_SET_MAX_SPELL_DC_CAP: the max value ex: 99 - * + * PRC_SET_MAX_SPELL_DC_CAP: the max value ex: 99 + * */ const string PRC_ACTIVATE_MAX_SPELL_DC_CAP = "PRC_ACTIVATE_MAX_SPELL_DC_CAP"; const string PRC_SET_MAX_SPELL_DC_CAP = "PRC_SET_MAX_SPELL_DC_CAP"; @@ -289,8 +291,8 @@ const string PRC_DC_BASE_OVERRIDE = "PRC_DC_BASE_OVERRIDE"; const string PRC_DC_ADJUSTMENT = "PRC_DC_ADJUSTMENT"; /* - * By default when calculating caster level for characters with PrCs, the highest class rule will - * be used (ie. Bard 2/Wizard 4/Elemental Savant 6 - Wizard is the highest arcane class so levels + * By default when calculating caster level for characters with PrCs, the highest class rule will + * be used (ie. Bard 2/Wizard 4/Elemental Savant 6 - Wizard is the highest arcane class so levels * form PrC will be added to that class, and the caster level will be 2 for Bard and 10 for Wizard). * When this is set, the first class rule will be used (with the same character caster level would * be 8 for Bard and 4 for Wizard). @@ -570,12 +572,12 @@ const string PRC_SOUL_EATER_MAX_SLAVES = "PRC_SOUL_EATER_MAX_SLAVES"; * For the Psionic Slayer prestige class, this switch limits the Favored Enemy selection * to the Aberration racial type. * - * This switch is provided to allow builders to more closely represent the Pen and Paper + * This switch is provided to allow builders to more closely represent the Pen and Paper * Illithid Slayer class, instead of the broader Open Game License "Slayer" class. * - * Type: Int + * Type: Int * Values: 0 [Default] (Favored Enemy racial type is not limited) - * 1 (Favored Enemy race is limited to Aberration only) + * 1 (Favored Enemy race is limited to Aberration only) */ const string PRC_PSIONIC_SLAYER_FAV_ENEMY_ABERRATION_ONLY = "PRC_PSIONIC_SLAYER_FAV_ENEMY_ABERRATION_ONLY"; @@ -583,20 +585,20 @@ const string PRC_PSIONIC_SLAYER_FAV_ENEMY_ABERRATION_ONLY = "PRC_PSIONIC_S * For the Psionic Slayer prestige class, this switch requires a character to make a "kill" * of a specific type of creature before the class becomes available. * - * Use of this switch requires that the module builder add a "Psionic Slayer Kill Token" - * (included in the PRC Items) to the designated creature. + * Use of this switch requires that the module builder add a "Psionic Slayer Kill Token" + * (included in the PRC Items) to the designated creature. * - * Alternately, a script or item can be made that will run the script "prc_psysly_killt" + * Alternately, a script or item can be made that will run the script "prc_psysly_killt" * on the PC. This script will set the flag that allows the target PC to take the Psionic Slayer Class. - * Example code: - * ExecuteScript("prc_psysly_killt", oPC); // Where oPC is an player charcter object + * Example code: + * ExecuteScript("prc_psysly_killt", oPC); // Where oPC is an player charcter object * - * This switch is provided to allow builders to more closely represent the Pen and Paper + * This switch is provided to allow builders to more closely represent the Pen and Paper * Illithid Slayer class, instead of the broader Open Game License "Slayer" class. * - * Type: Int + * Type: Int * Values: 0 [Default] (Kill Token / Script NOT required for taking the Psionic Slayer Class) - * 1 (Kill Token / Script REQUIRED before the Psionic Slayer Class is available to take) + * 1 (Kill Token / Script REQUIRED before the Psionic Slayer Class is available to take) */ const string PRC_PSIONIC_SLAYER_REQUIRE_KILL_TOKEN = "PRC_PSIONIC_SLAYER_REQUIRE_KILL_TOKEN"; @@ -605,19 +607,19 @@ const string PRC_PSIONIC_SLAYER_REQUIRE_KILL_TOKEN = "PRC_PSIONIC_SLAYER_R * By default the Werewolf class uses the Bioware Polymorph effect to perfrom its * Hybrid Form Shapchange. * - * This switch allows the Werewolf class to be toggled to use the PRC Shifter - * Shapchange code instead. + * This switch allows the Werewolf class to be toggled to use the PRC Shifter + * Shapchange code instead. * - * Type: Int + * Type: Int * Values: 0 [Default] (Werewolf Hybrid Shapchange uses Bioware Polymorph) - * 1 (Werewolf Hybrid Shapchange uses PRC Shifter shape change code) + * 1 (Werewolf Hybrid Shapchange uses PRC Shifter shape change code) */ -const string PRC_WEREWOLF_HYBRID_USE_SHIFTER_SHAPCHANGE = "PRC_WEREWOLF_HYBRID_USE_SHIFTER_SHAPCHANGE"; +const string PRC_WEREWOLF_HYBRID_USE_SHIFTER_SHAPECHANGE = "PRC_WEREWOLF_HYBRID_USE_SHIFTER_SHAPECHANGE"; /** * Sets the max bonus for the PnP Shifter shifting systems * - * Type: Int + * Type: Int * Values: any greater than 0 */ const string PRC_PNP_SHIFTER_BONUS = "PRC_PNP_SHIFTER_BONUS"; @@ -781,7 +783,7 @@ const string PRC_STAFF_CASTER_LEVEL = "PRC_STAFF_CASTER_LEVEL"; /** * [DEFUNCT] * A wand must be equipped before it can cast a spell - * + * * Any value above 0 turns off the requirement to have a wand equipped to use it * * This switch is defunct, wands must *always* be equipped to use them. @@ -929,7 +931,7 @@ const string PRC_PNP_FAMILIAR_FEEDING = "PRC_PNP_FAMILIAR_FEEDING /** * Use PRC henchmen-familiars instead of BioWare's - this will allow - * new classes to have familiars, but summoned creatures will no longer + * new classes to have familiars, but summoned creatures will no longer * be 'true' familiars (ie. can't possess PRC familiar) */ const string PRC_FAMILIARS = "PRC_FAMILIARS"; @@ -1112,7 +1114,7 @@ const string PRC_SPELL_ALIGNMENT_RESTRICT = "PRC_SPELL_ALIGNMENT_REST * Disable registration of custom cohorts */ const string PRC_DISABLE_REGISTER_COHORTS = "PRC_DISABLE_REGISTER_COHORTS"; - + /* * Disable cohorts starting with gear */ @@ -1142,19 +1144,19 @@ const string PRC_SPELL_ALIGNMENT_RESTRICT = "PRC_SPELL_ALIGNMENT_REST * Medium armor is a 25% speed reduction, Heavy is a 33% reduction */ const string PRC_PNP_ARMOR_SPEED = "PRC_PNP_ARMOR_SPEED"; - + /* * Applies a 99% speed boost when out of combat * Warning that it will likely cause PCs to be overly speedy when combat starts * Potential problem causer */ const string PRC_FAST_TRAVEL_SPEED = "PRC_FAST_TRAVEL_SPEED"; - + /* * Applys a Discipline bonus equal to BAB to all characters if turned on * Bonus only applies to characters with 0 ranks in Discipline */ - const string PRC_PNP_KNOCKDOWN = "PRC_PNP_KNOCKDOWN"; + const string PRC_PNP_KNOCKDOWN = "PRC_PNP_KNOCKDOWN"; /* * by Bioware rules, PCs have approximatly a 7th faster movement than NPCs @@ -1202,8 +1204,11 @@ const string PRC_SPELL_ALIGNMENT_RESTRICT = "PRC_SPELL_ALIGNMENT_REST */ const string PRC_APPEARNCE_CHANGE_DISABLE = "PRC_APPEARNCE_CHANGE_DISABLE"; - - +/* + * Allow "Monk" gloves to merge with a creature weapons when Wildshaped. + * Will also merge bracers with creature hides when Wildshaped. +*/ +const string PRC_WILDSHAPE_ALLOWS_ARMS_SLOT = "PRC_WILDSHAPE_ALLOWS_ARMS_SLOT"; /******************************************************************************\ * Death/Bleeding system * @@ -1221,7 +1226,7 @@ const string PRC_PNP_DEATH_ENABLE = "PRC_PNP_DEATH_ENA * if FALSE, dont bleed just die * By PnP this would be 1 round, or 6 seconds */ -const string PRC_DEATH_OR_BLEED = "PRC_DEATH_OR_BLEED"; +const string PRC_DEATH_OR_BLEED = "PRC_DEATH_OR_BLEED"; /* * Damage when bleeding @@ -1314,7 +1319,7 @@ const string PRC_ACP_DELAY = "PRC_ACP_DELAY"; /****************************************************************************** -* File End switches +* File End switches ******************************************************************************/ /** @@ -1488,10 +1493,10 @@ const string PRC_POISON_IS_FOOD_SCRIPT_NAME = "PRC_POISON_IS_FOOD_SCRIP const string PRC_POISON_ALLOW_CLEAN_IN_EQUIP = "PRC_POISON_ALLOW_CLEAN_IN_EQUIP"; /** - * + * * Default: crafting requires only gold and xp */ -const string PRC_CRAFT_POISON_USE_INGREDIENST = "PRC_CRAFT_POISON_USE_INGREDIENST"; +const string PRC_CRAFT_POISON_USE_INGREDIENTS = "PRC_CRAFT_POISON_USE_INGREDIENTS"; /******************************************************************************\ * PRGT system switches * @@ -1540,10 +1545,10 @@ const string PRC_PSI_ASTRAL_CONSTRUCT_DUR_MOD = "PRC_PSI_ASTRAL_CONSTRUCT /** * If this is set, The Astral Seed power will attempt to use the provided string as - * the ResRef to create the Astral Seed object instead of the of the phylactery + * the ResRef to create the Astral Seed object instead of the of the phylactery * ResRef("x2_plc_phylact"). * May be used by builders to create an object that CAN be destroyed, or has other traits, - * as desired. + * as desired. * Type: String * Values: "" [Default] (Blank, or not set: Use default phylactery ResRef for Astral Seed) * STRING (Entered String will be used as the ResRef of created Astral Seed object) @@ -1551,14 +1556,14 @@ const string PRC_PSI_ASTRAL_CONSTRUCT_DUR_MOD = "PRC_PSI_ASTRAL_CONSTRUCT const string PRC_PSI_ASTRAL_SEED_RESREF = "PRC_PSI_ASTRAL_SEED_RESREF"; /** - * By default the Astral Seed power respawns the player, and then makes them immobile for + * By default the Astral Seed power respawns the player, and then makes them immobile for * 24-game-hours. - * If this switch is set, it will adjust the imobility time period; shortening it, lengthing it, or + * If this switch is set, it will adjust the imobility time period; shortening it, lengthing it, or * effectively eliminating it. * Type: Int * Values: 0 [Default] (Not set: Use default 24 hour duration) * -1 (Any negative value will result in a fixed duratoion of 2 seconds, which effectively eliminates the wait period) - * 1 (Any potitive value: multiply duration by the value provided and then divide result by 1000. + * 1 (Any potitive value: multiply duration by the value provided and then divide result by 1000. * Values less than 1000 will shorten the duration, values higher than 1000 will lengthen it.) */ const string PRC_PSI_ASTRAL_SEED_RESPAWN_DELAY_X1000 = "PRC_PSI_ASTRAL_SEED_RESPAWN_DELAY_X1000"; @@ -1569,7 +1574,7 @@ const string PRC_PSI_ASTRAL_SEED_RESPAWN_DELAY_X1000 = "PRC_PSI_ASTRAL_SE * If this flag is set, the XP loss is completely eliminated. The standard PRC event hook script * of "prc_pw_astralseed" may be used to script any additional effects to occure upon Astral Seed * respawning, including scripting specific XP loss amount. - * Type: Int + * Type: Int * Values: 0 [Default] (Not set: lose 1 level worth of XP upon Astral Seed respawn) * 1 (Any potitive value: Remove all XP loss from Astral Seed respawn) */ @@ -1906,7 +1911,7 @@ const string PRC_CRAFT_TIMER_MAX = "PRC_CRAFT_TIMER_MAX"; */ const string PRC_CRAFT_TIMER_MIN = "PRC_CRAFT_TIMER_MIN"; -/** +/* * These three switches modify Bioware crafting so that the items produced have the * casterlevel of the spellcaster who created them. Normally under Bioware, it is possible * for a level 3 caster to produce level 9 items and for a level 40 caster to only produce @@ -1952,6 +1957,23 @@ 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 scepters + */ +const string PRC_CRAFT_SCEPTER_CASTER_LEVEL = "PRC_CRAFT_SCEPTER_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 +1983,59 @@ 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 scepters + * note that adding a second spell costs 75% + * defaults to 750 + */ +const string PRC_X2_CRAFTSCEPTER_COSTMODIFIER = "PRC_X2_CRAFTSCEPTER_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 @@ -2321,7 +2357,7 @@ const string PRC_XP_GIVE_XP_TO_NPCS = "PRC_XP_GIVE_XP_TO_NPCS"; /** * Setting this switch will turn off the messages about being too far awy to gain XP */ -const string PRC_XP_DISABLE_SPAM = "PRC_XP_DISABLE_SPAM"; +const string PRC_XP_DISABLE_SPAM = "PRC_XP_DISABLE_SPAM"; /** * PCs must be in the same area as the CR to gain XP. @@ -2880,13 +2916,13 @@ const string PRC_PERFECTED_MAP_MULTIPLIER = "PRC_PERFECTED_MAP_MULTIP \******************************************************************************/ /** - * Sets how many seconds it takes to contact a vestige. + * Sets how many seconds it takes to contact a vestige. * Any number less than 6 is ignored */ const string PRC_CONTACT_VESTIGE_TIMER = "PRC_CONTACT_VESTIGE_TIMER"; /** - * Sets how many seconds it takes to bind a vestige. + * Sets how many seconds it takes to bind a vestige. * Any number less than 12 is ignored */ const string PRC_BIND_VESTIGE_TIMER = "PRC_BIND_VESTIGE_TIMER"; @@ -2920,6 +2956,20 @@ const string PRC_PW_SECURITY_CD_CHECK = "PRC_PW_SECURITY_CD_CHECK"; */ const string PRC_DEBUG = "PRC_DEBUG"; +/******************************************************************************\ +* Duration NUI Switches * +\******************************************************************************/ + +/** + * Toggles allowing player to remove friendly PC spells on player through Duration NUI + * instead of just their own spells. + */ +const string PRC_ALLOWED_TO_REMOVE_FRIENDLY_SPELLS = "PRC_ALLOWED_TO_REMOVE_FRIENDLY_SPELLS"; +/** + * Toggles allowing players to see the duration of hostile spells on them. + */ +const string PRC_ALLOWED_TO_SEE_HOSTILE_SPELLS = "PRC_ALLOWED_TO_SEE_HOSTILE_SPELLS"; + diff --git a/src/include/prc_inc_turning.nss b/src/include/prc_inc_turning.nss index 5504819..2b6852f 100644 --- a/src/include/prc_inc_turning.nss +++ b/src/include/prc_inc_turning.nss @@ -10,21 +10,12 @@ //::////////////////////////////////////////////// //::////////////////////////////////////////////// +#include "prc_spell_const" + ////////////////////////////////////////////////// /* 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 +182,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 +388,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_unarmed.nss b/src/include/prc_inc_unarmed.nss index a08f912..27c3245 100644 --- a/src/include/prc_inc_unarmed.nss +++ b/src/include/prc_inc_unarmed.nss @@ -91,6 +91,107 @@ float DamageAvg(int iDamage); /* Function defintions */ ////////////////////////////////////////////////// +// StepDie: increases a damage die by 'nSteps' steps according to d20 SRD progression +// Increment the unarmed damage by nSteps +int StepDie(int nDamage, int nSteps) +{ + int i; + for (i = 0; i < nSteps; i++) + { + switch (nDamage) + { + // 1-dice increments + case IP_CONST_MONSTERDAMAGE_1d2: nDamage = IP_CONST_MONSTERDAMAGE_1d3; break; + case IP_CONST_MONSTERDAMAGE_1d3: nDamage = IP_CONST_MONSTERDAMAGE_1d4; break; + case IP_CONST_MONSTERDAMAGE_1d4: nDamage = IP_CONST_MONSTERDAMAGE_1d6; break; + case IP_CONST_MONSTERDAMAGE_1d6: nDamage = IP_CONST_MONSTERDAMAGE_1d8; break; + case IP_CONST_MONSTERDAMAGE_1d8: nDamage = IP_CONST_MONSTERDAMAGE_1d10; break; + case IP_CONST_MONSTERDAMAGE_1d10: nDamage = IP_CONST_MONSTERDAMAGE_1d12; break; + case IP_CONST_MONSTERDAMAGE_1d12: nDamage = IP_CONST_MONSTERDAMAGE_2d8; break; + + // 2-dice increments + //case IP_CONST_MONSTERDAMAGE_2d3: nDamage = IP_CONST_MONSTERDAMAGE_2d4; break; + case IP_CONST_MONSTERDAMAGE_2d4: nDamage = IP_CONST_MONSTERDAMAGE_2d6; break; + case IP_CONST_MONSTERDAMAGE_2d6: nDamage = IP_CONST_MONSTERDAMAGE_2d8; break; + case IP_CONST_MONSTERDAMAGE_2d8: nDamage = IP_CONST_MONSTERDAMAGE_2d10; break; + case IP_CONST_MONSTERDAMAGE_2d10: nDamage = IP_CONST_MONSTERDAMAGE_2d12; break; + case IP_CONST_MONSTERDAMAGE_2d12: nDamage = IP_CONST_MONSTERDAMAGE_3d10; break; + + // 3-dice increments + case IP_CONST_MONSTERDAMAGE_3d4: nDamage = IP_CONST_MONSTERDAMAGE_3d6; break; + case IP_CONST_MONSTERDAMAGE_3d6: nDamage = IP_CONST_MONSTERDAMAGE_3d8; break; + case IP_CONST_MONSTERDAMAGE_3d8: nDamage = IP_CONST_MONSTERDAMAGE_3d10; break; + case IP_CONST_MONSTERDAMAGE_3d10: nDamage = IP_CONST_MONSTERDAMAGE_3d12; break; + case IP_CONST_MONSTERDAMAGE_3d12: nDamage = IP_CONST_MONSTERDAMAGE_4d8; break; + + // 4-dice increments + case IP_CONST_MONSTERDAMAGE_4d4: nDamage = IP_CONST_MONSTERDAMAGE_4d6; break; + case IP_CONST_MONSTERDAMAGE_4d6: nDamage = IP_CONST_MONSTERDAMAGE_4d8; break; + case IP_CONST_MONSTERDAMAGE_4d8: nDamage = IP_CONST_MONSTERDAMAGE_4d10; break; + case IP_CONST_MONSTERDAMAGE_4d10: nDamage = IP_CONST_MONSTERDAMAGE_4d12; break; + case IP_CONST_MONSTERDAMAGE_4d12: nDamage = IP_CONST_MONSTERDAMAGE_5d8; break; + + // 5-dice increments + case IP_CONST_MONSTERDAMAGE_5d4: nDamage = IP_CONST_MONSTERDAMAGE_5d6; break; + case IP_CONST_MONSTERDAMAGE_5d6: nDamage = IP_CONST_MONSTERDAMAGE_5d8; break; + case IP_CONST_MONSTERDAMAGE_5d8: nDamage = IP_CONST_MONSTERDAMAGE_5d10; break; + case IP_CONST_MONSTERDAMAGE_5d10: nDamage = IP_CONST_MONSTERDAMAGE_5d12; break; + case IP_CONST_MONSTERDAMAGE_5d12: nDamage = IP_CONST_MONSTERDAMAGE_6d10; break; + + // 6-dice increments + //case IP_CONST_MONSTERDAMAGE_6d4: nDamage = IP_CONST_MONSTERDAMAGE_6d6; break; + case IP_CONST_MONSTERDAMAGE_6d6: nDamage = IP_CONST_MONSTERDAMAGE_6d8; break; + case IP_CONST_MONSTERDAMAGE_6d8: nDamage = IP_CONST_MONSTERDAMAGE_6d10; break; + case IP_CONST_MONSTERDAMAGE_6d10: nDamage = IP_CONST_MONSTERDAMAGE_6d12; break; + case IP_CONST_MONSTERDAMAGE_6d12: nDamage = IP_CONST_MONSTERDAMAGE_7d10; break; + + // 7-dice increments + case IP_CONST_MONSTERDAMAGE_7d4: nDamage = IP_CONST_MONSTERDAMAGE_7d6; break; + case IP_CONST_MONSTERDAMAGE_7d6: nDamage = IP_CONST_MONSTERDAMAGE_7d8; break; + case IP_CONST_MONSTERDAMAGE_7d8: nDamage = IP_CONST_MONSTERDAMAGE_7d10; break; + case IP_CONST_MONSTERDAMAGE_7d10: nDamage = IP_CONST_MONSTERDAMAGE_7d12; break; + case IP_CONST_MONSTERDAMAGE_7d12: nDamage = IP_CONST_MONSTERDAMAGE_9d10; break; + + // 8-dice increments + //case IP_CONST_MONSTERDAMAGE_8d4: nDamage = IP_CONST_MONSTERDAMAGE_8d6; break; + case IP_CONST_MONSTERDAMAGE_8d6: nDamage = IP_CONST_MONSTERDAMAGE_8d8; break; + case IP_CONST_MONSTERDAMAGE_8d8: nDamage = IP_CONST_MONSTERDAMAGE_8d10; break; + case IP_CONST_MONSTERDAMAGE_8d10: nDamage = IP_CONST_MONSTERDAMAGE_8d12; break; + case IP_CONST_MONSTERDAMAGE_8d12: nDamage = IP_CONST_MONSTERDAMAGE_10d10; break; + + // 9-dice increments + //case IP_CONST_MONSTERDAMAGE_9d4: nDamage = IP_CONST_MONSTERDAMAGE_9d6; break; + case IP_CONST_MONSTERDAMAGE_9d6: nDamage = IP_CONST_MONSTERDAMAGE_9d8; break; + case IP_CONST_MONSTERDAMAGE_9d8: nDamage = IP_CONST_MONSTERDAMAGE_9d10; break; + case IP_CONST_MONSTERDAMAGE_9d10: nDamage = IP_CONST_MONSTERDAMAGE_9d12; break; + case IP_CONST_MONSTERDAMAGE_9d12: nDamage = IP_CONST_MONSTERDAMAGE_6d20; break; + + // 10-dice increments + //case IP_CONST_MONSTERDAMAGE_10d4: nDamage = IP_CONST_MONSTERDAMAGE_10d6; break; + case IP_CONST_MONSTERDAMAGE_10d6: nDamage = IP_CONST_MONSTERDAMAGE_10d8; break; + case IP_CONST_MONSTERDAMAGE_10d8: nDamage = IP_CONST_MONSTERDAMAGE_10d10; break; + case IP_CONST_MONSTERDAMAGE_10d10: nDamage = IP_CONST_MONSTERDAMAGE_10d12; break; + case IP_CONST_MONSTERDAMAGE_10d12: nDamage = IP_CONST_MONSTERDAMAGE_7d20; break; + + // d20 increments + case IP_CONST_MONSTERDAMAGE_1d20: nDamage = IP_CONST_MONSTERDAMAGE_3d8; break; + case IP_CONST_MONSTERDAMAGE_2d20: nDamage = IP_CONST_MONSTERDAMAGE_4d12; break; + case IP_CONST_MONSTERDAMAGE_3d20: nDamage = IP_CONST_MONSTERDAMAGE_8d8; break; + case IP_CONST_MONSTERDAMAGE_4d20: nDamage = IP_CONST_MONSTERDAMAGE_8d12; break; + case IP_CONST_MONSTERDAMAGE_5d20: nDamage = IP_CONST_MONSTERDAMAGE_9d12; break; //:: Everything breaks down here + case IP_CONST_MONSTERDAMAGE_6d20: nDamage = IP_CONST_MONSTERDAMAGE_1d20; break; + case IP_CONST_MONSTERDAMAGE_7d20: nDamage = IP_CONST_MONSTERDAMAGE_8d20; break; + case IP_CONST_MONSTERDAMAGE_8d20: nDamage = IP_CONST_MONSTERDAMAGE_9d20; break; + case IP_CONST_MONSTERDAMAGE_9d20: nDamage = IP_CONST_MONSTERDAMAGE_10d20; break; + + default: break; // top tier or unknown + } + } + + return nDamage; +} + + // Clean up any extras in the inventory. void CleanExtraFists(object oCreature) { @@ -168,12 +269,169 @@ void ApplyUnarmedAttackEffects(object oCreature) } // Determines the amount of damage a character can do. -// IoDM: +1 dice at level 4, +2 dice at level 8 +// IoDM: +1 die at level 4, +2 dice at level 8 // Sacred Fist: Levels add to monk levels, or stand alone as monk levels. // Shou: 1d6 at level 1, 1d8 at level 2, 1d10 at level 3, 2d6 at level 5 // Monk: 1d6 at level 1, 1d8 at level 4, 1d10 at level 8, 2d6 at level 12, 2d8 at level 16, 2d10 at level 20 // Frostrager: 1d6 at level 1, 1d8 at level 4 int FindUnarmedDamage(object oCreature) +{ + int iDamage = 0; + int iMonk = GetLevelByClass(CLASS_TYPE_MONK, oCreature) + GetLocalInt(oCreature, "LiPengMonk"); + int iShou = GetLevelByClass(CLASS_TYPE_SHOU, oCreature); + int iBrawler = GetLevelByClass(CLASS_TYPE_BRAWLER, oCreature); + int iSacredFist = GetLevelByClass(CLASS_TYPE_SACREDFIST, oCreature); + int iEnlightenedFist = GetLevelByClass(CLASS_TYPE_ENLIGHTENEDFIST, oCreature); + int iHenshin = GetLevelByClass(CLASS_TYPE_HENSHIN_MYSTIC, oCreature); + int iZuoken = GetLevelByClass(CLASS_TYPE_FIST_OF_ZUOKEN, oCreature); + int iShadowSunNinja = GetLevelByClass(CLASS_TYPE_SHADOW_SUN_NINJA, oCreature); + int iFrost = GetLevelByClass(CLASS_TYPE_FROSTRAGER, oCreature); + int iAscetic = GetLevelByClass(CLASS_TYPE_NINJA, oCreature); + int iRonove = 0; + int iMonkDamage = 1; + int iShouDamage = 1; + int iBrawlerDamage = 1; + int iFrostDamage = 1; + int iSUSDamage = 1; + int iDieIncrease = 0; + int iSize; + + if (GetHasSpellEffect(VESTIGE_RONOVE, oCreature) && GetLevelByClass(CLASS_TYPE_BINDER, oCreature)) + iRonove = GetLocalInt(oCreature, "RonovesFists"); + + //:: Determine creature size + if( GetIsPolyMorphedOrShifted(oCreature) || GetPRCSwitch(PRC_APPEARANCE_SIZE)) + { + iSize = PRCGetCreatureSize(oCreature) - CREATURE_SIZE_MEDIUM + 5; + } + else + { + iSize = 5; // medium + if (GetHasFeat(FEAT_TINY, oCreature)) iSize = 3; + if (GetHasFeat(FEAT_SMALL, oCreature)) iSize = 4; + if (GetHasFeat(FEAT_LARGE, oCreature)) iSize = 6; + if (GetHasFeat(FEAT_HUGE, oCreature)) iSize = 7; + iSize += PRCGetCreatureSize(oCreature) - PRCGetCreatureSize(oCreature, PRC_SIZEMASK_NONE); + if (iSize < 1) iSize = 1; + if (iSize > 9) iSize = 9; + } + + // Sacred Fist code break protection + if (GetHasFeat(FEAT_SF_CODE, oCreature)) iSacredFist = 0; + + // Combine monk-like levels + iMonk += iSacredFist + iHenshin + iEnlightenedFist + iShou + iZuoken + iShadowSunNinja; + + // Superior Unarmed Strike + if (GetHasFeat(FEAT_SUPERIOR_UNARMED_STRIKE, oCreature)) + { + iMonk += 4; + int nHD = GetHitDice(oCreature); + if (nHD >= 16) iSUSDamage = IP_CONST_MONSTERDAMAGE_2d6; + else if (nHD >= 12) iSUSDamage = IP_CONST_MONSTERDAMAGE_1d10; + else if (nHD >= 8) iSUSDamage = IP_CONST_MONSTERDAMAGE_1d8; + else if (nHD >= 4) iSUSDamage = IP_CONST_MONSTERDAMAGE_1d6; + else if (nHD >= 3) iSUSDamage = IP_CONST_MONSTERDAMAGE_1d4; + } + + // Ascetic Stalker + if (GetHasFeat(FEAT_ASCETIC_STALKER, oCreature)) + iMonk += iAscetic; + + // Cap monk progression + if (iMonk > 16 && !GetPRCSwitch(PRC_3_5e_FIST_DAMAGE)) iMonk = 16; + else if (iMonk > 20) iMonk = 20; + + // Ronove replacement + if (iRonove > iMonk) iMonk = iRonove; + + // Monk damage calculation (2DA row) + if (iMonk > 0) iMonkDamage = iMonk / 4 + 3; + if (iSize == 5 && iMonkDamage == 7 && !GetPRCSwitch(PRC_3_5e_FIST_DAMAGE)) + iMonkDamage = 8; + + // Shou Disciple base damage + if (iShou > 0) + { + int nRow; + if (iShou == 1) nRow = 3; + else if (iShou == 2) nRow = 4; + else if (iShou == 3) nRow = 5; + else if (iShou == 4) nRow = 5; + else if (iShou == 5) nRow = 6; + else nRow = 3; + + if (nRow > 6) nRow = 6; + + iShouDamage = StringToInt(Get2DACache("unarmed_dmg", "size" + IntToString(iSize), nRow)); + } + + // Frostrager + if (iFrost > 0) iFrostDamage = IP_CONST_MONSTERDAMAGE_1d6; + if (iFrost > 3) iFrostDamage = IP_CONST_MONSTERDAMAGE_1d8; + + // Brawler + if (iBrawler > 0) iBrawlerDamage = iBrawler / 6 + 3; + if (iBrawler >= 36) iBrawlerDamage += 2; + + // Armor/shield penalties + if (iMonkDamage > 1) + { + object oArmor = GetItemInSlot(INVENTORY_SLOT_CHEST, oCreature); + object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oCreature); + int bShieldEq = GetBaseItemType(oShield) == BASE_ITEM_SMALLSHIELD || + GetBaseItemType(oShield) == BASE_ITEM_LARGESHIELD || + GetBaseItemType(oShield) == BASE_ITEM_TOWERSHIELD; + if (GetBaseAC(oArmor) > 0 || bShieldEq) + iMonkDamage = 1; + } + + if (iShouDamage > 1) + { + object oArmor = GetItemInSlot(INVENTORY_SLOT_CHEST, oCreature); + object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oCreature); + int bShieldEq = GetBaseItemType(oShield) == BASE_ITEM_SMALLSHIELD || + GetBaseItemType(oShield) == BASE_ITEM_LARGESHIELD || + GetBaseItemType(oShield) == BASE_ITEM_TOWERSHIELD; + if (GetBaseAC(oArmor) > 3 || bShieldEq) + iShouDamage = 1; + } + + // Determine IoDM die increase + if (GetHasFeat(FEAT_INCREASE_DAMAGE2, oCreature)) iDieIncrease = 2; + else if (GetHasFeat(FEAT_INCREASE_DAMAGE1, oCreature)) iDieIncrease = 1; + + // Lookup monk damage in 2DA + iMonkDamage = StringToInt(Get2DACache("unarmed_dmg","size" + IntToString(iSize), iMonkDamage)); + + // 3.0e monk special cases + if (iSize <= 5 && !GetPRCSwitch(PRC_3_5e_FIST_DAMAGE)) + { + if (iMonkDamage == IP_CONST_MONSTERDAMAGE_2d6) iMonkDamage = IP_CONST_MONSTERDAMAGE_1d12; + if (iMonkDamage == IP_CONST_MONSTERDAMAGE_2d10) iMonkDamage = IP_CONST_MONSTERDAMAGE_1d20; + } + + // Apply IoDM die increase last, after 2DA lookups + if (iMonkDamage > 0) iMonkDamage = StepDie(iMonkDamage, iDieIncrease); + if (iShouDamage > 0) iShouDamage = StepDie(iShouDamage, iDieIncrease); + if (iBrawlerDamage > 0) iBrawlerDamage = StepDie(iBrawlerDamage, iDieIncrease); + if (iFrostDamage > 0) iFrostDamage = StepDie(iFrostDamage, iDieIncrease); + if (iSUSDamage > 0) iSUSDamage = StepDie(iSUSDamage, iDieIncrease); + + // Select best damage + iDamage = iMonkDamage; + iDamage = (DamageAvg(iShouDamage ) > DamageAvg(iDamage)) ? iShouDamage : iDamage; + iDamage = (DamageAvg(iFrostDamage ) > DamageAvg(iDamage)) ? iFrostDamage : iDamage; + iDamage = (DamageAvg(iSUSDamage ) > DamageAvg(iDamage)) ? iSUSDamage : iDamage; + iDamage = (DamageAvg(iBrawlerDamage) > DamageAvg(iDamage)) ? iBrawlerDamage : iDamage; + + if (DEBUG) DoDebug("prc_inc_unarmed: iDamage "+IntToString(iDamage)); + + return iDamage; +} + + +/* int FindUnarmedDamage(object oCreature) { int iDamage = 0; int iMonk = GetLevelByClass(CLASS_TYPE_MONK, oCreature) + GetLocalInt(oCreature, "LiPengMonk"); @@ -195,36 +453,30 @@ int FindUnarmedDamage(object oCreature) int iDieIncrease = 0; int iSize; - if (GetHasSpellEffect(VESTIGE_RONOVE, oCreature) && GetLevelByClass(CLASS_TYPE_BINDER, oCreature)) iRonove = GetLocalInt(oCreature, "RonovesFists"); + if (GetHasSpellEffect(VESTIGE_RONOVE, oCreature) && GetLevelByClass(CLASS_TYPE_BINDER, oCreature)) + iRonove = GetLocalInt(oCreature, "RonovesFists"); - // if the creature is shifted, use model size - // otherwise, we want to stick to what the feats say they "should" be. - // No making pixies with Dragon Appearance for "huge" fist damage. - if( GetIsPolyMorphedOrShifted(oCreature) - || GetPRCSwitch(PRC_APPEARANCE_SIZE)) + // Determine creature size + if( GetIsPolyMorphedOrShifted(oCreature) || GetPRCSwitch(PRC_APPEARANCE_SIZE)) { - iSize = PRCGetCreatureSize(oCreature) - CREATURE_SIZE_MEDIUM + 5; // medium is size 5 for us + iSize = PRCGetCreatureSize(oCreature) - CREATURE_SIZE_MEDIUM + 5; } else { - // Determine creature size by feats. - iSize = 5; // medium is size 5 for us + iSize = 5; // medium if (GetHasFeat(FEAT_TINY, oCreature)) iSize = 3; if (GetHasFeat(FEAT_SMALL, oCreature)) iSize = 4; if (GetHasFeat(FEAT_LARGE, oCreature)) iSize = 6; if (GetHasFeat(FEAT_HUGE, oCreature)) iSize = 7; - // include size changes iSize += PRCGetCreatureSize(oCreature) - PRCGetCreatureSize(oCreature, PRC_SIZEMASK_NONE); - // cap if needed if (iSize < 1) iSize = 1; if (iSize > 9) iSize = 9; } - // Sacred Fist cannot add their levels if they've broken their code. + // Sacred Fist code break protection if (GetHasFeat(FEAT_SF_CODE, oCreature)) iSacredFist = 0; - // several classes add their levels to the monk class, - // or use monk progression if the character has no monk levels + // Combine monk-like levels iMonk += iSacredFist + iHenshin + iEnlightenedFist + iShou + iZuoken + iShadowSunNinja; // Superior Unarmed Strike @@ -232,49 +484,66 @@ int FindUnarmedDamage(object oCreature) { iMonk += 4; int nHD = GetHitDice(oCreature); - if (nHD >= 16) iSUSDamage = IP_CONST_MONSTERDAMAGE_2d6; + if (nHD >= 16) iSUSDamage = IP_CONST_MONSTERDAMAGE_2d6; else if (nHD >= 12) iSUSDamage = IP_CONST_MONSTERDAMAGE_1d10; - else if (nHD >= 8) iSUSDamage = IP_CONST_MONSTERDAMAGE_1d8; - else if (nHD >= 4) iSUSDamage = IP_CONST_MONSTERDAMAGE_1d6; - else if (nHD >= 3) iSUSDamage = IP_CONST_MONSTERDAMAGE_1d4; + else if (nHD >= 8) iSUSDamage = IP_CONST_MONSTERDAMAGE_1d8; + else if (nHD >= 4) iSUSDamage = IP_CONST_MONSTERDAMAGE_1d6; + else if (nHD >= 3) iSUSDamage = IP_CONST_MONSTERDAMAGE_1d4; } // Ascetic Stalker if (GetHasFeat(FEAT_ASCETIC_STALKER, oCreature)) iMonk += iAscetic; - // In 3.0e, Monk progression stops after level 16: - if (iMonk > 16 && !GetPRCSwitch(PRC_3_5e_FIST_DAMAGE) ) iMonk = 16; - // in 3.5e, monk progression stops at 20. - else if(iMonk > 20) iMonk = 20; + // Cap monk progression + if (iMonk > 16 && !GetPRCSwitch(PRC_3_5e_FIST_DAMAGE)) iMonk = 16; + else if (iMonk > 20) iMonk = 20; - // Ronove is in place of monk, does not stack + // Ronove replacement if (iRonove > iMonk) iMonk = iRonove; - // monks damage progesses every four levels, starts at 1d6 - if (iMonk > 0) - iMonkDamage = iMonk / 4 + 3; + // Monk damage calculation + if (iMonk > 0) iMonkDamage = iMonk / 4 + 3; - // For medium monks in 3.0e skip 2d8 and go to 1d20 - if(iSize == 5 && iMonkDamage == 7 && !GetPRCSwitch(PRC_3_5e_FIST_DAMAGE) ) iMonkDamage = 8; + if(iSize == 5 && iMonkDamage == 7 && !GetPRCSwitch(PRC_3_5e_FIST_DAMAGE)) + iMonkDamage = 8; // Shou Disciple either adds its level to existing class or does its own damage, depending // on which is better. Here we will determine how much damage the Shou Disciple does // without stacking. - if (iShou > 0) iShouDamage = iShou + 2; // Lv. 1: 1d6, Lv. 2: 1d8, Lv. 3: 1d10 - if (iShou > 3) iShouDamage--; // Lv. 4: 1d10, Lv. 5: 2d6 - iShouDamage = StringToInt(Get2DACache("unarmed_dmg","size" + IntToString(iSize), iShouDamage)); - - // Frostrager does not stack with other damage types - if (iFrost > 0) iFrostDamage = IP_CONST_MONSTERDAMAGE_1d6; // Lv. 1: 1d6 - if (iFrost > 3) iFrostDamage = IP_CONST_MONSTERDAMAGE_1d8; // Lv. 3: 1d8 + //if (iShou > 0) iShouDamage = iShou + 2; // Lv. 1: 1d6, Lv. 2: 1d8, Lv. 3: 1d10 + //if (iShou > 3) iShouDamage--; // Lv. 4: 1d10, Lv. 5: 2d6 + //iShouDamage = StringToInt(Get2DACache("unarmed_dmg","size" + IntToString(iSize), iShouDamage)); + + if (iShou > 0) + { + // Determine 2DA row for Shou progression + int nRow; + if (iShou == 1) nRow = 3; // monk1 + else if (iShou == 2) nRow = 4; // monk2 + else if (iShou == 3) nRow = 5; // monk3 + else if (iShou == 4) nRow = 6; // monk4 + else if (iShou == 5) nRow = 7; // monk5 + else if (iShou == 6) nRow = 8; // monk6 + else if (iShou == 7) nRow = 9; // monk7 + else nRow = 10; // monk8+ + + nRow += iDieIncrease; + if (nRow > 10) nRow = 10; // clamp to max row - // Brawler follows monk progression except for the last one (3d8) - if (iBrawler > 0) iBrawlerDamage = iBrawler / 6 + 3; // 1d6, 1d8, 1d10, 2d6, 2d8, 2d10 - if (iBrawler >= 36) iBrawlerDamage += 2; // 3d8 + // Lookup damage in unarmed_damage.2da using size column + iShouDamage = StringToInt(Get2DACache("unarmed_dmg","size" + IntToString(iSize), nRow)); + } - // Monks and monk-like classes deal no additional damage when wearing any armor, at - // least in NWN. This is to reflect that. No shields too. + // Frostrager + if (iFrost > 0) iFrostDamage = IP_CONST_MONSTERDAMAGE_1d6; + if (iFrost > 3) iFrostDamage = IP_CONST_MONSTERDAMAGE_1d8; + + // Brawler + if (iBrawler > 0) iBrawlerDamage = iBrawler / 6 + 3; + if (iBrawler >= 36) iBrawlerDamage += 2; + + // Armor/shield penalties if (iMonkDamage > 1) { object oArmor = GetItemInSlot(INVENTORY_SLOT_CHEST, oCreature); @@ -284,13 +553,10 @@ int FindUnarmedDamage(object oCreature) GetBaseItemType(oShield) == BASE_ITEM_TOWERSHIELD; if (GetBaseAC(oArmor) > 0 || bShieldEq) - { iMonkDamage = 1; - } } -// Shou Disciples can wear light armor - if (iShouDamage > 1) + if (iShouDamage > 1) { object oArmor = GetItemInSlot(INVENTORY_SLOT_CHEST, oCreature); object oShield = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oCreature); @@ -299,53 +565,31 @@ int FindUnarmedDamage(object oCreature) GetBaseItemType(oShield) == BASE_ITEM_TOWERSHIELD; if (GetBaseAC(oArmor) > 3 || bShieldEq) - { - iShouDamage = 1; - } + iShouDamage = 1; } - // For Initiate of Draconic Mysteries - if (GetHasFeat(FEAT_INCREASE_DAMAGE2, oCreature)) iDieIncrease = 2; - else if (GetHasFeat(FEAT_INCREASE_DAMAGE1, oCreature)) iDieIncrease = 1; - - //:: Expansion / Compression powers - int nExpansion = GetLocalInt(oCreature, "PRC_Power_Expansion_SizeIncrease"); - int nCompression = GetLocalInt(oCreature, "PRC_Power_Compression_SizeReduction"); - - if (nExpansion) - { - iSize += nExpansion; - } - - if (nCompression) - { - iSize -= nCompression; - } + // IoDM die increase + if (GetHasFeat(FEAT_INCREASE_DAMAGE2, oCreature)) iDieIncrease += 2; + else if (GetHasFeat(FEAT_INCREASE_DAMAGE1, oCreature)) iDieIncrease += 1; iMonkDamage += iDieIncrease; iShouDamage += iDieIncrease; iBrawlerDamage += iDieIncrease; iFrostDamage += iDieIncrease; iSUSDamage += iDieIncrease; - - //FloatingTextStringOnCreature("prc_inc_unarmed: Size is: "+IntToString(iSize)+".", oCreature); - //FloatingTextStringOnCreature("prc_inc_unarmed: Pre 2DA Lookup >> iMonkDamage = "+IntToString(iMonkDamage)+".", oCreature); - // now, read the damage from the table in unarmed_dmg.2da + // Lookup final monk damage in 2DA iMonkDamage = StringToInt(Get2DACache("unarmed_dmg","size" + IntToString(iSize), iMonkDamage)); - iShouDamage = StringToInt(Get2DACache("unarmed_dmg","size" + IntToString(iSize), iShouDamage)); - - //FloatingTextStringOnCreature("prc_inc_unarmed: Post 2DA Lookup >> iMonkDamage = "+IntToString(iMonkDamage)+".", oCreature); - // Medium+ monks have some special values on the table in 3.0: + // 3.0e monk special cases if (iSize >= 5 && !GetPRCSwitch(PRC_3_5e_FIST_DAMAGE)) { if (iMonkDamage == IP_CONST_MONSTERDAMAGE_2d6) iMonkDamage = IP_CONST_MONSTERDAMAGE_1d12; if (iMonkDamage == IP_CONST_MONSTERDAMAGE_2d10) iMonkDamage = IP_CONST_MONSTERDAMAGE_1d20; } + // Select best damage iDamage = iMonkDamage; - // Future unarmed classes: if you do your own damage, add in "comparisons" below here. iDamage = (DamageAvg(iShouDamage ) > DamageAvg(iDamage)) ? iShouDamage : iDamage; iDamage = (DamageAvg(iFrostDamage ) > DamageAvg(iDamage)) ? iFrostDamage : iDamage; iDamage = (DamageAvg(iSUSDamage ) > DamageAvg(iDamage)) ? iSUSDamage : iDamage; @@ -354,6 +598,8 @@ int FindUnarmedDamage(object oCreature) return iDamage; } + */ + // Adds appropriate feats to the skin. Stolen from SoulTaker + expanded with overwhelming/devastating critical. void UnarmedFeats(object oCreature) @@ -426,13 +672,13 @@ void UnarmedFists(object oCreature) // Sacred Fists who break their code get no benefits. if (GetHasFeat(FEAT_SF_CODE,oCreature)) iSacFist = 0; - + // The monk adds all these classes. - int iMonkEq = iMonk + iShou + iSacFist + iHenshin + iZuoken + iShadowSunNinja; - + int iMonkEq = iMonk + iShou + iSacFist + iHenshin + iZuoken + iShadowSunNinja; + // Ascetic Stalker if (GetHasFeat(FEAT_ASCETIC_STALKER, oCreature)) - iMonkEq += iAscetic; + iMonkEq += iAscetic; // Determine the type of damage the character should do. string sWeapType; diff --git a/src/include/prc_inc_wpnrest.nss b/src/include/prc_inc_wpnrest.nss index c101e78..0a59892 100644 --- a/src/include/prc_inc_wpnrest.nss +++ b/src/include/prc_inc_wpnrest.nss @@ -1,6 +1,6 @@ //:://///////////////////////////////////////////// //:: Weapon Restriction System Include -//:: prc_inc_restwpn.nss +//:: prc_inc_wpnrest.nss //:://///////////////////////////////////////////// /* Functions to support PnP Weapon Proficiency and @@ -15,6 +15,56 @@ #include "inc_item_props" #include "prc_x2_itemprop" +//:: Detects if "monk" gloves are being equipped & set a +//:: variable if TRUE for use with other functions +void DetectMonkGloveEquip(object oItem) +{ + int nItemType = GetBaseItemType(oItem); + + object oPC = GetItemPossessor(oItem); + if (!GetIsObjectValid(oItem)) + { + if (DEBUG) DoDebug("prc_inc_wpnrest >> DetectMonkGloveEquip(): Unable to determine item possessor"); + return; + } + + if(nItemType != BASE_ITEM_GLOVES && nItemType != BASE_ITEM_BRACER) {return;} + + else if (nItemType == BASE_ITEM_BRACER) + { + if(DEBUG) DoDebug("prc_inc_wpnrest >> DetectMonkGloveEquip(): Bracer found!"); + DeleteLocalInt(oPC, "WEARING_MONK_GLOVES"); + return; + } + else + { + itemproperty ipG = GetFirstItemProperty(oItem); + + while(GetIsItemPropertyValid(ipG)) + { + int nTypeG = GetItemPropertyType(ipG); + + // Damage related properties we care about + if(nTypeG == ITEM_PROPERTY_DAMAGE_BONUS + || nTypeG == ITEM_PROPERTY_ATTACK_BONUS + || nTypeG == ITEM_PROPERTY_DAMAGE_BONUS_VS_ALIGNMENT_GROUP + || nTypeG == ITEM_PROPERTY_DAMAGE_BONUS_VS_RACIAL_GROUP + || nTypeG == ITEM_PROPERTY_DAMAGE_BONUS_VS_SPECIFIC_ALIGNMENT) + { + if(DEBUG) DoDebug("prc_inc_wpnrest >> DetectMonkGloves(): Monk gloves found!"); + SetLocalInt(oPC, "WEARING_MONK_GLOVES", 1); + return; + } + else + { + if(DEBUG) DoDebug("prc_inc_wpnrest >> DetectMonkGloves(): Monk gloves not found! You should never see this."); + DeleteLocalInt(oPC, "WEARING_MONK_GLOVES"); + return; + } + } + } +} + /** * All of the following functions use the following parameters: * @@ -23,6 +73,69 @@ * @param nHand The hand the weapon is wielded in. In the form of * ATTACK_BONUS_ONHAND or ATTACK_BONUS_OFFHAND. */ +//:: returns TRUE if the wielded weapon works with the Swashbuckler's class abilities. +int GetHasSwashbucklerWeapon(object oPC) +{ + object oWeap = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC); + if (!GetIsObjectValid(oWeap)) return FALSE; + + int nType = GetBaseItemType(oWeap); + + switch (nType) + { + case BASE_ITEM_DAGGER: + case BASE_ITEM_KATAR: + case BASE_ITEM_HANDAXE: + case BASE_ITEM_KAMA: + case BASE_ITEM_KUKRI: + case BASE_ITEM_LIGHTHAMMER: + case BASE_ITEM_LIGHTMACE: + case BASE_ITEM_LIGHT_PICK: + case BASE_ITEM_RAPIER: + case BASE_ITEM_SHORTSWORD: + case BASE_ITEM_SICKLE: + case BASE_ITEM_WHIP: + case BASE_ITEM_SAI: + case BASE_ITEM_SAP: + case BASE_ITEM_NUNCHAKU: + case BASE_ITEM_GOAD: + case BASE_ITEM_ELVEN_LIGHTBLADE: + case BASE_ITEM_ELVEN_THINBLADE: + case BASE_ITEM_EAGLE_CLAW: + return TRUE; + } + + // Iaijutsu Master allows katana + if (GetLevelByClass(CLASS_TYPE_IAIJUTSU_MASTER, oPC) > 0) + { + if (nType == BASE_ITEM_KATANA) return TRUE; + } + + return FALSE; +} + +//:: returns TRUE if the wielded weapon works with the Champion of Corellon's Elegant Strike. +int GetHasCorellonWeapon(object oPC) +{ + object oWeap = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC); + if (!GetIsObjectValid(oWeap)) return FALSE; + + int nType = GetBaseItemType(oWeap); + + switch (nType) + { + case BASE_ITEM_SCIMITAR: + case BASE_ITEM_LONGSWORD: + case BASE_ITEM_RAPIER: + case BASE_ITEM_ELVEN_COURTBLADE: + case BASE_ITEM_ELVEN_LIGHTBLADE: + case BASE_ITEM_ELVEN_THINBLADE: + return TRUE; + } + + return FALSE; +} + void DoRacialEquip(object oPC, int nBaseType); //return if PC has proficiency in an item @@ -40,13 +153,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,12 +283,14 @@ 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_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); @@ -300,167 +433,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; @@ -725,6 +876,7 @@ int IsMeleeWeapon(int nBaseItemType) case BASE_ITEM_CLOAK: case BASE_ITEM_CRAFTED_ROD: case BASE_ITEM_CRAFTED_STAFF: + case BASE_ITEM_CRAFTED_SCEPTER: case BASE_ITEM_CRAFTMATERIALMED: case BASE_ITEM_CRAFTMATERIALSML: case BASE_ITEM_CREATUREITEM: diff --git a/src/include/prc_ipfeat_const.nss b/src/include/prc_ipfeat_const.nss index a1829be..672fd04 100644 --- a/src/include/prc_ipfeat_const.nss +++ b/src/include/prc_ipfeat_const.nss @@ -262,7 +262,7 @@ const int IP_CONST_FEAT_WEAPON_PROFICIENCY_LIGHT_LANCE = 4638; const int IP_CONST_FEAT_WEAPON_PROFICIENCY_HEAVY_PICK = 4639; const int IP_CONST_FEAT_WEAPON_PROFICIENCY_LIGHT_PICK = 4640; const int IP_CONST_FEAT_WEAPON_PROFICIENCY_SAI = 4641; -const int IP_CONST_FEAT_WEAPON_PROFICIENCY_NUNCHUKU = 4642; +const int IP_CONST_FEAT_WEAPON_PROFICIENCY_NUNCHAKU = 4642; const int IP_CONST_FEAT_WEAPON_PROFICIENCY_FALCHION = 4643; const int IP_CONST_FEAT_WEAPON_PROFICIENCY_SAP = 4644; const int IP_CONST_FEAT_WEAPON_PROFICIENCY_KATAR = 4645; @@ -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..0171bbb 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_CRAFTED_SCEPTER = 249; +const int BASE_ITEM_CRAFTED_VIAL = 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 index bf9bc51..4bba080 100644 --- a/src/include/prc_nui_com_inc.nss +++ b/src/include/prc_nui_com_inc.nss @@ -3,6 +3,7 @@ #include "psi_inc_psifunc" #include "inc_lookups" #include "nw_inc_nui" +#include "tob_inc_tobfunc" // // GetCurrentSpellLevel @@ -153,8 +154,53 @@ json GreyOutButton(json jButton, float w, float h); // json CreateGreyOutRectangle(float w, float h); +// +// 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); + void CreateSpellDescriptionNUI(object oPlayer, int featID, int spellId=0, int realSpellId=0, int nClass=0); + +void CallSpellUnlevelScript(object oPC, int nClass, int nLevel); void ClearSpellDescriptionNUI(object oPlayer=OBJECT_SELF); +void RemoveIPFeat(object oPC, int ipFeatID); + +void CallSpellUnlevelScript(object oPC, int nClass, int nLevel) +{ + SetScriptParam("UnLevel_ClassChoice", IntToString(nClass)); + SetScriptParam("UnLevel_LevelChoice", IntToString(nLevel)); + ExecuteScript("prc_unlvl_script", oPC); +} + +void RemoveIPFeat(object oPC, int ipFeatID) +{ + object oSkin = GetPCSkin(oPC); + itemproperty ipTest = GetFirstItemProperty(oSkin); + while(GetIsItemPropertyValid(ipTest)) + { + // Check if the itemproperty is a bonus feat that has been marked for removal + if(GetItemPropertyType(ipTest) == ITEM_PROPERTY_BONUS_FEAT) + { + if (GetItemPropertySubType(ipTest) == ipFeatID) + { + if(DEBUG) DoDebug("_ManeuverRecurseRemoveArray(): Removing bonus feat itemproperty:\n" + DebugIProp2Str(ipTest)); + // If so, remove it + RemoveItemProperty(oSkin, ipTest); + } + + } + + ipTest = GetNextItemProperty(oSkin); + } +} int GetCurrentSpellLevel(int nClass, int nLevel) { @@ -401,6 +447,9 @@ json GetSpellIcon(int spellId,int featId=0,int nClass=0) // the FeatID holds the accurate spell icon, not the SpellID int nFeatID = StringToInt(Get2DACache("spells", "FeatID", spellId)); + // however if no featId was found use the spell's icon instead + if (!nFeatID) + return JsonString(Get2DACache("spells", "IconResRef", spellId)); return JsonString(Get2DACache("feat", "Icon", nFeatID)); } @@ -528,3 +577,39 @@ void ClearSpellDescriptionNUI(object oPlayer=OBJECT_SELF) DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_CLASSID_VAR); } +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; +} + diff --git a/src/include/prc_nui_consts.nss b/src/include/prc_nui_consts.nss index e1e9c5c..745c860 100644 --- a/src/include/prc_nui_consts.nss +++ b/src/include/prc_nui_consts.nss @@ -156,3 +156,16 @@ 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"; + +////////////////////////////////////////////////// +// // +// Spell Duration NUI // +// // +////////////////////////////////////////////////// + +const string DURATION_NUI_WINDOW_ID = "DurationNUI"; +const string NUI_DURATION_MANUALLY_OPENED_PARAM = "DurationNUIManuallyOpenedParam"; +const string NUI_DURATION_NO_LOOP_PARAM = "DurationNUINoLoopParam"; +const string NUI_DURATION_TRACKED_SPELLS = "durationNUI_trackedSpellList"; +const string NUI_SPELL_DURATION_BASE_BIND = "durationNUI_durationSpellId"; +const string NUI_SPELL_DURATION_SPELLID_BASE_CANCEL_BUTTON = "NuiDurationCancelButtonSpellID"; diff --git a/src/include/prc_nui_lv_inc.nss b/src/include/prc_nui_lv_inc.nss index 184467b..b3d862f 100644 --- a/src/include/prc_nui_lv_inc.nss +++ b/src/include/prc_nui_lv_inc.nss @@ -98,21 +98,9 @@ void OpenNUILevelUpWindow(int nClass, object oPC=OBJECT_SELF); // // CloseNUILevelUpWindow // Closes the NUI Level Up Window if its open +// setting reset to 1 will make it clear the entire cache as if the NUI was never opened // -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); +void CloseNUILevelUpWindow(object oPC=OBJECT_SELF, int reset=0); // // GetRemainingSpellChoices @@ -432,7 +420,7 @@ json GetChosenReplaceListObject(object oPC=OBJECT_SELF); // Returns: // int:Boolean TRUE if the spell is a expanded knowledge spell, FALSE otherwise // -int IsExpKnowledgePower(int nClass, int spellbookId); +int IsExpKnowledgePower(int nClass, int spellbookId, object oPC=OBJECT_SELF); // // GetExpKnowledgePowerListRequired @@ -861,7 +849,7 @@ void AddSpellToChosenList(int nClass, int spellbookId, int spellCircle, object o // 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) + if (IsExpKnowledgePower(nClass, spellbookId, oPC) || GetRemainingPowerChoices(nClass, spellCircle, oPC, FALSE) == 0) { string sFile = GetClassSpellbookFile(nClass); @@ -1061,53 +1049,20 @@ void OpenNUILevelUpWindow(int nClass, object oPC=OBJECT_SELF) // 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) +void CloseNUILevelUpWindow(object oPC=OBJECT_SELF, int reset=0) { 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); + if (reset) + { + ClearLevelUpNUICaches(currentClass, oPC); + } int nPreviousToken = NuiFindWindow(oPC, NUI_LEVEL_UP_WINDOW_ID); if (nPreviousToken != 0) { @@ -1126,7 +1081,7 @@ int ShouldSpellButtonBeEnabled(int nClass, int circleLevel, int spellbookId, obj // 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)) + if (IsExpKnowledgePower(nClass, spellbookId, oPC)) { int remainingExp = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC) + GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC); @@ -1540,7 +1495,7 @@ int GetRemainingSpellChoices(int nClass, int circleLevel, object oPC=OBJECT_SELF 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; + totalSpellsKnown = 7; // logic for spont casters json selectedCircle = JsonObjectGet(chosenSpells, IntToString(circleLevel)); @@ -1554,6 +1509,7 @@ int GetRemainingSpellChoices(int nClass, int circleLevel, object oPC=OBJECT_SELF if (chosenCircle == circleLevel) SetLocalInt(oPC, NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR, remainingChoices); + if (DEBUG) DoDebug("Remaining spell choices is " + IntToString(remainingChoices)); return remainingChoices; } @@ -1664,11 +1620,13 @@ void RemoveSpells(int nClass, object oPC=OBJECT_SELF) { string sFile = GetClassSpellbookFile(nClass); string sSpellBook = GetSpellsKnown_Array(nClass); + string spellsAtLevelList = "SpellsKnown_" + IntToString(nClass) + "_AtLevel" + IntToString(GetHitDice(oPC)); // remove the spell from the spellbook array_extract_int(oPC, sSpellBook, nSpellbookID); + array_extract_int(oPC, spellsAtLevelList, nSpellbookID); // wipe the spell from the player int ipFeatID = StringToInt(Get2DACache(sFile, "IPFeatID", nSpellbookID)); - WipeSpellFromHide(ipFeatID, oPC); + RemoveIPFeat(oPC, ipFeatID); } } } @@ -1779,6 +1737,7 @@ void LearnSpells(int nClass, object oPC=OBJECT_SELF) // get location of persistant storage on the hide string sSpellbook = GetSpellsKnown_Array(nClass, nSpellLevel); + if (DEBUG) DoDebug("Adding spell " + IntToString(nSpellbookID) + "to " + sSpellbook); //object oToken = GetHideToken(oPC); // Create spells known persistant array if it is missing @@ -1789,13 +1748,25 @@ void LearnSpells(int nClass, object oPC=OBJECT_SELF) nSize = 0; } + string spellsAtLevelList = "SpellsKnown_" + IntToString(nClass) + "_AtLevel" + IntToString(GetHitDice(oPC)); + int spellsAtLevelSize = persistant_array_get_size(oPC, spellsAtLevelList); + if (spellsAtLevelSize < 0) + { + persistant_array_create(oPC, spellsAtLevelList); + spellsAtLevelSize = 0; + } + // set the list of spells learned at this level + string sFile = GetClassSpellbookFile(nClass); + int spellId = StringToInt(Get2DACache(sFile, "SpellID", nSpellbookID)); + persistant_array_set_int(oPC, spellsAtLevelList, spellsAtLevelSize, spellId); + if (DEBUG) DoDebug("Adding spells to array " + spellsAtLevelList); + // 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)); @@ -1975,7 +1946,7 @@ void RemoveSpellKnown(int nClass, int spellbookId, object oPC=OBJECT_SELF, int n //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!"); + if (DEBUG) DoDebug("Could not find spellID " + IntToString(spellID) + " in the class's spellbook!"); return; } } @@ -2002,10 +1973,7 @@ void RemoveSpellKnown(int nClass, int spellbookId, object oPC=OBJECT_SELF, int n // 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); + RemoveIPFeat(oPC, ipFeatID); } json GetSpellIDsKnown(int nClass, object oPC=OBJECT_SELF, int nList=0) @@ -2088,7 +2056,7 @@ string ReasonForDisabledSpell(int nClass, int spellbookId, object oPC=OBJECT_SEL // 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)) + if (IsExpKnowledgePower(nClass, spellbookId, oPC)) { int remainingExp = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC) + GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC); @@ -2322,11 +2290,15 @@ json GetChosenReplaceListObject(object oPC=OBJECT_SELF) //////////////////////////////////////////////////////////////////////////// -int IsExpKnowledgePower(int nClass, int spellbookId) +int IsExpKnowledgePower(int nClass, int spellbookId, object oPC=OBJECT_SELF) { string sFile = GetClassSpellbookFile(nClass); int isExp = StringToInt(Get2DACache(sFile, "Exp", spellbookId)); - return isExp; + if (isExp) + return TRUE; + int featId = StringToInt(Get2DACache(sFile, "FeatID", spellbookId)); + int isOuterDomain = (featId) ? !CheckPowerPrereqs(featId, oPC) : FALSE; + return isOuterDomain; } json GetCurrentPowerList(object oPC=OBJECT_SELF) @@ -2346,12 +2318,8 @@ 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) + if (!CheckPowerPrereqs(featId, oPC) || isExp) { // and we have a expanded knowledge choice left to make then show // the button @@ -2360,10 +2328,12 @@ int ShouldAddPower(int nClass, int spellbookId, object oPC=OBJECT_SELF) int currentCircle = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR); int choicesLeft = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC); + if (DEBUG) DoDebug("You still have " + IntToString(choicesLeft) + " expanded power choices left!"); if (choicesLeft && (currentCircle <= (maxLevel-1))) addPower = TRUE; choicesLeft = GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC); - if (choicesLeft) + if (DEBUG) DoDebug("You still have " + IntToString(choicesLeft) + " epic expanded power choices left!"); + if (choicesLeft && (currentCircle <= (maxLevel-1))) addPower = TRUE; // otherwise don't show the button. return addPower; @@ -2383,7 +2353,7 @@ void LearnPowers(int nClass, object oPC=OBJECT_SELF) 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); + AddPowerKnown(oPC, nClass, nSpellbookID, TRUE, GetHitDice(oPC), expKnow); } } @@ -2569,7 +2539,8 @@ int HasPreRequisitesForManeuver(int nClass, int spellbookId, object oPC=OBJECT_S json chosenDisc = JsonObjectGet(discInfo, discipline); if (chosenDisc != JsonNull()) { - int nManCount = JsonGetInt(JsonObjectGet(chosenDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + int nManCount = (JsonGetInt(JsonObjectGet(chosenDisc, IntToString(MANEUVER_TYPE_MANEUVER))) + + JsonGetInt(JsonObjectGet(chosenDisc, IntToString(MANEUVER_TYPE_STANCE)))); if (nManCount >= prereqs) return TRUE; } @@ -3165,13 +3136,18 @@ json GetInvokerKnownListObject(int nClass, object oPC=OBJECT_SELF) } SetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_INVOCATIONS_CACHE_VAR + IntToString(nClass), knownObject); + if (DEBUG) DoDebug("Printing json representation of allowed invocations for class " + IntToString(nClass)); + if (DEBUG) DoDebug(JsonDump(knownObject, 2)); return knownObject; } int GetRemainingInvocationChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE) { + if (DEBUG) DoDebug ("Getting remaining invocation choices at " + IntToString(chosenCircle) + " circle"); int remaining = 0; int nLevel = GetInvokerLevel(oPC, nClass); + if (nClass == CLASS_TYPE_DRAGON_SHAMAN) nLevel = GetLevelByClass(nClass, oPC); + if (DEBUG) DoDebug("Invoker level is " + IntToString(nLevel)); json knownObject = GetInvokerKnownListObject(nClass, oPC); json chosenInv = GetChosenSpellListObject(nClass, oPC); @@ -3199,8 +3175,10 @@ int GetRemainingInvocationChoices(int nClass, int chosenCircle, object oPC=OBJEC currentChosen += 1; } } + if (DEBUG) DoDebug(IntToString(currentChosen) + " incantations chosen at " + IntToString(chosenCircle) + " circle"); int allowedAtCircle = JsonGetInt(JsonObjectGet(currentLevelKnown, IntToString(i))); + if (DEBUG) DoDebug(IntToString(allowedAtCircle) + " incantations allowed at " + IntToString(chosenCircle) + " circle"); remaining = (allowedAtCircle - currentChosen + remaining); // if the circle is below the chosen circle and we have a positive remaining, @@ -3334,3 +3312,5 @@ json GetArchivistNewSpellsList(object oPC=OBJECT_SELF) 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_sb_inc.nss b/src/include/prc_nui_sb_inc.nss index 409044f..7148413 100644 --- a/src/include/prc_nui_sb_inc.nss +++ b/src/include/prc_nui_sb_inc.nss @@ -387,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 @@ -506,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 @@ -527,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 @@ -845,4 +842,4 @@ int IsSpellbookNUIOpen(object oPC) } return FALSE; -} +} \ No newline at end of file diff --git a/src/include/prc_nui_sbd_inc.nss b/src/include/prc_nui_sbd_inc.nss index 4f84658..8027a70 100644 --- a/src/include/prc_nui_sbd_inc.nss +++ b/src/include/prc_nui_sbd_inc.nss @@ -28,11 +28,17 @@ void CreateSpellDescriptionNUI(object oPlayer, int featID, int spellId=0, int re void CreateSpellDescriptionNUI(object oPlayer, int featID, int spellId=0, int realSpellId=0) { // look for existing window and destroy - int nPreviousToken = NuiFindWindow(OBJECT_SELF, NUI_SPELL_DESCRIPTION_WINDOW_ID); + int nPreviousToken = NuiFindWindow(oPlayer, NUI_SPELL_DESCRIPTION_WINDOW_ID); + if(nPreviousToken != 0) + { + NuiDestroy(oPlayer, nPreviousToken); + } + +/* int nPreviousToken = NuiFindWindow(OBJECT_SELF, NUI_SPELL_DESCRIPTION_WINDOW_ID); if(nPreviousToken != 0) { NuiDestroy(OBJECT_SELF, nPreviousToken); - } + } */ // in order of accuracy for names it goes RealSpellID > SpellID > FeatID string spellName; diff --git a/src/include/prc_sp_func.nss b/src/include/prc_sp_func.nss index 233090f..c249c5f 100644 --- a/src/include/prc_sp_func.nss +++ b/src/include/prc_sp_func.nss @@ -180,6 +180,24 @@ void RunImpactScript(object oPC, int nSpellID, int nEventType) DeleteLocalInt(oPC, PRC_SPELLID_OVERRIDE); } +//Returns true if the spell is one of the repair spells +int IsRepair(int nSpellID) +{ + return ((nSpellID >= SPELL_REPAIR_MINOR_DAMAGE) && (nSpellID <= SPELL_REPAIR_CRITICAL_DAMAGE)); +} + +//Returns true if the spell is one of the mass repair spells +int IsMassRepair(int nSpellID) +{ + return ((nSpellID >= SPELL_MASS_REPAIR_LIGHT_DAMAGE) && (nSpellID <= SPELL_MASS_REPAIR_CRITICAL_DAMAGE)); +} + +//Returns true if the spell is one of the mass inflict damage spells +int IsMassInflictDamage(int nSpellID) +{ + return ((nSpellID >= SPELL_MASS_INFLICT_LIGHT_DAMAGE) && (nSpellID <= SPELL_MASS_INFLICT_CRITICAL_DAMAGE)); +} + //Returns true if the spell is one of the cure spells int IsCure(int nSpellID) { diff --git a/src/include/prc_spell_const.nss b/src/include/prc_spell_const.nss index bb8b1cd..c93417f 100644 --- a/src/include/prc_spell_const.nss +++ b/src/include/prc_spell_const.nss @@ -13,6 +13,9 @@ const int SPELL_BLACKLIGHT = 2091; const int SPELL_BARD_SONG = 411; const int SPELL_BARD_CURSE_SONG = 644; +//:: Monk +const int SPELL_MONK_ABUNDANT_STEP = 17986; + //:: Epic Level Handbook const int SPELL_EPIC_SWARM_OF_ARROWS = 17996; @@ -22,6 +25,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,20 +1295,128 @@ 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; //:: Spell Compendium Spells -const int SPELL_SPIRIT_WORM = 17248; -const int SPELL_FORCE_MISSILES = 2480; +const int SPELL_FORCE_MISSILES = 2480; +const int SPELL_REPAIR_MINOR_DAMAGE = 17094; +const int SPELL_REPAIR_LIGHT_DAMAGE = 17095; +const int SPELL_REPAIR_MODERATE_DAMAGE = 17096; +const int SPELL_REPAIR_SERIOUS_DAMAGE = 17097; +const int SPELL_REPAIR_CRITICAL_DAMAGE = 17098; +const int SPELL_INFLICT_LIGHT_DAMAGE = 17100; +const int SPELL_INFLICT_MODERATE_DAMAGE = 17101; +const int SPELL_INFLICT_SERIOUS_DAMAGE = 17102; +const int SPELL_INFLICT_CRITICAL_DAMAGE = 17103; +const int SPELL_SPIRIT_WORM = 17248; + +//:: Races of Eberron +const int SPELL_MASS_REPAIR_LIGHT_DAMAGE = 17105; +const int SPELL_MASS_REPAIR_MODERATE_DAMAGE = 17106; +const int SPELL_MASS_REPAIR_SERIOUS_DAMAGE = 17107; +const int SPELL_MASS_REPAIR_CRITICAL_DAMAGE = 17108; +const int SPELL_MASS_INFLICT_LIGHT_DAMAGE = 17110; +const int SPELL_MASS_INFLICT_MODERATE_DAMAGE = 17111; +const int SPELL_MASS_INFLICT_SERIOUS_DAMAGE = 17112; +const int SPELL_MASS_INFLICT_CRITICAL_DAMAGE = 17113; //:: 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; + +//:: Magic Item Compendium +const int SPELL_AROMA_OF_CURDLED_DEATH = 17987; +const int SPELL_ELIXIR_OF_THE_BEETLE = 17987; //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..f40f0f6 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) ) @@ -485,7 +487,8 @@ void SpellfireChargeItem(object oPC, object oItem) AddSpellfireLevels(oPC, nNewCharges - 50); nNewCharges = 50; } - SetItemCharges(oItem, nCharges + nExpend); + //SetItemCharges(oItem, nCharges + nExpend); + SetItemCharges(oItem, nNewCharges); //Assuming 50 is the maximum //refunding excess charges } @@ -525,3 +528,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_weap_apt.nss b/src/include/prc_weap_apt.nss index 8fa1043..7bda1b7 100644 --- a/src/include/prc_weap_apt.nss +++ b/src/include/prc_weap_apt.nss @@ -47,12 +47,10 @@ int GetWeaponFocusFeatItemProperty(int nFeatNumber) if(nItemProperty != -1) return nItemProperty; nItemProperty = GetFeatItemProperty(nFeatNumber, IP_CONST_FEAT_WEAPON_FOCUS_TRIDENT, IP_CONST_FEAT_WEAPON_FOCUS_TRIDENT); if(nItemProperty != -1) return nItemProperty; - nItemProperty = GetFeatItemProperty(nFeatNumber, IP_CONST_FEAT_WEAPON_FOCUS_LIGHT_LANCE, IP_CONST_FEAT_WEAPON_FOCUS_GOAD); + nItemProperty = GetFeatItemProperty(nFeatNumber, IP_CONST_FEAT_WEAPON_FOCUS_EAGLE_CLAW, IP_CONST_FEAT_WEAPON_FOCUS_GOAD); if(nItemProperty != -1) return nItemProperty; nItemProperty = GetFeatItemProperty(nFeatNumber, IP_CONST_FEAT_WEAPON_FOCUS_ELVEN_LIGHTBLADE, IP_CONST_FEAT_WEAPON_FOCUS_ELVEN_LIGHTBLADE); if(nItemProperty != -1) return nItemProperty; - nItemProperty = GetFeatItemProperty(nFeatNumber, IP_CONST_FEAT_WEAPON_FOCUS_EAGLE_CLAW, IP_CONST_FEAT_WEAPON_FOCUS_EAGLE_CLAW); - if(nItemProperty != -1) return nItemProperty; nItemProperty = GetFeatItemProperty(nFeatNumber, IP_CONST_FEAT_WEAPON_FOCUS_ELVEN_THINBLADE, IP_CONST_FEAT_WEAPON_FOCUS_ELVEN_THINBLADE); if(nItemProperty != -1) return nItemProperty; nItemProperty = GetFeatItemProperty(nFeatNumber, IP_CONST_FEAT_WEAPON_FOCUS_ELVEN_COURTBLADE, IP_CONST_FEAT_WEAPON_FOCUS_ELVEN_COURTBLADE); diff --git a/src/include/prc_x2_craft.nss b/src/include/prc_x2_craft.nss index 5834f63..c18dcc3 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,25 @@ 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 int X2_CI_CRAFTSCEPTER_FEAT_ID = 25962; + 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 +188,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 +208,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 +276,9 @@ 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_CRAFTED_SCEPTER || + nBt == BASE_ITEM_MUNDANE_HERB) return TRUE; else return FALSE; @@ -287,7 +304,7 @@ object CICraftBrewPotion(object oCreator, int nSpellID ) return OBJECT_INVALID; } - /* //just a tad retarded, don't you think? other crafting feats are not similarly restricted + /* //just a tad silly, don't you think? other crafting feats are not similarly restricted //Uses per day int nUsesAllowed; @@ -453,11 +470,159 @@ 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_OCULAR: + 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 +656,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 +672,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,6 +711,7 @@ object CICraftScribeScroll(object oCreator, int nSpellID) } return oTarget; } + */ // ----------------------------------------------------------------------------- // Returns TRUE if the player used the last spell to brew a potion @@ -593,7 +763,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 +794,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); @@ -698,7 +868,6 @@ These dont work as IPs since they are hardcoded */ } - // ----------------------------------------------------------------------------- // Returns TRUE if the player used the last spell to create a scroll // ----------------------------------------------------------------------------- @@ -728,7 +897,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 +1053,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 +1065,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); @@ -966,6 +1135,169 @@ These dont work as IPs since they are hardcoded */ return FALSE; } +// ----------------------------------------------------------------------------- +// Returns TRUE if the player used the last spell to craft a scepter +// ----------------------------------------------------------------------------- +int CICraftCheckCraftScepter(object oSpellTarget, object oCaster, int nSpellID = 0) +{ + + if(nSpellID == 0) nSpellID = PRCGetSpellId(); + int nCasterLevel = GetAlternativeCasterLevel(oCaster, PRCGetCasterLevel(oCaster)); + int bSuccess = TRUE; + int nCount = 0; + itemproperty ip = GetFirstItemProperty(oSpellTarget); + int nMetaMagic = PRCGetMetaMagicFeat(); + + while(GetIsItemPropertyValid(ip)) + { + if(GetItemPropertyType(ip) == ITEM_PROPERTY_CAST_SPELL) + nCount++; + ip = GetNextItemProperty(oSpellTarget); + } + if(nCount >= 2) //:: Scepters are limited to two spells + { + FloatingTextStringOnCreature("* Failure - Too many castspell itemproperties *", oCaster); + return TRUE; + } + if(!GetHasFeat(X2_CI_CRAFTSCEPTER_FEAT_ID, oCaster)) + { + FloatingTextStrRefOnCreature(40487, oCaster); // Item Creation Failed - Don't know how to create that type of item + return TRUE; // tried item creation but do not know how to do it + } + if(CIGetIsSpellRestrictedFromCraftFeat(nSpellID, X2_CI_CRAFTSCEPTER_FEAT_ID)) + { + FloatingTextStrRefOnCreature(16829169, oCaster); // can not be used with this feat + return TRUE; + } + + // Get the base spell level (circle) before metamagic adjustments + int nBaseLevel = CIGetSpellInnateLevel(nSpellID, TRUE); + + // Check if spell circle is 7th level or lower + if (nBaseLevel > 7) + { + //FloatingTextStrRefOnCreature(83623, oCaster); // Spell level too high + FloatingTextStringOnCreature("* Failure - scepters can not hold spells higher than level 7", oCaster); + return TRUE; + } + + int nLevel = nBaseLevel; + + if(GetPRCSwitch(PRC_CRAFT_SCEPTER_CASTER_LEVEL)) + { + switch(nMetaMagic) + { + case METAMAGIC_EMPOWER: + nLevel += 2; + break; + case METAMAGIC_EXTEND: + nLevel += 1; + break; + case METAMAGIC_MAXIMIZE: + nLevel += 3; + break; +/* case METAMAGIC_QUICKEN: + nLevel += 1; + break; + case METAMAGIC_SILENT: + nLevel += 5; + break; + case METAMAGIC_STILL: + nLevel += 6; + break; + These dont work as IPs since they are hardcoded */ + } + } + + int nCostMod = GetPRCSwitch(PRC_X2_CRAFTSCEPTER_COSTMODIFIER); + if(!nCostMod) nCostMod = 750; + int nLvlRow = IPGetIPConstCastSpellFromSpellID(nSpellID); + int nCLevel = StringToInt(Get2DACache("iprp_spells","CasterLvl",nLvlRow)); + int nCost = CIGetCraftGPCost(nLevel, nCostMod, PRC_CRAFT_SCEPTER_CASTER_LEVEL); + + //discount for second spell + if(nCount+1 == 2) + nCost = (nCost/2); + + //takes epic xp costs into account + struct craft_cost_struct costs = GetModifiedCostsFromBase(nCost, oCaster, FEAT_CRAFT_SCEPTER, (nMetaMagic > 0)); + + if(costs.nGoldCost < 1) costs.nXPCost = 1; + if(costs.nXPCost < 1) costs.nXPCost = 1; + //if(GetGold(oCaster) < nGoldCost) // enough gold? + if (!GetHasGPToSpend(oCaster, costs.nGoldCost)) + { + FloatingTextStrRefOnCreature(3786, oCaster); // Item Creation Failed - not enough gold! + return TRUE; + } + int nHD = GetHitDice(oCaster); + int nMinXPForLevel = (nHD * (nHD - 1)) * 500; + int nNewXP = GetXP(oCaster) - costs.nXPCost; + //if (nMinXPForLevel > nNewXP || nNewXP == 0 ) + if (!GetHasXPToSpend(oCaster, costs.nXPCost)) + { + FloatingTextStrRefOnCreature(3785, oCaster); // Item Creation Failed - Not enough XP + return TRUE; + } + //check spell emulation + if(!CheckAlternativeCrafting(oCaster, nSpellID, costs)) + { + FloatingTextStringOnCreature("*Crafting failed!*", oCaster, FALSE); + return TRUE; + } + int nPropID = IPGetIPConstCastSpellFromSpellID(nSpellID); + if (nPropID == 0 && nSpellID != 0) + { + FloatingTextStrRefOnCreature(84544,oCaster); + return TRUE; + } + if (nPropID != -1) + { + AddItemProperty(DURATION_TYPE_PERMANENT,ItemPropertyCastSpell(nPropID,IP_CONST_CASTSPELL_NUMUSES_1_CHARGE_PER_USE),oSpellTarget); + + if(GetPRCSwitch(PRC_CRAFT_SCEPTER_CASTER_LEVEL)) + { + AddItemProperty(DURATION_TYPE_PERMANENT,ItemPropertyCastSpellCasterLevel(nSpellID, nCasterLevel),oSpellTarget); + AddItemProperty(DURATION_TYPE_PERMANENT,ItemPropertyCastSpellMetamagic(nSpellID, PRCGetMetaMagicFeat()),oSpellTarget); + AddItemProperty(DURATION_TYPE_PERMANENT,ItemPropertyCastSpellDC(nSpellID, PRCGetSaveDC(PRCGetSpellTargetObject(), OBJECT_SELF)),oSpellTarget); + } + } + else + bSuccess = FALSE; + + if(bSuccess) + { + //TakeGoldFromCreature(nGoldCost, oCaster, TRUE); + //SetXP(oCaster, nNewXP); + SpendXP(oCaster, costs.nXPCost); + SpendGP(oCaster, costs.nGoldCost); + //DestroyObject (oSpellTarget); + FloatingTextStrRefOnCreature(8502, oCaster); // Item Creation successful + + //advance time here + if(!costs.nTimeCost) costs.nTimeCost = 1; + AdvanceTimeForPlayer(oCaster, RoundsToSeconds(costs.nTimeCost)); + string sName; + sName = GetName(oCaster)+"'s Magic Scepter"; + SetItemCharges(oSpellTarget, 50); + //sName = Get2DACache("spells", "Name", nID); + //sName = "Wand of "+GetStringByStrRef(StringToInt(sName)); + SetName(oSpellTarget, sName); + SetItemCursedFlag(oSpellTarget, FALSE); + SetDroppableFlag(oSpellTarget, TRUE); + return TRUE; + } + else + { + FloatingTextStrRefOnCreature(76417, oCaster); // Item Creation Failed + return TRUE; + } + return TRUE; +} + +// ----------------------------------------------------------------------------- +// Returns TRUE if the player used the last spell to craft a staff +// ----------------------------------------------------------------------------- int CICraftCheckCraftStaff(object oSpellTarget, object oCaster, int nSpellID = 0) { @@ -1027,7 +1359,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)); @@ -1114,6 +1446,9 @@ These dont work as IPs since they are hardcoded */ return TRUE; } +// ----------------------------------------------------------------------------- +// Returns TRUE if the player used the last spell to craft a rod +// ----------------------------------------------------------------------------- int CICraftCheckCraftRod(object oSpellTarget, object oCaster, int nSpellID = 0) { @@ -1175,7 +1510,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)); @@ -1260,6 +1595,7 @@ These dont work as IPs since they are hardcoded */ return TRUE; } + int InscribeRune(object oTarget = OBJECT_INVALID, object oCaster = OBJECT_INVALID, int nSpell = 0) { if(!GetIsObjectValid(oCaster)) oCaster = OBJECT_SELF; @@ -1310,6 +1646,11 @@ int InscribeRune(object oTarget = OBJECT_INVALID, object oCaster = OBJECT_INVALI if(!GetIsObjectValid(oTarget)) oTarget = PRCGetSpellTargetObject(); int nCaster = GetAlternativeCasterLevel(oCaster, PRCGetCasterLevel(oCaster)); + +//:: Check for Inscribe Epic Runes and cap CL at 20 if it doesn't exist. + int bEpicRunes = GetHasFeat(EPIC_FEAT_INSCRIBE_EPIC_RUNES, oCaster); + if (!bEpicRunes) { if(nCaster > 20) nCaster = 20; } + int nDC = PRCGetSaveDC(oTarget, oCaster); if(!nSpell) nSpell = PRCGetSpellId(); int nSpellLevel = 0; @@ -1332,6 +1673,7 @@ int InscribeRune(object oTarget = OBJECT_INVALID, object oCaster = OBJECT_INVALI // Minimum level. if (nSpellLevel == 0) nSpellLevel = 1; + // This will be modified with Runecaster code later. int nCharges = 1; if (GetLocalInt(oCaster, "RuneCharges")) nCharges = nCount; @@ -1440,9 +1782,14 @@ int InscribeRune(object oTarget = OBJECT_INVALID, object oCaster = OBJECT_INVALI // If they have this active, the bonuses are already added, so skip // If they don't, add the bonuses down below if(GetHasSpellEffect(SPELL_RUNE_CHANT)) - nRuneChant = 0; + nRuneChant = 0; - itemproperty ipLevel = ItemPropertyCastSpellCasterLevel(nSpell, PRCGetCasterLevel()); + //:: Check for Inscribe Epic Runes and cap CL at 20 if it doesn't exist. + nCaster = PRCGetCasterLevel(); + + if (!bEpicRunes) { if(nCaster > 20) nCaster = 20; } + + itemproperty ipLevel = ItemPropertyCastSpellCasterLevel(nSpell, nCaster); AddItemProperty(DURATION_TYPE_PERMANENT,ipLevel,oRune); itemproperty ipMeta = ItemPropertyCastSpellMetamagic(nSpell, PRCGetMetaMagicFeat()); AddItemProperty(DURATION_TYPE_PERMANENT,ipMeta,oRune); @@ -1544,18 +1891,28 @@ 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. if(!GetIsObjectValid(oTarget)) oTarget = PRCGetSpellTargetObject(); - // Only accepts bioware gems - if (GetStringLeft(GetResRef(oTarget), 5) == "it_gem") + // Only accepts bioware gems & Craftable Natural Resources gems, but not gem dust. + int bIsBioGem = (GetStringLeft(GetResRef(oTarget), 5) == "it_gem"); + int bIsCNRGem = (GetStringLeft(GetResRef(oTarget), 6) == "cnrgem"); + int bIsDust = (GetStringLeft(GetResRef(oTarget), 10) == "cnrgemdust"); + + if (!(bIsBioGem || bIsCNRGem) || bIsDust) + { + FloatingTextStringOnCreature("Spell target is not a valid gem.", oCaster, FALSE); + return TRUE; + } + +/* if ((GetStringLeft(GetResRef(oTarget), 5) == "it_gem") || (GetStringLeft(GetResRef(oTarget), 6) == "cnrgem") && (GetStringLeft(GetResRef(oTarget), 10) != "cnrgemdust")) { FloatingTextStringOnCreature("Spell target is not a valid gem.", oCaster, FALSE); // And out we go return TRUE; - } + } */ int nCaster = GetAlternativeCasterLevel(oCaster, PRCGetCasterLevel(oCaster)); int nDC = PRCGetSaveDC(oTarget, oCaster); @@ -1952,6 +2309,19 @@ int CIGetSpellWasUsedForItemCreation(object oSpellTarget) nRet = CICraftCheckCraftStaff(oSpellTarget,oCaster); break; + case BASE_ITEM_CRAFTED_SCEPTER : + // ------------------------------------------------- + // Craft Scepter + // ------------------------------------------------- + nRet = CICraftCheckCraftScepter(oSpellTarget,oCaster); + break; + + case BASE_ITEM_MUNDANE_HERB : + // ------------------------------------------------- + // Create Infusion + // ------------------------------------------------- + nRet = CICraftCheckCreateInfusion(oSpellTarget,oCaster); + break; // you could add more crafting basetypes here.... } @@ -2740,6 +3110,16 @@ int GetMagicalArtisanFeat(int nCraftingFeat) nReturn = FEAT_MAGICAL_ARTISAN_CRAFT_SKULL_TALISMAN; break; } + case FEAT_CREATE_INFUSION: + { + nReturn = FEAT_MAGICAL_ARTISAN_CREATE_INFUSION; + break; + } + case FEAT_CRAFT_SCEPTER: + { + nReturn = FEAT_MAGICAL_ARTISAN_CRAFT_SCEPTER; + break; + } default: { if(DEBUG) DoDebug("GetMagicalArtisanFeat: invalid crafting feat"); @@ -2941,6 +3321,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..1abf20c 100644 --- a/src/include/prc_x2_itemprop.nss +++ b/src/include/prc_x2_itemprop.nss @@ -20,9 +20,6 @@ //:: Last Update: 2003-10-07 //::////////////////////////////////////////////// -//:: Test void -//:: void main (){} - //Changed by primogenitor to include CEP itemtypes // * The tag of the ip work container, a placeable which has to be set into each @@ -697,6 +694,7 @@ if(nItem == BASE_ITEM_BASTARDSWORD || nItem == BASE_ITEM_SICKLE || nItem == BASE_ITEM_TWOBLADEDSWORD || nItem == BASE_ITEM_CLUB + || nItem == BASE_ITEM_CRAFTED_SCEPTER || nItem == BASE_ITEM_DAGGER || nItem == BASE_ITEM_DIREMACE || nItem == BASE_ITEM_HEAVYFLAIL @@ -729,6 +727,7 @@ if(nItem == BASE_ITEM_BASTARDSWORD || nItem == BASE_ITEM_ELVEN_THINBLADE || nItem == BASE_ITEM_ELVEN_COURTBLADE || nItem == BASE_ITEM_CRAFTED_STAFF + || nItem == BASE_ITEM_CRAFTED_SCEPTER || nItem == 300 //CEP Trident || nItem == 303 //CEP Sai || nItem == 304 //CEP nunchaku @@ -768,7 +767,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) @@ -1613,31 +1611,140 @@ int IPGetDamageBonusConstantFromNumber(int nNumber) // oOld - Item equipped before polymorphing (source for item props) // oNew - Item equipped after polymorphing (target for item props) // bWeapon - Must be set TRUE when oOld is a weapon. -// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- void IPWildShapeCopyItemProperties(object oOld, object oNew, int bWeapon = FALSE) { - if (GetIsObjectValid(oOld) && GetIsObjectValid(oNew)) - { - itemproperty ip = GetFirstItemProperty(oOld); - while (GetIsItemPropertyValid(ip)) - { - if (bWeapon) - { - if (GetWeaponRanged(oOld) == GetWeaponRanged(oNew) ) - { - AddItemProperty(DURATION_TYPE_PERMANENT,ip,oNew); - } - } - else - { - AddItemProperty(DURATION_TYPE_PERMANENT,ip,oNew); - } - ip = GetNextItemProperty(oOld); + // Invalid source/target + if (!GetIsObjectValid(oOld) || !GetIsObjectValid(oNew)) + return; + + // Determine possessor + object oPC = GetItemPossessor(oOld); + if (!GetIsObjectValid(oPC)) + oPC = GetItemPossessor(oNew); - } - } + if (!GetIsObjectValid(oPC)) + { + if (DEBUG) DoDebug("IPWS: Unable to determine item possessor"); + return; + } + + // Determine glove state once + int bMonkGloves = GetLocalInt(oPC, "WEARING_MONK_GLOVES"); + + // Weapon ranged mismatch = do nothing (intent is no partial copy) + if (bWeapon && GetWeaponRanged(oOld) != GetWeaponRanged(oNew)) + { + if (DEBUG) DoDebug("IPWS: Weapon ranged mismatch — skipping all IP copy"); + return; + } + + // Begin property copy + itemproperty ip = GetFirstItemProperty(oOld); + while (GetIsItemPropertyValid(ip)) + { + int nType = GetItemPropertyType(ip); + + // If copying from gloves and monk gloves are active + if (bMonkGloves + && (nType == ITEM_PROPERTY_DAMAGE_BONUS + || nType == ITEM_PROPERTY_DAMAGE_BONUS_VS_RACIAL_GROUP + || nType == ITEM_PROPERTY_DAMAGE_BONUS_VS_ALIGNMENT_GROUP)) + { + // Always apply glove damage IPs + AddItemProperty(DURATION_TYPE_PERMANENT, ip, oNew); + ip = GetNextItemProperty(oOld); + continue; + } + + // Normal weapon pass + if (bWeapon) + { + // If monk gloves active ? skip ALL weapon damage IPs + if (bMonkGloves + && (nType == ITEM_PROPERTY_DAMAGE_BONUS + || nType == ITEM_PROPERTY_DAMAGE_BONUS_VS_RACIAL_GROUP + || nType == ITEM_PROPERTY_DAMAGE_BONUS_VS_ALIGNMENT_GROUP)) + { + ip = GetNextItemProperty(oOld); + continue; + } + + AddItemProperty(DURATION_TYPE_PERMANENT, ip, oNew); + } + else + { + AddItemProperty(DURATION_TYPE_PERMANENT, ip, oNew); + } + + ip = GetNextItemProperty(oOld); + } } + +/* // ---------------------------------------------------------------------------- +// GZ, Sept. 30 2003 +// Special Version of Copy Item Properties for use with greater wild shape +// oOld - Item equipped before polymorphing (source for item props) +// oNew - Item equipped after polymorphing (target for item props) +// bWeapon - Must be set TRUE when oOld is a weapon. +// ---------------------------------------------------------------------------- +void IPWildShapeCopyItemProperties(object oOld, object oNew, int bWeapon = FALSE) +{ + if (!GetIsObjectValid(oOld) || !GetIsObjectValid(oNew)) + return; + + object oPC = GetItemPossessor(oOld); + if (!GetIsObjectValid(oPC)) + { + oPC = GetItemPossessor(oNew); + } + if (!GetIsObjectValid(oPC)) + { + if (DEBUG) DoDebug("IPWS: Unable to determine item possessor"); + return; + } + + int bMonkGloves = GetLocalInt(oPC, "WEARING_MONK_GLOVES"); + + itemproperty ip = GetFirstItemProperty(oOld); + while (GetIsItemPropertyValid(ip)) + { + if (bWeapon) + { + // Gloves override weapon damage — skip weapon damage properties + if (bMonkGloves) + { + int nType = GetItemPropertyType(ip); + + // skip damage props + if (nType == ITEM_PROPERTY_DAMAGE_BONUS + || nType == ITEM_PROPERTY_DAMAGE_BONUS_VS_RACIAL_GROUP + || nType == ITEM_PROPERTY_DAMAGE_BONUS_VS_ALIGNMENT_GROUP) + { + if (DEBUG) DoDebug("IPWS: SKIPPED weapon damage IP"); + } + else + { + if (DEBUG) DoDebug("IPWS: Applied non-damage weapon IP"); + AddItemProperty(DURATION_TYPE_PERMANENT, ip, oNew); + } + + } + else if (GetWeaponRanged(oOld) == GetWeaponRanged(oNew) ) + { + AddItemProperty(DURATION_TYPE_PERMANENT,ip,oNew); + } + } + else + { + AddItemProperty(DURATION_TYPE_PERMANENT,ip,oNew); + } + + ip = GetNextItemProperty(oOld); + } +} */ + // ---------------------------------------------------------------------------- // Returns the current enhancement bonus of a weapon (+1 to +20), 0 if there is // no enhancement bonus. You can test for a specific type of enhancement bonus @@ -1883,7 +1990,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; } @@ -2019,4 +2126,6 @@ int IPOnHitSaveDC(int nSaveDC) if (nSaveDC > 26) nIPBonus = IP_CONST_ONHIT_SAVEDC_26; return nIPBonus; -} */ \ No newline at end of file +} */ + +//:: void main(){} \ No newline at end of file diff --git a/src/include/prcsp_archmaginc.nss b/src/include/prcsp_archmaginc.nss index 4287a19..72e5d44 100644 --- a/src/include/prcsp_archmaginc.nss +++ b/src/include/prcsp_archmaginc.nss @@ -73,6 +73,7 @@ void SetMasteryOfElements(); //#include "lookup_2da_spell" #include "prcsp_reputation" +#include "prc_inc_core" //#include "prc_inc_spells" diff --git a/src/include/psi_inc_ac_spawn.nss b/src/include/psi_inc_ac_spawn.nss index 5b3947e..43765dd 100644 --- a/src/include/psi_inc_ac_spawn.nss +++ b/src/include/psi_inc_ac_spawn.nss @@ -11,6 +11,7 @@ #include "prc_ipfeat_const" #include "prc_feat_const" #include "inc_vfx_const" +#include "prc_inc_nwscript" ////////////////////////////////////////////////// diff --git a/src/include/psi_inc_core.nss b/src/include/psi_inc_core.nss index 9083bee..b4ca245 100644 --- a/src/include/psi_inc_core.nss +++ b/src/include/psi_inc_core.nss @@ -41,6 +41,8 @@ const int POWER_LIST_WARMIND = CLASS_TYPE_WARMIND; /* Function prototypes */ ////////////////////////////////////////////////// +int IsHiddenTalent(object oPC = OBJECT_SELF); + /** * Attempts to use psionic focus. If the creature was focused, it * loses the focus. If it has Epic Psionic Focus feats, it will @@ -520,9 +522,9 @@ void GainPsionicFocus(object oGainee = OBJECT_SELF) { int nPsySneak = 1; if(GetHasFeat(FEAT_PSY_SNEAK_ATTACK_2d6, oGainee)) - nPsySneak += 2; + nPsySneak += 1; if(GetHasFeat(FEAT_PSY_SNEAK_ATTACK_3d6, oGainee)) - nPsySneak += 3; + nPsySneak += 1; SetLocalInt(oGainee, "PsyRogueSneak",nPsySneak); DelayCommand(0.1, ExecuteScript("prc_sneak_att", oGainee)); @@ -786,69 +788,12 @@ int GetIsPsionicCharacter(object oCreature) GetHasFeat(FEAT_KALASHTAR_PP, oCreature) || GetHasFeat(FEAT_NATPSIONIC_1, oCreature) || GetHasFeat(FEAT_NATPSIONIC_2, oCreature) || - GetHasFeat(FEAT_NATPSIONIC_3, oCreature) + GetHasFeat(FEAT_NATPSIONIC_3, oCreature) || + IsHiddenTalent(oCreature) // Racial psionicity signifying feats go here ); } -int IsHiddenTalent(object oPC = OBJECT_SELF) -{ - if (GetHasFeat(FEAT_HIDDEN_TALENT_BIOFEEDBACK, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_BITE_WOLF, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_BOLT, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_BURST, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_CALLTOMIND, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_CALL_WEAPONRY, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_CHAMELEON, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_CLAWS_BEAST, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_COMPRESSION, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_CONCEALTHOUGHT, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_CREATESOUND, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_CRYSTALSHARD, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_DAZE, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_DECELERATION, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_DEFPRECOG, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_DEMORALIZE, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_DISABLE, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_DISSIPATINGTOUCH, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_DISTRACT, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_ELFSIGHT, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_EMPATHY, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_EMPTYMIND, oPC) || - //GetHasFeat(FEAT_HIDDEN_TALENT_ENERGYRAY, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_ENTANGLE, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_EXPANSION, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_FARHAND, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_FORCESCREEN, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_GREASE, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_HAMMER, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_INERTIALARMOUR, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_MATTERAGITATION, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_METAPHYSICAL_CLAW, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_METAPHYSICAL_WEAPON, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_MINDTHRUST, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_MYLIGHT, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_OFFPRECOG, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_OFFPRESC, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_PREVENOM, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_PREVENOM_WEAPON, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_SKATE, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_STOMP, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_SYNESTHETE, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_TELEMPATHICPRO, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_THICKSKIN, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_VIGOR, oPC) || - GetHasFeat(FEAT_HIDDEN_TALENT_GRIP_IRON, oPC)) - { - return TRUE; - } - else - { - return FALSE; - } -} - - void LocalCleanExtraFists(object oCreature) { int iIsCWeap, iIsEquip; @@ -985,6 +930,48 @@ int PracticedManifesting(object oManifester, int iManifestingClass, int iManifes int GetManifesterLevel(object oManifester, int nSpecificClass = CLASS_TYPE_INVALID, int nMaxPowerLevel = FALSE) { + // Handle POWER_LIST_MISC (Hidden Talent) powers + // Check if this is a power list call + int nPowerType = GetLocalInt(oManifester, "PRC_UsePowerList"); + + if(nSpecificClass == CLASS_TYPE_INVALID && nPowerType == POWER_LIST_MISC) + { + if(DEBUG) DoDebug("psi_inc_core >> GetManifesterLevel: CLASS_TYPE_INVALID + POWER_LIST_MISC found!"); + // Check if character has psionic class levels + int nPsionLevel = GetLevelByClass(CLASS_TYPE_PSION, oManifester); + int nPsywarLevel = GetLevelByClass(CLASS_TYPE_PSYWAR, oManifester); + int nWilderLevel = GetLevelByClass(CLASS_TYPE_WILDER, oManifester); + int nWarmindLevel = GetLevelByClass(CLASS_TYPE_WARMIND, oManifester); + int nFistOfZuokenLevel = GetLevelByClass(CLASS_TYPE_FIST_OF_ZUOKEN, oManifester); + int nPsychicRogueLevel = GetLevelByClass(CLASS_TYPE_PSYCHIC_ROGUE, oManifester); + + // If no psionic levels, use Charisma-based calculation (treat as 1st level) + if(nPsionLevel + nPsywarLevel + nWilderLevel + nWarmindLevel + + nFistOfZuokenLevel + nPsychicRogueLevel == 0) + { + // Hidden Talent: considered 1st-level manifester, but must have CHA 11+ + if(DEBUG) DoDebug("psi_inc_core >> GetManifesterLevel: Hidden Talent found!"); + if(GetAbilityScore(oManifester, ABILITY_CHARISMA) >= 11) + return 1; + else + return 0; // Cannot manifest without CHA 11+ + } + + if(DEBUG) DoDebug("psi_inc_core >> GetManifesterLevel: nSpecificClass=" + IntToString(nSpecificClass) + + ", nPowerType=" + IntToString(nPowerType)); + + // Has psionic levels - return highest manifester level + int nHighest = 0; + if(nPsionLevel > 0) nHighest = GetManifesterLevel(oManifester, CLASS_TYPE_PSION); + if(nPsywarLevel > 0) nHighest = PRCMax(nHighest, GetManifesterLevel(oManifester, CLASS_TYPE_PSYWAR)); + if(nWilderLevel > 0) nHighest = PRCMax(nHighest, GetManifesterLevel(oManifester, CLASS_TYPE_WILDER)); + if(nWarmindLevel > 0) nHighest = PRCMax(nHighest, GetManifesterLevel(oManifester, CLASS_TYPE_WARMIND)); + if(nFistOfZuokenLevel > 0) nHighest = PRCMax(nHighest, GetManifesterLevel(oManifester, CLASS_TYPE_FIST_OF_ZUOKEN)); + if(nPsychicRogueLevel > 0) nHighest = PRCMax(nHighest, GetManifesterLevel(oManifester, CLASS_TYPE_PSYCHIC_ROGUE)); + + return nHighest; + } + int nLevel; int nAdjust = GetLocalInt(oManifester, PRC_CASTERLEVEL_ADJUSTMENT); nAdjust -= GetLocalInt(oManifester, "WoLManifPenalty"); @@ -1049,17 +1036,27 @@ int GetManifesterLevel(object oManifester, int nSpecificClass = CLASS_TYPE_INVAL DelayCommand(1.0, DeleteLocalInt(oManifester, PRC_CASTERLEVEL_OVERRIDE)); nLevel = GetLocalInt(oManifester, PRC_CASTERLEVEL_OVERRIDE); - } - else if(GetManifestingClass(oManifester) != CLASS_TYPE_INVALID) - { - //Gets the manifesting class - int nManifestingClass = GetManifestingClass(oManifester); - if(DEBUG) DoDebug("Manifesting Class #2: " + IntToString(nManifestingClass), oManifester); - nLevel = GetLevelByClass(nManifestingClass, oManifester); - // Add levels from +ML PrCs only for the first manifesting class - nLevel += GetPsionicPRCLevels(oManifester, nManifestingClass); - //nLevel += nManifestingClass == GetPrimaryPsionicClass(oManifester) ? GetPsionicPRCLevels(oManifester) : 0; - + } + else if(GetManifestingClass(oManifester) != CLASS_TYPE_INVALID) + { + //Gets the manifesting class + int nManifestingClass = GetManifestingClass(oManifester); + if(DEBUG) DoDebug("Manifesting Class #2: " + IntToString(nManifestingClass), oManifester); + + nLevel = GetLevelByClass(nManifestingClass, oManifester); + // Add levels from +ML PrCs only for the first manifesting class + nLevel += GetPsionicPRCLevels(oManifester, nManifestingClass); + + // CHECK: If this is Hidden Talent and character has no levels, set to 1 + if(nLevel == 0 && GetLocalInt(oManifester, "PRC_UsePowerList") == TRUE && + GetLocalInt(oManifester, "PRC_PowerListType") == POWER_LIST_MISC) + { + if(GetAbilityScore(oManifester, ABILITY_CHARISMA) >= 11) + { + if(DEBUG) DoDebug("GetManifesterLevel: Hidden Talent with no psionic levels, returning 1"); + nLevel = 1; + } + } // Psionic vestiges are tucked in here to override things. // This assumes that there will never be a psion with this spell effect manifesting things if (nManifestingClass == CLASS_TYPE_PSION && GetHasSpellEffect(VESTIGE_ARETE, oManifester) && !nMaxPowerLevel) @@ -1085,7 +1082,37 @@ int GetManifesterLevel(object oManifester, int nSpecificClass = CLASS_TYPE_INVAL // if(DEBUG) DoDebug("Level gotten via GetLevelByClass: " + IntToString(nLevel), oManifester); } - // If you have a primary psionic class and no manifester level yet, get levels based on that + // If you have a primary psionic class and no manifester level yet, get levels based on that + if (GetPrimaryPsionicClass(oManifester) && nLevel == 0) + { + int nClass = GetPrimaryPsionicClass(oManifester); + nLevel = GetLevelByClass(nClass, oManifester); + nLevel += GetPsionicPRCLevels(oManifester, nClass); + nLevel += PracticedManifesting(oManifester, nClass, nLevel); + } + + // If everything else fails, check for Hidden Talent before returning 0 + if(nLevel == 0) + { + // Check if this is a Hidden Talent power + if(GetLocalInt(oManifester, "PRC_UsePowerList") == POWER_LIST_MISC) + { + // Hidden Talent: manifester level is 1 if they have CHA 11+ + if(GetAbilityScore(oManifester, ABILITY_CHARISMA) >= 11) + { + if(DEBUG) DoDebug("GetManifesterLevel: Hidden Talent character, returning level 1"); + return 1; + } + } + + if(DEBUG) DoDebug("Failed to get manifester level for creature " + DebugObject2Str(oManifester) + ", using first class slot"); + //else WriteTimestampedLogEntry("Failed to get manifester level for creature " + DebugObject2Str(oManifester) + ", using first class slot"); + + return 0; + } + + +/* // If you have a primary psionic class and no manifester level yet, get levels based on that if (GetPrimaryPsionicClass(oManifester) && nLevel == 0) { int nClass = GetPrimaryPsionicClass(oManifester); @@ -1102,7 +1129,7 @@ int GetManifesterLevel(object oManifester, int nSpecificClass = CLASS_TYPE_INVAL return 0; } - + */ // The bonuses inside only apply to normal manifestation if(!GetLocalInt(oManifester, PRC_IS_PSILIKE)) @@ -1665,4 +1692,225 @@ int GetMaxPowerLevel(object oManifester) int nMax = StringToInt(Get2DACache(sFile, "MaxPowerLevel", nLevel)); if (DEBUG) DoDebug("GetMaxPowerLevel is "+IntToString(nMax)); return nMax; -} \ No newline at end of file +} + +////////////////////////////////////////////////////// +/* START HIDDEN TALENT */ +////////////////////////////////////////////////////// + +int IsHiddenTalent(object oPC = OBJECT_SELF) +{ + if (GetHasFeat(FEAT_HIDDEN_TALENT_BIOFEEDBACK, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_BITE_WOLF, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_BOLT, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_BURST, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_CALLTOMIND, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_CALL_WEAPONRY, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_CHAMELEON, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_CLAWS_BEAST, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_COMPRESSION, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_CONCEALTHOUGHT, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_CREATESOUND, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_CRYSTALSHARD, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_DAZE, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_DECELERATION, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_DEFPRECOG, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_DEMORALIZE, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_DISABLE, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_DISSIPATINGTOUCH, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_DISTRACT, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_ELFSIGHT, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_EMPATHY, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_EMPTYMIND, oPC) || + //GetHasFeat(FEAT_HIDDEN_TALENT_ENERGYRAY, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_ENTANGLE, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_EXPANSION, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_FARHAND, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_FORCESCREEN, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_GREASE, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_HAMMER, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_INERTIALARMOUR, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_MATTERAGITATION, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_METAPHYSICAL_CLAW, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_METAPHYSICAL_WEAPON, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_MINDTHRUST, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_MYLIGHT, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_OFFPRECOG, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_OFFPRESC, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_PREVENOM, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_PREVENOM_WEAPON, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_SKATE, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_STOMP, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_SYNESTHETE, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_TELEMPATHICPRO, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_THICKSKIN, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_VIGOR, oPC) || + GetHasFeat(FEAT_HIDDEN_TALENT_GRIP_IRON, oPC)) + { + return TRUE; + } + else + { + return FALSE; + } +} + +int GetHiddenTalentPowerFromFeat(int nFeatID) +{ + // Map Hidden Talent feats to their corresponding power IDs + // Using the same mappings as GetIsHiddenTalentPower() + if(nFeatID == FEAT_HIDDEN_TALENT_BIOFEEDBACK) return POWER_BIOFEEDBACK; + if(nFeatID == FEAT_HIDDEN_TALENT_BITE_WOLF) return POWER_BITE_WOLF; + if(nFeatID == FEAT_HIDDEN_TALENT_BOLT) return POWER_BOLT; + if(nFeatID == FEAT_HIDDEN_TALENT_BURST) return POWER_BURST; + if(nFeatID == FEAT_HIDDEN_TALENT_CALLTOMIND) return POWER_CALLTOMIND; + if(nFeatID == FEAT_HIDDEN_TALENT_CALL_WEAPONRY) return POWER_CALL_WEAPONRY; + if(nFeatID == FEAT_HIDDEN_TALENT_CHAMELEON) return POWER_CHAMELEON; + if(nFeatID == FEAT_HIDDEN_TALENT_CLAWS_BEAST) return POWER_CLAWS_BEAST; + if(nFeatID == FEAT_HIDDEN_TALENT_COMPRESSION) return POWER_COMPRESSION; + if(nFeatID == FEAT_HIDDEN_TALENT_CONCEALTHOUGHT) return POWER_CONCEALTHOUGHT; + if(nFeatID == FEAT_HIDDEN_TALENT_CREATESOUND) return POWER_CREATESOUND; + if(nFeatID == FEAT_HIDDEN_TALENT_CRYSTALSHARD) return POWER_CRYSTALSHARD; + if(nFeatID == FEAT_HIDDEN_TALENT_DAZE) return POWER_DAZE; + if(nFeatID == FEAT_HIDDEN_TALENT_DECELERATION) return POWER_DECELERATION; + if(nFeatID == FEAT_HIDDEN_TALENT_DEFPRECOG) return POWER_DEFPRECOG; + if(nFeatID == FEAT_HIDDEN_TALENT_DEMORALIZE) return POWER_DEMORALIZE; + if(nFeatID == FEAT_HIDDEN_TALENT_DISABLE) return POWER_DISABLE; + if(nFeatID == FEAT_HIDDEN_TALENT_DISSIPATINGTOUCH)return POWER_DISSIPATINGTOUCH; + if(nFeatID == FEAT_HIDDEN_TALENT_DISTRACT) return POWER_DISTRACT; + if(nFeatID == FEAT_HIDDEN_TALENT_ELFSIGHT) return POWER_ELFSIGHT; + if(nFeatID == FEAT_HIDDEN_TALENT_EMPATHY) return POWER_EMPATHY; + if(nFeatID == FEAT_HIDDEN_TALENT_EMPTYMIND) return POWER_EMPTYMIND; + if(nFeatID == FEAT_HIDDEN_TALENT_ENTANGLE) return POWER_ENTANGLE; + if(nFeatID == FEAT_HIDDEN_TALENT_EXPANSION) return POWER_EXPANSION; + if(nFeatID == FEAT_HIDDEN_TALENT_FARHAND) return POWER_FARHAND; + if(nFeatID == FEAT_HIDDEN_TALENT_FORCESCREEN) return POWER_FORCESCREEN; + if(nFeatID == FEAT_HIDDEN_TALENT_GREASE) return POWER_GREASE; + if(nFeatID == FEAT_HIDDEN_TALENT_HAMMER) return POWER_HAMMER; + if(nFeatID == FEAT_HIDDEN_TALENT_INERTIALARMOUR) return POWER_INERTIALARMOUR; + if(nFeatID == FEAT_HIDDEN_TALENT_MATTERAGITATION) return POWER_MATTERAGITATION; + if(nFeatID == FEAT_HIDDEN_TALENT_METAPHYSICAL_CLAW) return POWER_METAPHYSICAL_CLAW; + if(nFeatID == FEAT_HIDDEN_TALENT_METAPHYSICAL_WEAPON) return POWER_METAPHYSICAL_WEAPON; + if(nFeatID == FEAT_HIDDEN_TALENT_MINDTHRUST) return POWER_MINDTHRUST; + if(nFeatID == FEAT_HIDDEN_TALENT_MYLIGHT) return POWER_MYLIGHT; + if(nFeatID == FEAT_HIDDEN_TALENT_OFFPRECOG) return POWER_OFFPRECOG; + if(nFeatID == FEAT_HIDDEN_TALENT_OFFPRESC) return POWER_OFFPRESC; + if(nFeatID == FEAT_HIDDEN_TALENT_PREVENOM) return POWER_PREVENOM; + if(nFeatID == FEAT_HIDDEN_TALENT_PREVENOM_WEAPON) return POWER_PREVENOM_WEAPON; + if(nFeatID == FEAT_HIDDEN_TALENT_SKATE) return POWER_SKATE; + if(nFeatID == FEAT_HIDDEN_TALENT_STOMP) return POWER_STOMP; + if(nFeatID == FEAT_HIDDEN_TALENT_SYNESTHETE) return POWER_SYNESTHETE; + if(nFeatID == FEAT_HIDDEN_TALENT_TELEMPATHICPRO) return POWER_TELEMPATHICPRO; + if(nFeatID == FEAT_HIDDEN_TALENT_THICKSKIN) return POWER_THICKSKIN; + if(nFeatID == FEAT_HIDDEN_TALENT_VIGOR) return POWER_VIGOR; + if(nFeatID == FEAT_HIDDEN_TALENT_GRIP_IRON) return POWER_GRIP_IRON; + + return -1; // Not found +} + +int GetHiddenTalentCount(object oPC = OBJECT_SELF) +{ + int nCount = 0; + + if (GetHasFeat(FEAT_HIDDEN_TALENT_BIOFEEDBACK, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_BITE_WOLF, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_BOLT, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_BURST, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_CALLTOMIND, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_CALL_WEAPONRY, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_CHAMELEON, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_CLAWS_BEAST, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_COMPRESSION, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_CONCEALTHOUGHT, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_CREATESOUND, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_CRYSTALSHARD, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_DAZE, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_DECELERATION, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_DEFPRECOG, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_DEMORALIZE, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_DISABLE, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_DISSIPATINGTOUCH, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_DISTRACT, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_ELFSIGHT, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_EMPATHY, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_EMPTYMIND, oPC)) nCount++; + //if (GetHasFeat(FEAT_HIDDEN_TALENT_ENERGYRAY, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_ENTANGLE, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_EXPANSION, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_FARHAND, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_FORCESCREEN, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_GREASE, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_HAMMER, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_INERTIALARMOUR, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_MATTERAGITATION, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_METAPHYSICAL_CLAW, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_METAPHYSICAL_WEAPON, oPC))nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_MINDTHRUST, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_MYLIGHT, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_OFFPRECOG, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_OFFPRESC, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_PREVENOM, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_PREVENOM_WEAPON, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_SKATE, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_STOMP, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_SYNESTHETE, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_TELEMPATHICPRO, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_THICKSKIN, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_VIGOR, oPC)) nCount++; + if (GetHasFeat(FEAT_HIDDEN_TALENT_GRIP_IRON, oPC)) nCount++; + + return nCount; +} + +int GetIsHiddenTalentPower(object oPC, int nPower) +{ + // Check each Hidden Talent feat to see if it grants this power + if(nPower == POWER_BIOFEEDBACK && GetHasFeat(FEAT_HIDDEN_TALENT_BIOFEEDBACK, oPC)) return TRUE; + if(nPower == POWER_BITE_WOLF && GetHasFeat(FEAT_HIDDEN_TALENT_BITE_WOLF, oPC)) return TRUE; + if(nPower == POWER_BOLT && GetHasFeat(FEAT_HIDDEN_TALENT_BOLT, oPC)) return TRUE; + if(nPower == POWER_BURST && GetHasFeat(FEAT_HIDDEN_TALENT_BURST, oPC)) return TRUE; + if(nPower == POWER_CALLTOMIND && GetHasFeat(FEAT_HIDDEN_TALENT_CALLTOMIND, oPC)) return TRUE; + if(nPower == POWER_CALL_WEAPONRY && GetHasFeat(FEAT_HIDDEN_TALENT_CALL_WEAPONRY, oPC)) return TRUE; + if(nPower == POWER_CHAMELEON && GetHasFeat(FEAT_HIDDEN_TALENT_CHAMELEON, oPC)) return TRUE; + if(nPower == POWER_CLAWS_BEAST && GetHasFeat(FEAT_HIDDEN_TALENT_CLAWS_BEAST, oPC)) return TRUE; + if(nPower == POWER_COMPRESSION && GetHasFeat(FEAT_HIDDEN_TALENT_COMPRESSION, oPC)) return TRUE; + if(nPower == POWER_CONCEALTHOUGHT && GetHasFeat(FEAT_HIDDEN_TALENT_CONCEALTHOUGHT, oPC)) return TRUE; + if(nPower == POWER_CREATESOUND && GetHasFeat(FEAT_HIDDEN_TALENT_CREATESOUND, oPC)) return TRUE; + if(nPower == POWER_CRYSTALSHARD && GetHasFeat(FEAT_HIDDEN_TALENT_CRYSTALSHARD, oPC)) return TRUE; + if(nPower == POWER_DAZE && GetHasFeat(FEAT_HIDDEN_TALENT_DAZE, oPC)) return TRUE; + if(nPower == POWER_DECELERATION && GetHasFeat(FEAT_HIDDEN_TALENT_DECELERATION, oPC)) return TRUE; + if(nPower == POWER_DEFPRECOG && GetHasFeat(FEAT_HIDDEN_TALENT_DEFPRECOG, oPC)) return TRUE; + if(nPower == POWER_DEMORALIZE && GetHasFeat(FEAT_HIDDEN_TALENT_DEMORALIZE, oPC)) return TRUE; + if(nPower == POWER_DISABLE && GetHasFeat(FEAT_HIDDEN_TALENT_DISABLE, oPC)) return TRUE; + if(nPower == POWER_DISSIPATINGTOUCH && GetHasFeat(FEAT_HIDDEN_TALENT_DISSIPATINGTOUCH, oPC)) return TRUE; + if(nPower == POWER_DISTRACT && GetHasFeat(FEAT_HIDDEN_TALENT_DISTRACT, oPC)) return TRUE; + if(nPower == POWER_ELFSIGHT && GetHasFeat(FEAT_HIDDEN_TALENT_ELFSIGHT, oPC)) return TRUE; + if(nPower == POWER_EMPATHY && GetHasFeat(FEAT_HIDDEN_TALENT_EMPATHY, oPC)) return TRUE; + if(nPower == POWER_EMPTYMIND && GetHasFeat(FEAT_HIDDEN_TALENT_EMPTYMIND, oPC)) return TRUE; + //if(nPower == POWER_ENERGYRAY && GetHasFeat(FEAT_HIDDEN_TALENT_ENERGYRAY, oPC)) return TRUE; + if(nPower == POWER_ENTANGLE && GetHasFeat(FEAT_HIDDEN_TALENT_ENTANGLE, oPC)) return TRUE; + if(nPower == POWER_EXPANSION && GetHasFeat(FEAT_HIDDEN_TALENT_EXPANSION, oPC)) return TRUE; + if(nPower == POWER_FARHAND && GetHasFeat(FEAT_HIDDEN_TALENT_FARHAND, oPC)) return TRUE; + if(nPower == POWER_FORCESCREEN && GetHasFeat(FEAT_HIDDEN_TALENT_FORCESCREEN, oPC)) return TRUE; + if(nPower == POWER_GREASE && GetHasFeat(FEAT_HIDDEN_TALENT_GREASE, oPC)) return TRUE; + if(nPower == POWER_HAMMER && GetHasFeat(FEAT_HIDDEN_TALENT_HAMMER, oPC)) return TRUE; + if(nPower == POWER_INERTIALARMOUR && GetHasFeat(FEAT_HIDDEN_TALENT_INERTIALARMOUR, oPC)) return TRUE; + if(nPower == POWER_MATTERAGITATION && GetHasFeat(FEAT_HIDDEN_TALENT_MATTERAGITATION, oPC)) return TRUE; + if(nPower == POWER_METAPHYSICAL_CLAW && GetHasFeat(FEAT_HIDDEN_TALENT_METAPHYSICAL_CLAW, oPC)) return TRUE; + if(nPower == POWER_METAPHYSICAL_WEAPON && GetHasFeat(FEAT_HIDDEN_TALENT_METAPHYSICAL_WEAPON, oPC)) return TRUE; + if(nPower == POWER_MINDTHRUST && GetHasFeat(FEAT_HIDDEN_TALENT_MINDTHRUST, oPC)) return TRUE; + if(nPower == POWER_MYLIGHT && GetHasFeat(FEAT_HIDDEN_TALENT_MYLIGHT, oPC)) return TRUE; + if(nPower == POWER_OFFPRECOG && GetHasFeat(FEAT_HIDDEN_TALENT_OFFPRECOG, oPC)) return TRUE; + if(nPower == POWER_OFFPRESC && GetHasFeat(FEAT_HIDDEN_TALENT_OFFPRESC, oPC)) return TRUE; + if(nPower == POWER_PREVENOM && GetHasFeat(FEAT_HIDDEN_TALENT_PREVENOM, oPC)) return TRUE; + if(nPower == POWER_PREVENOM_WEAPON && GetHasFeat(FEAT_HIDDEN_TALENT_PREVENOM_WEAPON, oPC)) return TRUE; + if(nPower == POWER_SKATE && GetHasFeat(FEAT_HIDDEN_TALENT_SKATE, oPC)) return TRUE; + if(nPower == POWER_STOMP && GetHasFeat(FEAT_HIDDEN_TALENT_STOMP, oPC)) return TRUE; + if(nPower == POWER_SYNESTHETE && GetHasFeat(FEAT_HIDDEN_TALENT_SYNESTHETE, oPC)) return TRUE; + if(nPower == POWER_TELEMPATHICPRO && GetHasFeat(FEAT_HIDDEN_TALENT_TELEMPATHICPRO, oPC)) return TRUE; + if(nPower == POWER_THICKSKIN && GetHasFeat(FEAT_HIDDEN_TALENT_THICKSKIN, oPC)) return TRUE; + if(nPower == POWER_VIGOR && GetHasFeat(FEAT_HIDDEN_TALENT_VIGOR, oPC)) return TRUE; + if(nPower == POWER_GRIP_IRON && GetHasFeat(FEAT_HIDDEN_TALENT_GRIP_IRON, oPC)) return TRUE; + + return FALSE; +} diff --git a/src/include/psi_inc_metapsi.nss b/src/include/psi_inc_metapsi.nss index c005f0c..3148718 100644 --- a/src/include/psi_inc_metapsi.nss +++ b/src/include/psi_inc_metapsi.nss @@ -110,6 +110,7 @@ object GetSplitPsionicRayTarget(struct manifestation manif, object oPrimaryTarge ////////////////////////////////////////////////// #include "psi_inc_core" +#include "psi_inc_psifunc" ////////////////////////////////////////////////// /* Internal functions */ diff --git a/src/include/psi_inc_powknown.nss b/src/include/psi_inc_powknown.nss index 51e304f..20c1951 100644 --- a/src/include/psi_inc_powknown.nss +++ b/src/include/psi_inc_powknown.nss @@ -572,7 +572,11 @@ int GetMaxPowerCount(object oCreature, int nList) int GetHasPower(int nPower, object oCreature = OBJECT_SELF) { - if((GetLevelByClass(CLASS_TYPE_PSION, oCreature) + // Check MISC list first (for Hidden Talent and similar feats) + if(GetHasFeat(GetClassFeatFromPower(nPower, CLASS_TYPE_INVALID), oCreature)) + return TRUE; + + if((GetLevelByClass(CLASS_TYPE_PSION, oCreature) && GetHasFeat(GetClassFeatFromPower(nPower, CLASS_TYPE_PSION), oCreature) ) || (GetLevelByClass(CLASS_TYPE_PSYWAR, oCreature) diff --git a/src/include/psi_inc_ppoints.nss b/src/include/psi_inc_ppoints.nss index a98f0fb..ca66fba 100644 --- a/src/include/psi_inc_ppoints.nss +++ b/src/include/psi_inc_ppoints.nss @@ -93,9 +93,8 @@ void GainPowerPoints(object oChar, int nGain, int bCanExceedMax = FALSE, int bIn * @param bInform If TRUE, runs TellCharacterPowerPointStatus() on oChar * after making the modification. */ -/* void GainTemporaryPowerPoints(object oChar, int nGain, float fDuration, int bInform = TRUE); -*/ + /** * Decreases the character's current power point count by up to the given * amount, limited to not going below 0. @@ -138,8 +137,12 @@ int _GetFeatBonusPP(object oChar) { int nBonusPP = 0; -//:: Wild Talent & Hidden Talents - if(GetHasFeat(FEAT_WILD_TALENT, oChar) || IsHiddenTalent()) +//:: Wild Talent + if(GetHasFeat(FEAT_WILD_TALENT, oChar)) + nBonusPP += 2; + +//:: Hidden Talent + if(GetHasFeat(FEAT_HIDDEN_TALENT, oChar)) nBonusPP += 2; //:: Psionic Feats diff --git a/src/include/psi_inc_psicraft.nss b/src/include/psi_inc_psicraft.nss index 058ba59..e5e244d 100644 --- a/src/include/psi_inc_psicraft.nss +++ b/src/include/psi_inc_psicraft.nss @@ -40,7 +40,8 @@ void IdentifyPower(object oManifester, int nPowerId); // Always access via psi_inc_psifunc. -//#include "psi_inc_core" +#include "psi_inc_core" +#include "inc_2dacache" ////////////////////////////////////////////////// /* Internal functions */ diff --git a/src/include/psi_inc_psifunc.nss b/src/include/psi_inc_psifunc.nss index 5e96a40..c35b122 100644 --- a/src/include/psi_inc_psifunc.nss +++ b/src/include/psi_inc_psifunc.nss @@ -407,6 +407,8 @@ void _CleanManifestationVariables(object oManifester) DeleteLocalInt(oManifester, PRC_POWER_LEVEL); DeleteLocalInt(oManifester, PRC_IS_PSILIKE); DeleteLocalInt(oManifester, PRC_AUGMENT_OVERRIDE); + DeleteLocalInt(oManifester, "PRC_UsePowerList"); + DeleteLocalInt(oManifester, "PRC_PowerListType"); } /** Internal function. @@ -692,10 +694,28 @@ void _UsePowerAux(object oManifester, object oMfToken, int nSpellId, struct manifestation EvaluateManifestation(object oManifester, object oTarget, struct power_augment_profile pap, int nMetaPsiFlags) { - /* Get some data */ + //:: Handle Hidden Talent + int nSpellID = PRCGetSpellId(); + int bIsHiddenTalent = GetIsHiddenTalentPower(oManifester, nSpellID); + if(bIsHiddenTalent) + { + SetLocalInt(oManifester, "PRC_UsePowerList", TRUE); + SetLocalInt(oManifester, "PRC_PowerListType", POWER_LIST_MISC); + } + /* Get some data */ int bIgnoreConstraints = (DEBUG) ? GetLocalInt(oManifester, PRC_DEBUG_IGNORE_CONSTRAINTS) : FALSE; + // Manifester-related stuff - int nManifesterLevel = GetManifesterLevel(oManifester); + //int nManifesterLevel = GetManifesterLevel(oManifester); + int nManifesterLevel; + if(bIsHiddenTalent) + { + nManifesterLevel = GetManifesterLevel(oManifester, CLASS_TYPE_INVALID); + } + else + { + nManifesterLevel = GetManifesterLevel(oManifester); + } int nPowerLevel = GetPowerLevel(oManifester); int nClass = GetManifestingClass(oManifester); int nWildSurge = GetWildSurge(oManifester); @@ -714,6 +734,8 @@ struct manifestation EvaluateManifestation(object oManifester, object oTarget, s manif.nManifesterLevel = nManifesterLevel; manif.nSpellID = PRCGetSpellId(); + + // Run an ability score check to see if the manifester can manifest the power at all if (bIsPsiLike) { @@ -767,7 +789,9 @@ struct manifestation EvaluateManifestation(object oManifester, object oTarget, s //If the manifester does not have enough points before hostile modifiers, cancel power if(manif.nPPCost > nManifesterPP && !bIsPsiLike && !bIgnoreConstraints) { - FloatingTextStrRefOnCreature(16826412, oManifester, FALSE); // "You do not have enough Power Points to manifest this power" + // DEBUG: show why the cost over cap branch triggered + FloatingTextStringOnCreature("DEBUG: manif.nManifesterLevel=" + IntToString(manif.nManifesterLevel) + " manif.nPPCost=" + IntToString(manif.nPPCost) +" PRC_UsePowerList=" + IntToString(GetLocalInt(manif.oManifester, "PRC_UsePowerList")), manif.oManifester, FALSE); + FloatingTextStrRefOnCreature(16826412, oManifester, FALSE); // "You do not have enough Power Points to manifest this power" manif.bCanManifest = FALSE; } // The manifester has enough power points that they would be able to use the power, barring extra costs diff --git a/src/include/psi_inc_pwresist.nss b/src/include/psi_inc_pwresist.nss index e1c5a96..447c917 100644 --- a/src/include/psi_inc_pwresist.nss +++ b/src/include/psi_inc_pwresist.nss @@ -14,6 +14,7 @@ #include "prc_class_const" */ #include "prc_alterations" +#include "prcsp_engine" // Constants that dictate ResistPower results const int POWER_RESIST_FAIL = 1; diff --git a/src/include/psi_spellhook.nss b/src/include/psi_spellhook.nss index 12c651d..aef699f 100644 --- a/src/include/psi_spellhook.nss +++ b/src/include/psi_spellhook.nss @@ -75,6 +75,15 @@ int PsiPrePowerCastCode() int nContinue = !ExecuteScriptAndReturnInt("prespellcode", oManifester); + //--------------------------------------------------------------------------- + // Forsakers can't use psionics + //--------------------------------------------------------------------------- + if (nContinue && GetLevelByClass(CLASS_TYPE_FORSAKER, oManifester)) + { + FloatingTextStringOnCreature("Forsakers cannot manifest psionic powers!", oManifester, FALSE); + nContinue = FALSE; + } + //--------------------------------------------------------------------------- // Break any spell require maintaining concentration //--------------------------------------------------------------------------- @@ -209,3 +218,4 @@ int PsiPrePowerCastCode() return nContinue; } +//:: void main (){} \ No newline at end of file diff --git a/src/include/shd_inc_mystknwn.nss b/src/include/shd_inc_mystknwn.nss index b335485..0189d05 100644 --- a/src/include/shd_inc_mystknwn.nss +++ b/src/include/shd_inc_mystknwn.nss @@ -192,7 +192,8 @@ int PathFeatToIPFeat(int nFeat); /* Includes */ ////////////////////////////////////////////////// - +#include "inc_lookups" +#include "inc_pers_array" ////////////////////////////////////////////////// /* Internal functions */ @@ -519,7 +520,7 @@ int GetMaxMysteryLevelLearnable(object oShadow, int nClass, int nType) if(DEBUG) DoDebug("GetMaxMysteryLevelLearnable nType: " + IntToString(nType)); // Rules Quote: - // Within a category—Apprentice, Initiate, Master—you must have at least two mysteries of any given level + // Within a category�Apprentice, Initiate, Master�you must have at least two mysteries of any given level // before you can take any mysteries of the next higher level. For instance, you must have two 1st-level // mysteries before you can take any 2nds, and at least two 2nds before you can take any 3rds. int nMaxLrn, i, nMystLevel, nCount1, nCount2; diff --git a/src/include/shd_inc_shdfunc.nss b/src/include/shd_inc_shdfunc.nss index 63eb943..2d19e9f 100644 --- a/src/include/shd_inc_shdfunc.nss +++ b/src/include/shd_inc_shdfunc.nss @@ -210,6 +210,7 @@ int GetHasNocturnal(object oShadow, int nPath); #include "prc_alterations" #include "shd_inc_myst" #include "shd_inc_mystknwn" +#include "lookup_2da_spell" ////////////////////////////////////////////////// /* Internal functions */ @@ -236,12 +237,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 +289,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/shd_mysthook.nss b/src/include/shd_mysthook.nss index 51c63be..dc9ad89 100644 --- a/src/include/shd_mysthook.nss +++ b/src/include/shd_mysthook.nss @@ -17,6 +17,8 @@ #include "prc_inc_spells" #include "inc_utility" #include "prc_inc_itmrstr" +#include "shd_inc_shdfunc" +#include "lookup_2da_spell" // This function holds all functions that are supposed to run before the actual // spellscript gets run. If this functions returns FALSE, the spell is aborted @@ -132,6 +134,15 @@ int ShadPreMystCastCode() int nContinue = !ExecuteScriptAndReturnInt("prespellcode",oShadow); + //--------------------------------------------------------------------------- + // Block forsakers from using shadowcasting + //--------------------------------------------------------------------------- + if(GetLevelByClass(CLASS_TYPE_FORSAKER, oShadow) > 0) + { + SendMessageToPC(oShadow, "Forsakers cannot use the power of shadowcasting."); + return FALSE; + } + //--------------------------------------------------------------------------- // Break any spell require maintaining concentration //--------------------------------------------------------------------------- @@ -277,4 +288,6 @@ int ShadPreMystCastCode() if(DEBUG) DoDebug("ShadPreMystCastCode nContinue #6: " + IntToString(nContinue)); return nContinue; -} \ No newline at end of file +} + +//:: void main (){} diff --git a/src/include/tob_movehook.nss b/src/include/tob_movehook.nss index 3ac1493..614e2a8 100644 --- a/src/include/tob_movehook.nss +++ b/src/include/tob_movehook.nss @@ -14,6 +14,7 @@ #include "prc_inc_spells" #include "inc_utility" #include "x2_inc_spellhook" +#include "tob_inc_tobfunc" // This function holds all functions that are supposed to run before the actual // spellscript gets run. If this functions returns FALSE, the spell is aborted @@ -78,7 +79,14 @@ int PreManeuverCastCode() //--------------------------------------------------------------------------- if(nContinue) nContinue = !GetLocalInt(oInitiator, "CrusaderBreak"); - + //--------------------------------------------------------------------------- + // Forsakers can't use supernatural maneuvers + //--------------------------------------------------------------------------- + if (nContinue && GetIsManeuverSupernatural(nMoveId) && GetLevelByClass(CLASS_TYPE_FORSAKER, oInitiator)) + { + FloatingTextStringOnCreature("Forsakers cannot use supernatural maneuvers!", oInitiator, FALSE); + nContinue = FALSE; + } //--------------------------------------------------------------------------- // Run NullPsionicsField Check //--------------------------------------------------------------------------- diff --git a/src/include/true_inc_truespk.nss b/src/include/true_inc_truespk.nss index 7523024..4787683 100644 --- a/src/include/true_inc_truespk.nss +++ b/src/include/true_inc_truespk.nss @@ -109,11 +109,14 @@ int GetIsSyllable(int nSpellId); */ int DoSpellTruenameCheck(object oTrueSpeaker, object oTarget, int nPersonal = FALSE); +string GetNormalUtterSpellId(int nSpellId); + ////////////////////////////////////////////////// /* Includes */ ////////////////////////////////////////////////// #include "prc_inc_spells" +#include "true_inc_trufunc" ////////////////////////////////////////////////// /* Internal functions */ diff --git a/src/include/true_inc_trufunc.nss b/src/include/true_inc_trufunc.nss index 355783a..ad010b3 100644 --- a/src/include/true_inc_trufunc.nss +++ b/src/include/true_inc_trufunc.nss @@ -260,6 +260,7 @@ int GetCadenceCount(object oTrueSpeaker); #include "prc_alterations" #include "true_inc_utter" #include "true_inc_truknwn" +#include "true_inc_truespk" ////////////////////////////////////////////////// /* Function definitions */ diff --git a/src/include/true_inc_truknwn.nss b/src/include/true_inc_truknwn.nss index 22fbea0..9f9a606 100644 --- a/src/include/true_inc_truknwn.nss +++ b/src/include/true_inc_truknwn.nss @@ -141,6 +141,7 @@ int GetHasUtterance(int nUtter, object oCreature = OBJECT_SELF); #include "inc_pers_array" #include "prc_inc_nwscript" #include "inc_lookups" +#include "prc_x2_itemprop" ////////////////////////////////////////////////// /* Internal functions */ diff --git a/src/include/true_utterhook.nss b/src/include/true_utterhook.nss index 36b6b8c..dfb3671 100644 --- a/src/include/true_utterhook.nss +++ b/src/include/true_utterhook.nss @@ -18,6 +18,7 @@ #include "prc_inc_spells" #include "inc_utility" #include "prc_inc_itmrstr" +#include "true_inc_trufunc" // This function holds all functions that are supposed to run before the actual @@ -42,6 +43,16 @@ int TruePreUtterCastCode() int nContinue = !ExecuteScriptAndReturnInt("prespellcode",oTrueSpeaker); + //--------------------------------------------------------------------------- + // Block forsakers from using truenaming + //--------------------------------------------------------------------------- + + if(GetLevelByClass(CLASS_TYPE_FORSAKER, oTrueSpeaker) > 0) + { + SendMessageToPC(oTrueSpeaker, "Forsakers cannot use the power of truenaming."); + return FALSE; + } + //--------------------------------------------------------------------------- // Break any spell require maintaining concentration //--------------------------------------------------------------------------- diff --git a/src/include/x2_inc_spellhook.nss b/src/include/x2_inc_spellhook.nss index c631c0e..5449590 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 @@ -2804,7 +2902,7 @@ int WandEquipped(object oCaster, object oSpellCastItem) int nType = GetBaseItemType(oSpellCastItem); - if(nType == BASE_ITEM_MAGICWAND || nType == BASE_ITEM_ENCHANTED_WAND) // Has to be a wand, obv + if(nType == BASE_ITEM_MAGICWAND || nType == BASE_ITEM_ENCHANTED_WAND || nType == BASE_ITEM_CRAFTED_SCEPTER) // Has to be a wand, obv { if(GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oCaster) == oSpellCastItem || GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oCaster) == oSpellCastItem) // Needs to be equipped { @@ -2812,7 +2910,7 @@ int WandEquipped(object oCaster, object oSpellCastItem) } else { - FloatingTextStringOnCreature("You must equip a wand to cast from it.", oCaster, FALSE); + FloatingTextStringOnCreature("You must equip this item to cast from it.", oCaster, FALSE); return FALSE; // It's a wand not equipped } } @@ -3249,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) @@ -3399,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/include/x3_inc_horse.nss b/src/include/x3_inc_horse.nss index 65548a0..422e5c2 100644 --- a/src/include/x3_inc_horse.nss +++ b/src/include/x3_inc_horse.nss @@ -24,6 +24,7 @@ #include "x0_i0_position" #include "X0_INC_HENAI" #include "x3_inc_skin" +#include "prc_racial_const" /* @@ -638,7 +639,7 @@ int HorseGetMountTail(object oHorse); // FILE: x3_inc_horse FUNCTION: HorseGetMountFailureMessage() // This is a companion function to HorseGetCanBeMounted. If you need a text // message that explains why the horse cannot be mounted. -string HorseGetMountFailureMessage(object oTarget,object oRider=OBJECT_INVALID); +string HorseGetMountFailureMessage(object oHorse,object oRider=OBJECT_INVALID); // FILE: x3_inc_horse FUNCTION: HorseAddHorseMenu() @@ -1050,6 +1051,8 @@ void HORSE_SupportOriginalSpeed(object oRider) } // check to see if matches conditions eSearch=GetNextEffect(oRider); } // cycle through effects + + } // HORSE_SupportOriginalSpeed() diff --git a/src/module/ifo/module.ifo.json b/src/module/ifo/module.ifo.json index 198dbab..02316dc 100644 --- a/src/module/ifo/module.ifo.json +++ b/src/module/ifo/module.ifo.json @@ -813,7 +813,7 @@ }, "Mod_OnPlrGuiEvt": { "type": "resref", - "value": "" + "value": "prc_onplayergui" }, "Mod_OnPlrLvlUp": { "type": "resref",