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.
This commit is contained in:
Jaysyn904
2026-05-27 15:53:01 -04:00
parent da1ff48ac3
commit b27d9d2e5f
8 changed files with 133 additions and 43 deletions

View File

@@ -1,5 +1,15 @@
Commit: Jaysyn904 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 was using the wrong prereqs.
Attune Gem didn't handle radial spells properly. Attune Gem didn't handle radial spells properly.

View File

@@ -5324,7 +5324,7 @@
5320 EttercapBerserker 16826796 16826797 ife_wildshape **** **** **** **** **** **** **** **** 293 **** 0 0 1 **** **** **** **** 1 **** **** **** **** **** **** **** **** **** **** **** **** FEAT_ALERTNESS 5 **** **** **** **** **** 0 1 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 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 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 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 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 5326 Crinti_ShadowWalk 16823086 16823087 is_teleport **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** 3800 **** **** 1 **** 1 **** **** **** **** **** **** **** **** **** FEAT_BLADE_MEDITATION **** **** **** **** **** **** **** 1

View File

@@ -795,10 +795,14 @@ void ApplyBreath(struct breath BreathUsed, location lTargetArea, int bLinger = F
{ {
//Mettle is Evasion for Fort saves //Mettle is Evasion for Fort saves
if (GetHasMettle(oTarget, SAVING_THROW_FORT)) if (GetHasMettle(oTarget, SAVING_THROW_FORT))
{
nAdjustedDamage = 0; nAdjustedDamage = 0;
}
else
{
nAdjustedDamage /= 2; nAdjustedDamage /= 2;
} }
}
} }
else else
nAdjustedDamage = PRCGetReflexAdjustedDamage(nDamage, oTarget, nSaveDC, nSaveDamageType); nAdjustedDamage = PRCGetReflexAdjustedDamage(nDamage, oTarget, nSaveDC, nSaveDamageType);

View File

@@ -3760,8 +3760,19 @@ int GetAttackBonus(object oDefender, object oAttacker, object oWeap, int iOffhan
iAbilityBonus = iWis; 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)
{
if((iTouchAttackType == TOUCH_ATTACK_RANGED || iTouchAttackType == TOUCH_ATTACK_RANGED_SPELL)
&& iWis > iDex && GetHasFeat(FEAT_ZEN_ARCHERY, oAttacker))
iAbilityBonus = iWis;
else
iAbilityBonus = iDex; iAbilityBonus = iDex;
}
/* //touch attacks always use dex, no matter what. Therefore override any calculations we have done so far
if(iTouchAttackType)
iAbilityBonus = iDex; */
// Expertise penalties apply to all attack rolls // Expertise penalties apply to all attack rolls
if (iCombatMode == COMBAT_MODE_EXPERTISE) iCombatModeBonus -= 5; if (iCombatMode == COMBAT_MODE_EXPERTISE) iCombatModeBonus -= 5;

View File

@@ -1727,6 +1727,24 @@ int InscribeRune(object oTarget = OBJECT_INVALID, object oCaster = OBJECT_INVALI
if(!GetIsObjectValid(oTarget)) oTarget = PRCGetSpellTargetObject(); if(!GetIsObjectValid(oTarget)) oTarget = PRCGetSpellTargetObject();
int nCaster = GetAlternativeCasterLevel(oCaster, PRCGetCasterLevel(oCaster)); 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. //:: Check for Inscribe Epic Runes and cap CL at 20 if it doesn't exist.
int bEpicRunes = GetHasFeat(EPIC_FEAT_INSCRIBE_EPIC_RUNES, oCaster); int bEpicRunes = GetHasFeat(EPIC_FEAT_INSCRIBE_EPIC_RUNES, oCaster);
if (!bEpicRunes) { if(nCaster > 20) nCaster = 20; } if (!bEpicRunes) { if(nCaster > 20) nCaster = 20; }
@@ -1744,7 +1762,6 @@ int InscribeRune(object oTarget = OBJECT_INVALID, object oCaster = OBJECT_INVALI
// Runecraft local int that counts uses/charges // Runecraft local int that counts uses/charges
int nCount = GetLocalInt(oCaster, "RuneCounter"); 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)); 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_DRUID) nSpellLevel = StringToInt(lookup_spell_druid_level(nSpell));
else if (nLastClass == CLASS_TYPE_WIZARD || nLastClass == CLASS_TYPE_SORCERER) nSpellLevel = StringToInt(lookup_spell_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; 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; return FALSE;
} }

