I don't see what the dilemma is. Just code your own. You can use a custom effect with either the effect id set to a custom number (if you're using NWNX) or an EffectCreator to distinguish it from other effects. Here's our TR function:
int GetTurnResistance (object oCreature) {
int nTR = GetLocalInt(oCreature, "TR"), nTRAdd = 0;
/* check for bolster effect */
if (GetHasSpellEffect(HGEFFECT_BOLSTER_UNDEAD, oCreature))
nTRAdd += 4;
/* check for Falxugon bardsong, adds 2 to TR */
effect eScan = GetFirstEffect(oCreature);
int nFalx = 0, nEffectID;
while (GetIsEffectValid(eScan)) {
nEffectID = GetEffectSpellId(eScan);
if (nEffectID >= 19000 && nEffectID <= 19099) {
if (GetLocalInt(GetEffectCreator(eScan), "ID") == 65) {
nFalx = 2;
break;
}
}
eScan = GetNextEffect(oCreature);
}
nTRAdd += nFalx;
/* return here if TR var was set */
if (nTR > 0)
return nTR + nTRAdd;
if (GetIsPC(oCreature))
nTR = GetHitDiceIncludingLLs(oCreature);
else {
float fCR = GetChallengeRating(oCreature);
if (fCR < 10.0)
nTR = FloatToInt(fCR);
else
nTR = 1 + FloatToInt(fCR / (log(fCR) / log(10.0)));
}
return nTR + nTRAdd;
}
Obviously, that last bit is going to depend on what your CRs look like, but this should give you an idea. You will of course have to customize your turning scripts, but it sounds like you're already doing that. Here's ours, in case it's of help:
#include "hg_inc"
#include "ac_data_inc"
#include "ac_spell_inc"
const int DAMAGE_BEHAVIOR_TURNED = 0x00000020;
const float RADIUS_SIZE_TURNING = 15.0;
void main() {
object oTurner = OBJECT_SELF;
if (GetAbilityScore(oTurner, ABILITY_CHARISMA, TRUE) < 10) {
FloatingTextStringOnCreature("You must have 10 base Charisma to turn undead!", oTurner, FALSE);
return;
}
int i, nclass, nLL, nBonus = 0, nUndead = GetLevelByclass(class_TYPE_CLERIC, oTurner);
int bPlanar = GetHasFeat(FEAT_EPIC_PLANAR_TURNING, oTurner);
/* check for creatures that block turning */
i = 1;
object oTurnBlocker = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY);
while (GetIsObjectValid(oTurnBlocker)) {
if (GetLocalInt(oTurnBlocker, "TurnBlock")) {
FloatingTextStringOnCreature("The " + GetName(oTurnBlocker) + " is blocking your power to turn!", oTurner, FALSE);
return;
}
oTurnBlocker = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, oTurner, ++i);
}
if ((nclass = GetLevelByclass(class_TYPE_PALADIN, oTurner)) > 2 &&
GetAlignmentGoodEvil(oTurner) == ALIGNMENT_GOOD) {
if (bPlanar &&
!GetPCOption(oTurner, PCOPTION_NOTURNSMITE) &&
GetHasFeat(FEAT_SMITE_EVIL, oTurner)) {
DecrementRemainingFeatUses(oTurner, FEAT_SMITE_EVIL);
} else
nclass -= 2;
nUndead += nclass;
}
if ((nclass = GetLevelByclass(class_TYPE_BLACKGUARD, oTurner)) > 2 &&
GetAlignmentGoodEvil(oTurner) == ALIGNMENT_EVIL) {
if (!GetQuasiclass(oTurner))
nclass += GetLevelByclass(class_TYPE_PALADIN, oTurner);
if (bPlanar &&
!GetPCOption(oTurner, PCOPTION_NOTURNSMITE) &&
GetHasFeat(FEAT_SMITE_GOOD, oTurner)) {
DecrementRemainingFeatUses(oTurner, FEAT_SMITE_GOOD);
} else
nclass -= 2;
nUndead += nclass;
}
if ((nLL = GetHitDiceIncludingLLs(oTurner) - 40) > 0) {
switch (GetLLControlclass(oTurner)) {
case class_TYPE_CLERIC:
case class_TYPE_PALADIN:
case class_TYPE_BLACKGUARD:
nUndead += nLL;
break;
default: nLL = 0; break;
}
} else
nLL = 0;
if (nUndead < 1) {
FloatingTextStringOnCreature("You have no turning powers! You must be good to use Paladin turning powers, and evil to use Blackguard turning powers.",
oTurner, FALSE);
return;
}
if (nUndead > 60) {
nBonus = nUndead - 60;
nUndead = 60;
}
/* check for the Extra Turning item feat */
int nExtra = GetLocalInt(oTurner, "ExtraTurning");
if (nExtra > 0) {
if (GetHasFeat(HGFEAT_Y_EXTRA_TURNING_ITEM, oTurner)) {
if (--nExtra <= 0) {
IncrementRemainingFeatUses(oTurner, FEAT_TURN_UNDEAD);
IncrementRemainingFeatUses(oTurner, FEAT_TURN_UNDEAD);
IncrementRemainingFeatUses(oTurner, FEAT_TURN_UNDEAD);
IncrementRemainingFeatUses(oTurner, FEAT_TURN_UNDEAD);
}
} else
nExtra = 0;
SetLocalInt(oTurner, "ExtraTurning", nExtra);
}
/* Total TR turned is equal to (TL + Cha modifer) * 3; and destruction DC is
* equal to (TL / 2) + Cha modifier. */
int nTotal = (nUndead + GetAbilityModifier(ABILITY_CHARISMA, oTurner)) * 3;
int nDC = 10 + (nUndead / 4) + GetAbilityModifier(ABILITY_CHARISMA, oTurner);
nDC -= GetSpellAreaDCPenalty(oTurner);
/* handle bonus turning capacity items */
for (i = 0; i < NUM_INVENTORY_SLOTS; i++) {
if (GetLocalInt(GetItemInSlot(i, oTurner), "ImproveTurnerCapacity")) {
nTotal += (nTotal / 3);
break;
}
}
/* ensure turning won't break in the following round */
int nUptime = GetLocalInt(GetModule(), "uptime") + 7;
float fDur = RoundsToSeconds(10);
/* TP vs Elementals receives +1 per 4 LLs for Planar Turning */
int nElemental = GetHasFeat(FEAT_AIR_DOMAIN_POWER, oTurner) +
GetHasFeat(FEAT_EARTH_DOMAIN_POWER, oTurner) +
GetHasFeat(FEAT_FIRE_DOMAIN_POWER, oTurner) +
GetHasFeat(FEAT_WATER_DOMAIN_POWER, oTurner);
if (nElemental)
nElemental = nUndead + (bPlanar ? nLL / 4 : 0);
/* TP vs Vermin receives +1 per 4 LLs for Planar Turning */
int nVermin = GetHasFeat(FEAT_PLANT_DOMAIN_POWER, oTurner);
if (nVermin)
nVermin = nUndead + (bPlanar ? nLL / 4 : 0);
/* TP vs Outsiders receives +1 per LL for Planar Turning */
int nOutsider = GetHasFeat(FEAT_GOOD_DOMAIN_POWER, oTurner) +
GetHasFeat(FEAT_EVIL_DOMAIN_POWER, oTurner);
if (nOutsider)
nOutsider = nUndead + (bPlanar ? nLL : 0);
/* TP vs Constructs is equal to TL */
int nConstruct = GetHasFeat(FEAT_DESTRUCTION_DOMAIN_POWER, oTurner);
if (nConstruct)
nConstruct = nUndead + GetSpellPenetration(oTurner);
/* TP vs Undead receives +1 per 2 LLs for Sun domain */
if (GetHasFeat(FEAT_SUN_DOMAIN_POWER))
nUndead += nLL / 2;
/* Vestments of the Gardenborn */
int bPlant = (GetResRef(GetItemInSlot(INVENTORY_SLOT_CHEST, oTurner)) == "elysetarm002");
effect eLink = EffectTurned();
if (!GetPCOption(oTurner, PCOPTION_NOTURNIMMOB))
eLink = EffectLinkEffects(eLink, EffectCutsceneImmobilize());
eLink = EffectLinkEffects(eLink, EffectVisualEffect(VFX_DUR_MIND_AFFECTING_FEAR));
eLink = EffectLinkEffects(eLink, EffectVisualEffect(VFX_DUR_CESSATE_NEGATIVE));
effect eDamage;
effect eDeath = SupernaturalEffect(EffectDeath(TRUE));
effect eVisTurn = EffectVisualEffect(VFX_IMP_SUNSTRIKE);
effect eVisDeath = EffectVisualEffect(HGVFX_IMP_TURN_DESTRUCTION_GOOD);
location lTarget = GetLocation(oTurner);
ApplyVisualAtLocation(HGVFX_FNF_LOS_TURNING_GOOD, lTarget);
int nTurned = 0;
struct List l = ListCreate(oTurner, "Turn");
object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_TURNING, lTarget, TRUE);
while (GetIsObjectValid(oTarget)) {
if (GetIsTarget(oTarget)) {
int nPower = 0, nTR = GetTurnResistance(oTarget) + 100;
int nFeat = GetLocalInt(oTarget, "TurnFeat");
int nRacial = GetLocalInt(oTarget, "TurnAs");
if (GetHasSpellImmunity(SPELL_PROTECTION_FROM_ALIGNMENT, oTarget))
nRacial = -1;
if (nFeat && !GetHasFeat(nFeat, oTurner))
nRacial = -1;
if (nRacial == 0 && GetLevelByclass(class_TYPE_MONK, oTarget) >= 20)
nRacial = RACIAL_TYPE_OUTSIDER;
if (nRacial == 0)
nRacial = GetRacialType(oTarget);
if (nRacial == RACIAL_TYPE_PLANT && bPlant)
nRacial = RACIAL_TYPE_VERMIN;
switch (nRacial) {
case RACIAL_TYPE_CONSTRUCT: nPower = nConstruct; break;
case RACIAL_TYPE_ELEMENTAL: nPower = nElemental; break;
case RACIAL_TYPE_UNDEAD: nPower = nUndead; break;
case RACIAL_TYPE_VERMIN: nPower = nVermin; break;
case RACIAL_TYPE_OUTSIDER:
if (GetIsPC(oTurner) &&
!GetIsPC(oTarget) &&
GetAlignmentGoodEvil(oTarget) == ALIGNMENT_GOOD &&
GetLevelByclass(class_TYPE_BLACKGUARD, oTurner) < 1)
nRacial = RACIAL_TYPE_INVALID;
else
nPower = nOutsider;
break;
}
/* TR is stored as an offset to TP, so we can sort by difficulty of
* turning. To ensure we never get a negative number, 100 is added
* to TR above (TP can never be higher than 80). */
if (nPower > 0 && nTR <= 200) {
int nRank = FloatToInt(GetDistanceBetween(oTarget, oTurner) * 10.0);
nRank = ((nTR - nPower) * 1000000) + ((nRank % 1000) * 1000) + (nPower % 1000);
for (ListSeek(l, LIST_SEEK_FIRST); ListGetValid(l); ListSeek(l, LIST_SEEK_NEXT)) {
if (ListGetInt(l) > nRank)
break;
}
if (!ListGetValid(l)) {
ListSeek(l, LIST_SEEK_LAST);
ListSetInt(l, nRank, LIST_SET_APPEND);
} else
ListSetInt(l, nRank, LIST_SET_INSERT);
ListSetObject(l, oTarget);
if (GetHasEffectOfType(EFFECT_TYPE_TURNED, oTarget, oTurner))
nTurned++;
}
}
oTarget = GetNextObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_TURNING, lTarget, TRUE);
}
for (ListSeek(l, LIST_SEEK_FIRST); nTotal > 0 && ListGetValid(l); ListSeek(l, LIST_SEEK_NEXT)) {
int nRank = ListGetInt(l);
int nPower = nRank % 1000;
int nTR = ((nRank / 1000000) + nPower) - 100;
if (nTR > nTotal)
break;
oTarget = ListGetObject(l);
int nLimit, nRacial = GetLocalInt(oTarget, "TurnAs");
if (nRacial == 0)
nRacial = GetRacialType(oTarget);
switch (nRacial) {
case RACIAL_TYPE_OUTSIDER: nLimit = 78; break;
case RACIAL_TYPE_UNDEAD: nLimit = 68; break;
default: nLimit = 63; break;
}
/* For creatures that cannot possibly be destroyed, limit the number
* turned at any one time to half the amount by which they exceed the
* destruction limit. For example, for outsiders:
*
* TR 79-80 = limit 4 (Amnizu, Cornugons)
* TR 81-82 = limit 3 (Absklarg, Fodorz)
* TR 83-84 = limit 2 (Pit Fiends, Malebranches)
* TR 85-88 = limit 1 (Ichors, Brood Serpents)
*/
int nTargetLimit = GetLocalInt(oTarget, "TurnLimit");
if (nTargetLimit > 0 || nTR > nLimit && nTurned > 0 && nTurned >= ((nLimit + 10) - nTR) / 2) {
if (nTargetLimit == 0) {
if ((nTargetLimit = ((nLimit + 10) - nTR) / 2) < 1)
nTargetLimit = 1;
}
SendMessageToPC(oTurner, C_MED_YELLOW + GetName(oTarget) + " : Turn limit of " +
IntToString(nTargetLimit) + " exceeded");
break;
}
/* calculate DC bonus, distance, and delay (standard spell effect delay) */
int nDCBonus = 0;
float fDist = GetDistanceBetweenLocations(lTarget, GetLocation(oTarget));
float fDelay = fDist / 20;
if (fDist <= RADIUS_SIZE_MEDIUM)
nDCBonus = 2;
else if (fDist <= RADIUS_SIZE_HUGE)
nDCBonus = 1;
SignalEvent(oTarget, EventSpellCastAt(oTurner, SPELLABILITY_TURN_UNDEAD));
if (nRacial == RACIAL_TYPE_CONSTRUCT) {
int nRoll = d20() + nBonus;
int nSR = GetSpellResistance(oTarget) + nTurned++;
if (nRoll > 20)
nRoll = 20;
SendSpellResistanceMessage(oTurner, oTarget, nRoll, nPower, nSR, fDelay);
if (nPower + nRoll >= nSR) {
if (bPlanar && GetSaveCheckResult(SAVING_THROW_WILL, oTarget, nDC + nDCBonus, SAVING_THROW_TYPE_TURNING) <= 0) {
DelayCommand(fDelay, ApplyEffectToObject(DURATION_TYPE_INSTANT, eVisDeath, oTarget));
DelayCommand(fDelay + 0.1, ApplyEffectToObject(DURATION_TYPE_INSTANT, eDeath, oTarget));
} else {
effect eDamage = EffectDamage(d3(nPower), DAMAGE_TYPE_MAGICAL);
DelayCommand(fDelay, ApplyEffectToObject(DURATION_TYPE_INSTANT, eVisTurn, oTarget));
DelayCommand(fDelay + 0.1, ApplyEffectToObject(DURATION_TYPE_INSTANT, eDamage, oTarget));
}
}
nTotal -= nTR;
} else {
int nRoll = d8() + nBonus;
if (nRoll >
nRoll = 8;
SendTurnResistanceMessage(oTurner, oTarget, nRoll, nPower, nTR, fDelay);
if (nPower + nRoll >= nTR) {
int bTurned = GetHasEffectOfType(EFFECT_TYPE_TURNED, oTarget);
if (bTurned && (nPower + nRoll) >= (nTR + 10) &&
GetSaveCheckResult(SAVING_THROW_WILL, oTarget, nDC + nDCBonus, SAVING_THROW_TYPE_TURNING) <= 0) {
DelayCommand(fDelay, ApplyEffectToObject(DURATION_TYPE_INSTANT, eVisDeath, oTarget));
DelayCommand(fDelay + 0.1, ApplyEffectToObject(DURATION_TYPE_INSTANT, eDeath, oTarget));
nTurned++;
} else if (!bTurned) {
int nBehavior = GetLocalInt(oTarget, "DamageBehavior") | DAMAGE_BEHAVIOR_TURNED;
SetLocalInt(oTarget, "DamageBehavior", nBehavior);
SetLocalObject(oTarget, "Turner", oTurner);
SetLocalInt(oTarget, "TurnerDC", nDC);
/* if the target could have been destroyed, guarantee they stay turned
* for at least 2 rounds instead of 1 */
if ((nPower + nRoll) >= (nTR + 10))
SetLocalInt(oTarget, "TurnBreakAttempt", nUptime + 6);
else
SetLocalInt(oTarget, "TurnBreakAttempt", nUptime);
DelayCommand(fDelay, ApplyEffectToObject(DURATION_TYPE_INSTANT, eVisTurn, oTarget));
DelayCommand(fDelay + 0.1, ApplyTurnEffect(DURATION_TYPE_TEMPORARY, eLink, oTarget, fDur));
nTurned++;
}
}
nTotal -= nTR;
}
}
ListDestroy(l);
}
FWIW, we've been considering tweaking the roll to get more granularity. The real pain is getting the numbers to fit your current creatures across the board, when retrofitting. LMK if you want a paste of any of the custom functions sabove.
Funky