From b27d9d2e5fdd6200adc536cdb50b74307fbb7d2b Mon Sep 17 00:00:00 2001 From: Jaysyn904 <68194417+Jaysyn904@users.noreply.github.com> Date: Wed, 27 May 2026 15:53:01 -0400 Subject: [PATCH] 2026/05/27 Update Verminlord requires Eldritch Blast as a prereq. Mettle branch in ApplyBreath() is fixed. Zen Archery now applies to Rays / Eldritch Blast. Runes will only support divine spells. School Specialization shouldn't block multiclass Red Wizards of Thay from divine spells in their opposition schools. Scepters, Runes, Skull Talismans & Attuned Gems are considered spell-completion items for RWoT School Specialization. Fixed Acolyte of the Skin's Damage Reduction. For real this time. Sneak Attack now takes Daring Outlaw / Swashbuckler into consideration. Updated Changelog. --- PRC8_ChangeLog.txt | 12 +++- nwn/nwnprc/trunk/2das/feat.2da | 2 +- nwn/nwnprc/trunk/include/prc_inc_breath.nss | 8 ++- nwn/nwnprc/trunk/include/prc_inc_combat.nss | 15 +++- nwn/nwnprc/trunk/include/prc_x2_craft.nss | 44 +++++------- nwn/nwnprc/trunk/include/x2_inc_spellhook.nss | 69 ++++++++++++++++++- nwn/nwnprc/trunk/scripts/prc_acolyte.nss | 16 ++--- nwn/nwnprc/trunk/scripts/prc_sneak_att.nss | 10 +++ 8 files changed, 133 insertions(+), 43 deletions(-) diff --git a/PRC8_ChangeLog.txt b/PRC8_ChangeLog.txt index 5c5592e9..cf24ac25 100644 --- a/PRC8_ChangeLog.txt +++ b/PRC8_ChangeLog.txt @@ -1,5 +1,15 @@ Commit: Jaysyn904 -Date: Sun May 25 +Date: Tues May 26 + +Added SoundImpact to Damning Darkness in vfx_persistent.2da. +Updated CheckPRCLimitations() to make Darkness, Deeper Darkness & Damning Darkness work better. +Fixed PnP Darkness & PnP Damning Darkness. +Fixed Oversized Two-Weapon Fighting. +Updated TLK for Attune Gem. +Fixed typo in Calm Emotions description. + +Commit: Jaysyn904 +Date: Mon May 25 Attune Gem was using the wrong prereqs. Attune Gem didn't handle radial spells properly. diff --git a/nwn/nwnprc/trunk/2das/feat.2da b/nwn/nwnprc/trunk/2das/feat.2da index 15f271f3..47781d72 100644 --- a/nwn/nwnprc/trunk/2das/feat.2da +++ b/nwn/nwnprc/trunk/2das/feat.2da @@ -5324,7 +5324,7 @@ 5320 EttercapBerserker 16826796 16826797 ife_wildshape **** **** **** **** **** **** **** **** 293 **** 0 0 1 **** **** **** **** 1 **** **** **** **** **** **** **** **** **** **** **** **** FEAT_ALERTNESS 5 **** **** **** **** **** 0 1 5321 IceTrollBerserker 16826798 16826799 ife_X2ArSkin **** **** **** **** **** 13 **** **** 293 **** 0 0 1 **** **** **** **** 1 **** **** **** **** **** **** **** **** **** **** **** **** FEAT_ALERTNESS 5 **** **** **** **** **** 0 1 5322 WolfBerserker 16826800 16826801 ife_animal **** **** **** **** **** **** **** **** 293 **** 0 0 1 **** **** **** **** 1 **** **** **** **** **** **** **** **** **** **** **** **** FEAT_ALERTNESS 5 **** **** **** **** **** 0 1 -5323 Verminlord 16836682 16836683 ife_X1PoisSav **** **** **** **** **** **** **** **** **** **** 0 0 0 **** **** 3795 **** 1 -1 **** **** **** **** **** **** **** 16 24 **** **** FEAT_MASTER_OF_THE_ELEMENTS 5 **** **** **** **** **** 1 0 +5323 Verminlord 16836682 16836683 ife_X1PoisSav **** **** **** **** **** **** **** **** 4460 **** 0 0 0 **** **** 3795 **** 1 -1 **** **** **** **** **** **** **** 16 24 **** **** FEAT_VERMINLORD 5 **** **** **** **** **** 1 0 5324 DespanaSchool 16836696 16836697 ife_X2CritDHalb 5 **** **** **** **** **** **** **** 28 94 0 0 1 22 **** 3798 **** 0.5 -1 **** **** **** **** **** **** **** **** **** **** **** FEAT_DESPANA_SCHOOL 6 1 **** **** **** **** 0 1 5325 Crinti_ShadowRide 16823084 16823085 spi_dimdoor **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** 3799 **** **** 5 **** **** **** **** **** **** **** **** **** **** **** FEAT_BLADE_MEDITATION **** **** **** **** **** **** **** 1 5326 Crinti_ShadowWalk 16823086 16823087 is_teleport **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** 3800 **** **** 1 **** 1 **** **** **** **** **** **** **** **** **** FEAT_BLADE_MEDITATION **** **** **** **** **** **** **** 1 diff --git a/nwn/nwnprc/trunk/include/prc_inc_breath.nss b/nwn/nwnprc/trunk/include/prc_inc_breath.nss index 70e1b34c..a7c41edd 100644 --- a/nwn/nwnprc/trunk/include/prc_inc_breath.nss +++ b/nwn/nwnprc/trunk/include/prc_inc_breath.nss @@ -795,9 +795,13 @@ void ApplyBreath(struct breath BreathUsed, location lTargetArea, int bLinger = F { //Mettle is Evasion for Fort saves if (GetHasMettle(oTarget, SAVING_THROW_FORT)) + { nAdjustedDamage = 0; - - nAdjustedDamage /= 2; + } + else + { + nAdjustedDamage /= 2; + } } } else diff --git a/nwn/nwnprc/trunk/include/prc_inc_combat.nss b/nwn/nwnprc/trunk/include/prc_inc_combat.nss index e36d5c9a..74b7e5b7 100644 --- a/nwn/nwnprc/trunk/include/prc_inc_combat.nss +++ b/nwn/nwnprc/trunk/include/prc_inc_combat.nss @@ -3759,9 +3759,20 @@ int GetAttackBonus(object oDefender, object oAttacker, object oWeap, int iOffhan && GetHasFeat(FEAT_INTUITIVE_ATTACK, oAttacker) ) iAbilityBonus = iWis; - //touch attacks always use dex, no matter what. Therefore override any calculations we have done so far + //touch attacks always use dex, no matter what. Therefore override any calculations we have done so far + //unless it's a ranged touch attack and the attacker has Zen Archery with higher WIS + if(iTouchAttackType) + { + if((iTouchAttackType == TOUCH_ATTACK_RANGED || iTouchAttackType == TOUCH_ATTACK_RANGED_SPELL) + && iWis > iDex && GetHasFeat(FEAT_ZEN_ARCHERY, oAttacker)) + iAbilityBonus = iWis; + else + iAbilityBonus = iDex; + } + +/* //touch attacks always use dex, no matter what. Therefore override any calculations we have done so far if(iTouchAttackType) - iAbilityBonus = iDex; + iAbilityBonus = iDex; */ // Expertise penalties apply to all attack rolls if (iCombatMode == COMBAT_MODE_EXPERTISE) iCombatModeBonus -= 5; diff --git a/nwn/nwnprc/trunk/include/prc_x2_craft.nss b/nwn/nwnprc/trunk/include/prc_x2_craft.nss index 887b65a0..bbd6156b 100644 --- a/nwn/nwnprc/trunk/include/prc_x2_craft.nss +++ b/nwn/nwnprc/trunk/include/prc_x2_craft.nss @@ -1726,6 +1726,24 @@ int InscribeRune(object oTarget = OBJECT_INVALID, object oCaster = OBJECT_INVALI if(!GetIsObjectValid(oTarget)) oTarget = PRCGetSpellTargetObject(); int nCaster = GetAlternativeCasterLevel(oCaster, PRCGetCasterLevel(oCaster)); + + // Get the spell and class + if(!nSpell) nSpell = PRCGetSpellId(); + int nLastClass = PRCGetLastSpellCastClass(); + + // Check if the casting class is divine + if (!GetIsDivineClass(nLastClass)) + { + FloatingTextStringOnCreature("Inscribe Rune can only be used with divine spells.", oCaster, FALSE); + return TRUE; + } + + // Check if the spell is on the caster's divine spell list + if (!GetHasSpellOnClassList(oCaster, nSpell)) + { + FloatingTextStringOnCreature("You must have this spell on your divine spell list to inscribe it.", oCaster, FALSE); + return TRUE; + } //:: Check for Inscribe Epic Runes and cap CL at 20 if it doesn't exist. int bEpicRunes = GetHasFeat(EPIC_FEAT_INSCRIBE_EPIC_RUNES, oCaster); @@ -1744,7 +1762,6 @@ int InscribeRune(object oTarget = OBJECT_INVALID, object oCaster = OBJECT_INVALI // Runecraft local int that counts uses/charges int nCount = GetLocalInt(oCaster, "RuneCounter"); - int nLastClass = PRCGetLastSpellCastClass(); if (nLastClass == CLASS_TYPE_CLERIC || nLastClass == CLASS_TYPE_UR_PRIEST) nSpellLevel = StringToInt(lookup_spell_cleric_level(nSpell)); else if (nLastClass == CLASS_TYPE_DRUID) nSpellLevel = StringToInt(lookup_spell_druid_level(nSpell)); else if (nLastClass == CLASS_TYPE_WIZARD || nLastClass == CLASS_TYPE_SORCERER) nSpellLevel = StringToInt(lookup_spell_level(nSpell)); @@ -3624,31 +3641,6 @@ int CICraftCheckCreateInfusion(object oSpellTarget, object oCaster, int nID = 0) 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; } diff --git a/nwn/nwnprc/trunk/include/x2_inc_spellhook.nss b/nwn/nwnprc/trunk/include/x2_inc_spellhook.nss index 5bac81dd..e68858f8 100644 --- a/nwn/nwnprc/trunk/include/x2_inc_spellhook.nss +++ b/nwn/nwnprc/trunk/include/x2_inc_spellhook.nss @@ -947,7 +947,72 @@ int SpellAlignmentRestrictions(object oCaster, int nSpellID, int nCastingClass) return TRUE; } -int RedWizRestrictedSchool(object oCaster, int nSchool, int nCastingClass, object oSpellCastItem) +int RedWizRestrictedSchool(object oCaster, int nSchool, int nCastingClass, object oSpellCastItem) +{ + // No need for wasting CPU on non-Red Wizards + if(GetLevelByClass(CLASS_TYPE_RED_WIZARD, oCaster)) + { + //can't cast prohibited spells from scrolls, wands, infused herbs, scepters, runes, gems, or skull talismans + if(GetIsObjectValid(oSpellCastItem)) + { + int nType = GetBaseItemType(oSpellCastItem); + string sTag = GetTag(oSpellCastItem); + + // Check base item types for standard items + if(nType != BASE_ITEM_MAGICWAND + && nType != BASE_ITEM_ENCHANTED_WAND + && nType != BASE_ITEM_SCROLL + && nType != BASE_ITEM_SPELLSCROLL + && nType != BASE_ITEM_ENCHANTED_SCROLL + && nType != BASE_ITEM_INFUSED_HERB + && nType != BASE_ITEM_CRAFTED_SCEPTER) // ADD THIS LINE + { + // Check tags for custom crafted items + if(sTag != "prc_rune_1" + && sTag != "prc_attunegem" + && sTag != "prc_skulltalis") + return TRUE; + } + } + else + { + // Direct casting: skip restriction for divine classes + if(GetIsDivineClass(nCastingClass)) + return TRUE; + } + + // Determine forbidden schools + int iRWRes; + switch(nSchool) + { + case SPELL_SCHOOL_ABJURATION: iRWRes = FEAT_RW_RES_ABJ; break; + case SPELL_SCHOOL_CONJURATION: iRWRes = FEAT_RW_RES_CON; break; + case SPELL_SCHOOL_DIVINATION: iRWRes = FEAT_RW_RES_DIV; break; + case SPELL_SCHOOL_ENCHANTMENT: iRWRes = FEAT_RW_RES_ENC; break; + case SPELL_SCHOOL_EVOCATION: iRWRes = FEAT_RW_RES_EVO; break; + case SPELL_SCHOOL_ILLUSION: iRWRes = FEAT_RW_RES_ILL; break; + case SPELL_SCHOOL_NECROMANCY: iRWRes = FEAT_RW_RES_NEC; break; + case SPELL_SCHOOL_TRANSMUTATION: iRWRes = FEAT_RW_RES_TRS; break; + } + + // Compare the spell's school versus the restricted schools + if(iRWRes && GetHasFeat(iRWRes, oCaster)) + { + FloatingTextStrRefOnCreature(16822359, oCaster, FALSE); // "You cannot cast spells of your prohibited schools. Spell terminated." + return FALSE; + } + // Other arcane casters cannot benefit from red wizard bonuses + if(GetIsArcaneClass(nCastingClass) && nCastingClass != CLASS_TYPE_WIZARD) + { + FloatingTextStringOnCreature("You have attempted to illegaly merge another arcane caster with a Red Wizard. All spellcasting will now fail.", oCaster, FALSE); + return FALSE; + } + } + + return TRUE; +} + +/* int RedWizRestrictedSchool(object oCaster, int nSchool, int nCastingClass, object oSpellCastItem) { // No need for wasting CPU on non-Red Wizards if(GetLevelByClass(CLASS_TYPE_RED_WIZARD, oCaster)) @@ -994,7 +1059,7 @@ int RedWizRestrictedSchool(object oCaster, int nSchool, int nCastingClass, objec return TRUE; } - + */ int PnPSpellSchools(object oCaster, int nCastingClass, int nSchool, object oSpellCastItem) { if(GetPRCSwitch(PRC_PNP_SPELL_SCHOOLS) diff --git a/nwn/nwnprc/trunk/scripts/prc_acolyte.nss b/nwn/nwnprc/trunk/scripts/prc_acolyte.nss index 6ebb0809..9bb0346e 100644 --- a/nwn/nwnprc/trunk/scripts/prc_acolyte.nss +++ b/nwn/nwnprc/trunk/scripts/prc_acolyte.nss @@ -10,6 +10,7 @@ //::////////////////////////////////////////////// #include "prc_feat_const" #include "inc_item_props" +#include "prc_x2_itemprop" // * Applies the Acolyte's AC bonuses as CompositeBonuses on object's skin. // * iLevel = integer AC Bonus @@ -22,15 +23,12 @@ void AcolyteFiendSkin(object oPC, object oSkin, int iLevel) // * Applies the Acolyte's damage reduction bonuses as CompositeBonuses on object's skin. // * iLevel = IP_CONST_DAMAGEREDUCTION_* -void AcolyteSymbiosis(object oPC, object oSkin, int iLevel) -{ - if(GetLocalInt(oSkin, "AcolyteSymbBonus") == iLevel) return; - - //RemoveSpecificProperty(oSkin, ITEM_PROPERTY_DAMAGE_REDUCTION, GetLocalInt(oSkin, "AcolyteSymbBonus"), IP_CONST_DAMAGESOAK_20_HP, 1, "AcolyteSymbBonus"); - RemoveSpecificProperty(oSkin, ITEM_PROPERTY_DAMAGE_REDUCTION, IP_CONST_DAMAGESOAK_20_HP, GetLocalInt(oSkin, "AcolyteSymbBonus"), 1, "AcolyteSymbBonus"); - //AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyDamageReduction(iLevel, IP_CONST_DAMAGESOAK_20_HP), oSkin); - AddItemProperty(DURATION_TYPE_PERMANENT, ItemPropertyDamageReduction(IP_CONST_DAMAGESOAK_20_HP, iLevel), oSkin); - SetLocalInt(oSkin, "AcolyteSymbBonus", iLevel); +void AcolyteSymbiosis(object oPC, object oSkin, int iLevel) +{ + itemproperty ipIP = ItemPropertyDamageReduction(iLevel, IP_CONST_DAMAGESOAK_20_HP); + IPSafeAddItemProperty(oSkin, ipIP, 0.0, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING, FALSE, FALSE); + + SetLocalInt(oSkin, "AcolyteSymbBonus", iLevel); } // * Applies the Acolyte's stat bonuses as CompositeBonuses on object's skin. diff --git a/nwn/nwnprc/trunk/scripts/prc_sneak_att.nss b/nwn/nwnprc/trunk/scripts/prc_sneak_att.nss index 9880ffd4..2bdac615 100644 --- a/nwn/nwnprc/trunk/scripts/prc_sneak_att.nss +++ b/nwn/nwnprc/trunk/scripts/prc_sneak_att.nss @@ -1,3 +1,6 @@ +// +// prc_sneak_att.nss +// // Written by WodahsEht. // Calculates the total sneak attack damage die given by all classes, // and applies the resulting bonuses to the skin. KNOWN ISSUE: @@ -114,6 +117,13 @@ void main() int iRogueSneakDice = GetRogueSneak(oPC); int iBlackguardSneakDice = GetBlackguardSneak(oPC); + // Daring Outlaw: Add half of Swashbuckler levels to Rogue sneak attack + if (GetHasFeat(FEAT_DARING_OUTLAW, oPC)) + { + int nSwashbucklerLevel = GetLevelByClass(CLASS_TYPE_SWASHBUCKLER, oPC); + iRogueSneakDice += (nSwashbucklerLevel / 2); + } + // Special case in case someone multiclasses Telflammar Shadowlord and Assassin -- These are the only // two classes that use Assassin Death Attack, and normally they would not stack. if((GetLevelByClass(CLASS_TYPE_SHADOWLORD) >= 6) && (GetLevelByClass(CLASS_TYPE_ASSASSIN)))