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
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.

View File

@@ -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

View File

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

View File

@@ -3760,8 +3760,19 @@ int GetAttackBonus(object oDefender, object oAttacker, object oWeap, int iOffhan
iAbilityBonus = iWis;
//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; */
// Expertise penalties apply to all attack rolls
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();
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);
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
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;
}

View File

@@ -948,6 +948,71 @@ int SpellAlignmentRestrictions(object oCaster, int nSpellID, int nCastingClass)
}
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)

View File

@@ -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
@@ -24,12 +25,9 @@ void AcolyteFiendSkin(object oPC, object oSkin, int iLevel)
// * iLevel = IP_CONST_DAMAGEREDUCTION_*
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);
}

View File

@@ -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)))