Working on some initial revisions to what you have, mainly on the actual adjustments to breath weapons.
Focusing on checks for the sanctuary effect. This is something which is useful in other areas of the AI as well. Also integrated the dragon age code you've got, with a var which can be used if a character polymorphs into a dragon to set the correct age to caster level.
I don't like a loop deep in the AI, it can get mess. Get effect type of sanctuary is often going to result in looping thru ALL the effects on every target, if you are dealing with a lot of buffed opponents this can get a bit much. While it looks like a single function call, GetHasEffectType(oTarget, EFFECT_TYPE_SANCTUARY) is actually calling a loop defined in ginc_effect.nss and not an engine function. My assumption here is that ethereal / sanctuary is only going to be used rarely by players, and quite often there will be quite a few effects on creatures which might result in occasional timeouts. It does add some overhead from a delay command, but there should only ever be a few of those running.
I am adding a tracking variable whenever effectethereal or effectsanctuary is applied via script using a variable named "CSL_ETHEREAL" or CSL_SANCTUARY". I am running a delay of the duration to remove this after the duration ends. Because the player can rest, or removed by dispel magic, or otherwise not have the effect when this variable is set, i am only using this variable to prove that there is no such effect on the target. If this variable is set i am going to loop thru the effects to make sure it's actually applied.
Now this is a bit of work, but then usually it's from the spell sanctuary, ethereal, or ethereal jaunt and in those cases it will check for the spellid.
The logic is such that most of the time it will just check for the spell itself, and then check for the variable, and only be about 3 or so steps, but with some safety. If the tracking var is not set the spellids should make it usually work, so it should work correctly if a custom spells.2da is not used.
[code=auto:0]
// in the _cslcore_math include
void CSLDecrementLocalInt_Void(object oObject, string sField, int iVal = 1, int bZeroLimit = FALSE)
{
int nNew = GetLocalInt(oObject, sField) - iVal;
if ( bZeroLimit && nNew < 0 )
{
nNew = 0;
}
SetLocalInt(oObject, sField, nNew);
}
int CSLIncrementLocalInt_Timed(object oObject, string sFld, float fDelay, int iVal = 1)
{
int nNew = GetLocalInt(oObject, sFld) + iVal;
SetLocalInt(oObject, sFld, nNew);
DelayCommand(fDelay, CSLDecrementLocalInt_Void(oObject, sFld, iVal, TRUE ));
return nNew;
}
// going into my _cslcore_info include
int CSLGetHasEtherealEffect( object oTarget )
{
if ( GetHasSpellEffect(SPELL_ETHEREAL_JAUNT, oTarget) )
{
return TRUE;
}
if ( GetHasSpellEffect(SPELL_ETHEREALNESS, oTarget) )
{
return TRUE;
}
if( GetLocalInt(oTarget, "CSL_ETHEREAL") )
{
effect eEffect = GetFirstEffect( oTarget );
while (GetIsEffectValid(eEffect))
{
if ( GetEffectType(eEffect) == EFFECT_TYPE_SANCTUARY )
{
return TRUE;
}
eEffect = GetNextEffect( oTarget );
}
}
return FALSE;
}
int CSLGetHasSanctuaryEffect( object oTarget )
{
if ( GetHasSpellEffect(SPELL_SANCTUARY, oTarget) )
{
return TRUE;
}
if( GetLocalInt(oTarget, "CSL_SANCTUARY") )
{
effect eEffect = GetFirstEffect( oTarget );
while (GetIsEffectValid(eEffect))
{
if ( GetEffectType(eEffect) == EFFECT_TYPE_ETHEREAL )
{
return TRUE;
}
eEffect = GetNextEffect( oTarget );
}
}
return FALSE;
}
// general purpose damage reduction function i use for adjusting damage to the target based on element type
// inside of _HKSpell
int HkApplyTargetModifiers( int nDamageTotal, int iDamageType, object oTarget, int iHitEffect = VFX_IMP_HEALING_M, float fDelay = 0.0f, object oCaster = OBJECT_SELF )
{
if (
( iDamageType == DAMAGE_TYPE_COLD ) && GetHasFeat(FEAT_ENERGY_ABSORB_COLD, oTarget) ||
( iDamageType == DAMAGE_TYPE_FIRE ) && GetHasFeat(FEAT_ENERGY_ABSORB_FIRE, oTarget) ||
( iDamageType == DAMAGE_TYPE_ACID ) && GetHasFeat(FEAT_ENERGY_ABSORB_ACID, oTarget) ||
( iDamageType == DAMAGE_TYPE_ACID ) && GetHasFeat(FEAT_ENERGY_ABSORB_ACID, oTarget) ||
( iDamageType == DAMAGE_TYPE_SONIC ) && GetHasFeat(FEAT_ENERGY_ABSORB_SONIC, oTarget) ||
( iDamageType == DAMAGE_TYPE_ELECTRICAL ) && GetHasFeat(FEAT_ENERGY_ABSORB_ELECTRICAL, oTarget) ||
( iDamageType == DAMAGE_TYPE_NEGATIVE ) && GetHasFeat(FEAT_ENERGY_ABSORB_NEGATIVE, oTarget) ||
( iDamageType == DAMAGE_TYPE_POSITIVE ) && GetHasFeat(FEAT_ENERGY_ABSORB_POSITIVE, oTarget) ||
( iDamageType == DAMAGE_TYPE_MAGICAL ) && GetHasFeat(FEAT_ENERGY_ABSORB_MAGIC, oTarget) ||
( iDamageType == DAMAGE_TYPE_DIVINE ) && GetHasFeat(FEAT_ENERGY_ABSORB_DIVINE, oTarget)
)
{
effect eHeal = EffectHeal( CSLGetMax(nDamageTotal/2, 1) );
CSLRemoveEffectByType(oTarget, EFFECT_TYPE_WOUNDING);
//Apply heal effect and VFX impact
//HkApplyEffectToObject(DURATION_TYPE_INSTANT, eHeal, oTarget);
//HkApplyEffectToObject(DURATION_TYPE_INSTANT, eVis2, oTarget);
DelayCommand(fDelay, HkApplyEffectToObject(DURATION_TYPE_INSTANT, eHeal, oTarget));
DelayCommand(fDelay, HkApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect( iHitEffect ), oTarget));
return 0;
}
// half damage if Target has Effect_Type_Sanctuary
if ( CSLGetHasEtherealEffect( oTarget ) )
{
return 0;
}
if ( CSLGetHasSanctuaryEffect(oTarget) )
{
nDamageTotal = nDamageTotal / 2;
}
return nDamageTotal;
}
// function to get the dragons age
// _SCInclude_Monster
int SCDragonGetAge( object oDragon = OBJECT_SELF ) // what if it's a druid or wizard shapechanger
{
int iHitDice = GetHitDice(oDragon); // so we can figure out non dragon levels
int iDragonLevel = GetLevelByclass(class_TYPE_DRAGON, oDragon);
if ( iHitDice > iDragonLevel ) // this is not a pure dragon, lets figure out what it is
{
// lets look for a variable setting the dragons hit dice
// this supports toolset overrides and also scripting changes
// ( for when a druid shifts into dragon form set this on polymorphing to the wildshape or caster level as appropriate)
int iTempDragonLevel = GetLocalInt( oDragon, "CSL_DRAGON_HD");
if ( iTempDragonLevel > 0 )
{
iDragonLevel = iTempDragonLevel;
}
else
{
iDragonLevel += GetLevelByclass(class_TYPE_SORCERER, oDragon); // full levels since draconic heritage is a prerequisite
iDragonLevel += GetLevelByclass(class_DRAGON_SHAMAN, oDragon);
iDragonLevel += GetLevelByclass(class_TYPE_DRAGON_DISCIPLE, oDragon);
iDragonLevel += GetLevelByclass(class_DRAGON_WARRIOR, oDragon);
iDragonLevel += (iHitDice-iDragonLevel)/2; // half of all other levels
SetLocalInt( oDragon, "CSL_DRAGON_HD", iDragonLevel );
}
}
iDragonLevel -= CSLGetNegativeLevels( oDragon );
if ( iDragonLevel < 1 )
{
return 1;
}
if ( iDragonLevel > iHitDice )
{
return iHitDice;
}
return iDragonLevel;
}
// general purpose breath weapon function, this way i only have code in one spot and it affects all dragons
void SCDragonBreath( int iDamageType, int VFXShape=SHAPE_SPELLCONE, float fRange=0.0, int iVFXImpact=VFX_NONE, int iVFXBreath=VFX_NONE, string sVFXBreath="", object oDragon = OBJECT_SELF )
{
int iAge = SCDragonGetAge(oDragon);
int iDice = iAge;
int iDC = 13 + iAge/2 + GetAbilityModifier( ABILITY_CONSTITUTION, oDragon);
int iDamage;
float fDelay;
if (fRange==0.0)
{
fRange=10.0 + iAge/2.0;
}
int iSaveType = CSLGetSaveTypeByDamageType(iDamageType);
effect eImpact = EffectVisualEffect(iVFXImpact);
effect eBreath;
SCPlayDragonBattleCry();
PlayCustomAnimation(oDragon, "Una_breathattack01", 0, 0.7f);
if (sVFXBreath!="") // NWN2 EFFECT
{
eBreath = EffectNWN2SpecialEffectFile(sVFXBreath);
DelayCommand(0.5f, HkApplyEffectToObject(DURATION_TYPE_TEMPORARY, eBreath, oDragon, 3.0f));
}
else if (iVFXBreath!=VFX_NONE) // NWN1 BEAM
{
eBreath = EffectVisualEffect(iVFXBreath);
object oEndTarget = CreateObject(OBJECT_TYPE_PLACEABLE, "plc_ipoint ", CSLGetAheadLocation(oDragon, fRange));
DelayCommand(0.5f, HkApplyEffectToObject(DURATION_TYPE_TEMPORARY, eBreath, oEndTarget, 4.0f));
DelayCommand(1.0f, DestroyObject(oEndTarget));
}
location lLocation = GetLocation( HkGetSpellTarget() );
object oTarget = GetFirstObjectInShape(VFXShape, fRange, lLocation, TRUE, OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE | OBJECT_TYPE_DOOR, GetPosition(oDragon));
while(GetIsObjectValid(oTarget))
{
if ( oTarget != oDragon && CSLSpellsIsTarget( oTarget, SCSPELL_TARGET_STANDARDHOSTILE, oDragon))
{
SignalEvent(oTarget, EventSpellCastAt(oDragon, GetSpellId()));
fDelay = GetDistanceBetween(oDragon, oTarget)/20.0f;
iDamage = HkGetSaveAdjustedDamage( SAVING_THROW_REFLEX, SAVING_THROW_METHOD_FORHALFDAMAGE, d6(iDice), oTarget, iDC, iSaveType, oDragon );
iDamage = HkApplyTargetModifiers( iDamage, iDamageType, oTarget, VFX_IMP_HEALING_M, fDelay, oDragon );
//if (HkSavingThrow(SAVING_THROW_REFLEX, oTarget, iDC, iSaveType))
//{
// iDamage = iDamage/2;
// if (GetHasFeat(FEAT_EVASION, oTarget) || GetHasFeat(FEAT_IMPROVED_EVASION, oTarget)) iDamage = 0;
//}
//else if (GetHasFeat(FEAT_IMPROVED_EVASION, oTarget))
//{
// iDamage = iDamage/2;
//}
if (iDamage)
{
eBreath = EffectDamage(iDamage, iDamageType);
DelayCommand(fDelay, HkApplyEffectToObject(DURATION_TYPE_INSTANT, eBreath, oTarget));
DelayCommand(fDelay, HkApplyEffectToObject(DURATION_TYPE_TEMPORARY, eImpact, oTarget, 3.0 ));
DelayCommand(fDelay+0.5, HkApplyEffectToObject(DURATION_TYPE_TEMPORARY, eImpact, oTarget, 3.0 ));
DelayCommand(fDelay+1.0, HkApplyEffectToObject(DURATION_TYPE_TEMPORARY, eImpact, oTarget, 3.0 ));
}
}
oTarget = GetNextObjectInShape(VFXShape, fRange, lLocation, TRUE, OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE | OBJECT_TYPE_DOOR);
}
}
// and implemented in FT_drgbrthfire and the other breath weapon ability scripts
#include "_HkSpell"
#include "_SCInclude_Monster"
////#include "_inc_helper_functions"
//#include "_SCUtility"
void main()
{
int iAttributes = SCMETA_ATTRIBUTES_SUPERNATURAL | SCMETA_ATTRIBUTES_SOMANTICCOMP | SCMETA_ATTRIBUTES_HOSTILE | SCMETA_ATTRIBUTES_CANTCASTINTOWN;
SCDragonBreath(DAMAGE_TYPE_FIRE, SHAPE_SPELLCONE, 0.0, VFX_IMP_FLAME_M, VFX_NONE, "fx_red_dragon_breath.sef");
}