2025/12/06 Update

Added Inscribe Epic Runes.
Added Great Intelligence and Inscribe Epic Runes as Runecaster epic bons feats.
Changed Runecaster epic bonus feat progression to 1 every 4 levels past 10th.
Bardic PrCs should be able to take Lingering Song & Extra Music as general feats.
Forsakers can't use psionics, invocations, spellfire, shadowcasting, truenaming, binding, soulmelds or Supernatural Martial Maneuvers.
Fixed elven courtblade / ambidexterity bug.
Added more guardrails to prevent self-damage from onHit spells during PerformAttack().
Updated GetProperTarget()
Removed ableist slur.
RHD casters should work with JPM now.
Reworked Blood in the Water's effect icon.
Fixed Seize Item's VFX.
RHD casters should be able to enter Dragon Disciple.
Sharp Note doesn't step on Dragonfire Inspiration anymore.
This commit is contained in:
Jaysyn904
2025-12-06 22:55:23 -05:00
parent a4fd246266
commit ce7d550670
29 changed files with 641 additions and 228 deletions

View File

@@ -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 (){}

View File

@@ -444,6 +444,7 @@ int Get2DALineFromItemprop(string sFile, itemproperty ip, object oItem)
}
break;
}
case ITEM_PROPERTY_ECHOBLADE: return 46; break;
}
}
return -1;

View File

@@ -1319,6 +1319,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;

View File

@@ -426,7 +426,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);

View File

@@ -1322,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);
@@ -1339,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);

View File

@@ -1,11 +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
@@ -23,7 +80,7 @@ object GetProperTarget(object oPC, object oTarget)
}
return oTarget;
}
} */
void DoNaturalAttack(object oWeapon)
{
@@ -452,6 +509,16 @@ void DoNaturalWeaponHB(object oPC = OBJECT_SELF)
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);
}
DoDebug("prc_inc_nat_hb >> DoNaturalWeaponHB: scheduling a secondary natural attack with "+GetName(oWeapon)+" at delay "+FloatToString(fDelay));
//do the attack within a delay

View File

@@ -302,7 +302,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;
@@ -710,6 +710,7 @@ object CICraftScribeScroll(object oCreator, int nSpellID)
return oTarget;
}
*/
// -----------------------------------------------------------------------------
// Returns TRUE if the player used the last spell to brew a potion
// -----------------------------------------------------------------------------
@@ -865,7 +866,6 @@ These dont work as IPs since they are hardcoded */
}
// -----------------------------------------------------------------------------
// Returns TRUE if the player used the last spell to create a scroll
// -----------------------------------------------------------------------------
@@ -1133,6 +1133,9 @@ These dont work as IPs since they are hardcoded */
return FALSE;
}
// -----------------------------------------------------------------------------
// Returns TRUE if the player used the last spell to craft a staff
// -----------------------------------------------------------------------------
int CICraftCheckCraftStaff(object oSpellTarget, object oCaster, int nSpellID = 0)
{
@@ -1281,6 +1284,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)
{
@@ -1427,6 +1433,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;
@@ -1478,8 +1485,9 @@ int InscribeRune(object oTarget = OBJECT_INVALID, object oCaster = OBJECT_INVALI
if(!GetIsObjectValid(oTarget)) oTarget = PRCGetSpellTargetObject();
int nCaster = GetAlternativeCasterLevel(oCaster, PRCGetCasterLevel(oCaster));
//:: [TO DO] make Inscribe Epic Rune.
if(nCaster > 20) nCaster = 20;
//:: 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();

View File

@@ -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 (){}

View File

@@ -132,6 +132,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 +286,6 @@ int ShadPreMystCastCode()
if(DEBUG) DoDebug("ShadPreMystCastCode nContinue #6: " + IntToString(nContinue));
return nContinue;
}
}
//:: void main (){}

View File

@@ -78,7 +78,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
//---------------------------------------------------------------------------

View File

@@ -42,6 +42,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
//---------------------------------------------------------------------------