View File

@@ -948,6 +948,71 @@ int SpellAlignmentRestrictions(object oCaster, int nSpellID, int nCastingClass)
} }
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 // No need for wasting CPU on non-Red Wizards
if(GetLevelByClass(CLASS_TYPE_RED_WIZARD, oCaster)) if(GetLevelByClass(CLASS_TYPE_RED_WIZARD, oCaster))
@@ -994,7 +1059,7 @@ int RedWizRestrictedSchool(object oCaster, int nSchool, int nCastingClass, objec
return TRUE; return TRUE;
} }
*/
int PnPSpellSchools(object oCaster, int nCastingClass, int nSchool, object oSpellCastItem) int PnPSpellSchools(object oCaster, int nCastingClass, int nSchool, object oSpellCastItem)
{ {
if(GetPRCSwitch(PRC_PNP_SPELL_SCHOOLS) if(GetPRCSwitch(PRC_PNP_SPELL_SCHOOLS)

View File

@@ -10,6 +10,7 @@
//::////////////////////////////////////////////// //:://////////////////////////////////////////////
#include "prc_feat_const" #include "prc_feat_const"
#include "inc_item_props" #include "inc_item_props"
#include "prc_x2_itemprop"
// * Applies the Acolyte's AC bonuses as CompositeBonuses on object's skin. // * Applies the Acolyte's AC bonuses as CompositeBonuses on object's skin.
// * iLevel = integer AC Bonus // * iLevel = integer AC Bonus
@@ -24,12 +25,9 @@ void AcolyteFiendSkin(object oPC, object oSkin, int iLevel)
// * iLevel = IP_CONST_DAMAGEREDUCTION_* // * iLevel = IP_CONST_DAMAGEREDUCTION_*
void AcolyteSymbiosis(object oPC, object oSkin, int iLevel) void AcolyteSymbiosis(object oPC, object oSkin, int iLevel)
{ {
if(GetLocalInt(oSkin, "AcolyteSymbBonus") == iLevel) return; itemproperty ipIP = ItemPropertyDamageReduction(iLevel, IP_CONST_DAMAGESOAK_20_HP);
IPSafeAddItemProperty(oSkin, ipIP, 0.0, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING, FALSE, FALSE);
//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); SetLocalInt(oSkin, "AcolyteSymbBonus", iLevel);
} }

View File

@@ -1,3 +1,6 @@
//
// prc_sneak_att.nss
//
// Written by WodahsEht. // Written by WodahsEht.
// Calculates the total sneak attack damage die given by all classes, // Calculates the total sneak attack damage die given by all classes,
// and applies the resulting bonuses to the skin. KNOWN ISSUE: // and applies the resulting bonuses to the skin. KNOWN ISSUE:
@@ -114,6 +117,13 @@ void main()
int iRogueSneakDice = GetRogueSneak(oPC); int iRogueSneakDice = GetRogueSneak(oPC);
int iBlackguardSneakDice = GetBlackguardSneak(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 // 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. // two classes that use Assassin Death Attack, and normally they would not stack.
if((GetLevelByClass(CLASS_TYPE_SHADOWLORD) >= 6) && (GetLevelByClass(CLASS_TYPE_ASSASSIN))) if((GetLevelByClass(CLASS_TYPE_SHADOWLORD) >= 6) && (GetLevelByClass(CLASS_TYPE_ASSASSIN)))