Simply, this is activated by SignalEvent and EventSpellCastAt, wether hostile or not. This will activate combat if any spell hits them (Either by moving to an ally who cast a spell, or attacking an enemy or a person who cast a hostile spell).
This will attack ANY caster who is an enemy (regardless of spell type), and also go to any allies who cast a spell and is attacking something. It will also work out what AOE spells are going to damage them and so forth.
This can be used to have special things happen with cirtain spells. Is a (invincible) monster who is hit with a hold monster spell killed? there are quite a few possibilities.
Arguments to use:
/************************ [On Spell Cast At] *********************************** Filename: j_ai_onspellcast or nw_c2_defaultb ************************* [On Spell Cast At] *********************************** What does this do? Well... - Any AOE spell effects are set in a timer, so we can react to them right - Reacts to hostile casters, or allies in combat And the normal attack :-) ************************* [History] ******************************************** 1.3 - Added special AOE checks. - Hide checks. ************************* [Workings] ******************************************* This is fired when EventSpellCastAt(object oCaster, int nSpell, int bHarmful=TRUE) is signaled on the creature. GetLastSpellCaster() = oCaster (Door, Placeable, Creature who cast it) GetLastSpell() = nSpell (The spell cast at us) GetLastSpellHarmful()= bHarmful (If it is harmful!) ************************* [Arguments] ****************************************** Arguments: GetLastSpellCaster, GetLastSpellHarmful GetLastSpell() ************************* [On Spell Cast At] **********************************/ #include "j_inc_other_ai" // Sets a local timer if the spell is an AOE one void SetAOESpell(int iSpellCast, object oCaster); // Gets the nearest AOE cast by oCaster, of sTag. object GetNearestAOECastBy(string sTag, object oCaster); // Gets the amount of protections we have - IE globes int GetOurSpellLevelImmunity(); void main() { // Pre-heartbeat-event if(FireUserEvent(AI_FLAG_UDE_SPELL_CAST_AT_PRE_EVENT, EVENT_SPELL_CAST_AT_PRE_EVENT)) // We may exit if it fires if(ExitFromUDE(EVENT_SPELL_CAST_AT_PRE_EVENT)) return; // AI status check. Is the AI on? if(GetAIOff()) return; object oCaster = GetLastSpellCaster(); int iHarmful = GetLastSpellHarmful(); int iSpellCast = GetLastSpell(); object oAttackerOfCaster; // If harmful, we set the spell to a timer, if an AOE one. if(iHarmful && GetIsObjectValid(oCaster)) { // Might set AOE spell to cast. SetAOESpell(iSpellCast, oCaster); } // If not a creature, probably an AOE or trap. if(GetObjectType(oCaster) != OBJECT_TYPE_CREATURE) { // 67: "[Spell] Caster isn't a creature! May look for target [Caster] " + GetName(oCaster) DebugActionSpeakByInt(67, oCaster); // Attack anyone else around if(!CannotPerformCombatRound()) { DetermineCombatRound(); } } else if(GetIsObjectValid(oCaster) && !GetIsDM(oCaster) && !GetIgnore(oCaster) && oCaster != OBJECT_SELF && !GetIsDead(oCaster)) { // 1.3 changes here: // - We do NOT need to know if it is hostile or not, except if it is hostile // and they are not our faction! We do, however, use iHarmful for speakstrings. if(!GetFactionEqual(oCaster)) { // If harmful, we attack anyone! (and if is enemy) if(iHarmful || GetIsEnemy(oCaster)) { if(iHarmful) { // Hostile spell speaksting, if set. SpeakArrayString(AI_TALK_ON_HOSTILE_SPELL_CAST_AT); } // Turn of hiding check TurnOffHiding(oCaster); // Attack AISpeakString(I_WAS_ATTACKED); // We attack if(!CannotPerformCombatRound()) { // 68: "[Spell:Enemy/Hostile] Not in combat. Attacking: [Caster] " + GetName(oCaster) DebugActionSpeakByInt(68, oCaster); DetermineCombatRound(oCaster); } } } // Else, faction is equal, we normally ignore, and only move to attack. else { // Spawn in condition hostile thingy if(GetSpawnInCondition(AI_FLAG_OTHER_CHANGE_FACTIONS_TO_HOSTILE_ON_ATTACK, AI_OTHER_MASTER)) { if(!GetIsEnemy(oCaster)) { AdjustReputation(oCaster, OBJECT_SELF, -100); } } // We move to the person, if they are attacking something we cannot see... AISpeakString(CALL_TO_ARMS); // Not in combat if(!CannotPerformCombatRound()) { // 69: "[Spell] (ally). Not in combat. May Attack/Move [Caster] " + GetName(oCaster) DebugActionSpeakByInt(69, oCaster); ClearAllActions(); // Check thier target. Might not be valid (IE AOE spell at location) oAttackerOfCaster = GetAttackTarget(oCaster); // - Faster then DetermineCombatRound(); and looping targets until // we get this ally, and get this attacker! :-) if(GetIsObjectValid(oAttackerOfCaster)) { // Move to the attack target, and wait for a proper on // perception event (as we are not currently in combat) ActionMoveToObject(oCaster, TRUE); } else { // Move to the caster otherwise ActionMoveToObject(oCaster, TRUE); } } } } // Fire End-spell cast at-UDE FireUserEvent(AI_FLAG_UDE_SPELL_CAST_AT_EVENT, EVENT_SPELL_CAST_AT_EVENT); } // Sets a local timer if the spell is an AOE one void SetAOESpell(int iSpellCast, object oCaster) { // Check it is one we can check int iImmuneLevel = GetOurSpellLevelImmunity(); switch(iSpellCast) { case SPELL_ACID_FOG: case SPELL_MIND_FOG: case SPELL_STORM_OF_VENGEANCE: case SPELL_GREASE: case SPELL_CREEPING_DOOM: case SPELL_SILENCE: case SPELL_BLADE_BARRIER: case SPELL_CLOUDKILL: case SPELL_STINKING_CLOUD: case SPELL_WALL_OF_FIRE: case SPELL_INCENDIARY_CLOUD: case SPELL_ENTANGLE: case SPELL_EVARDS_BLACK_TENTACLES: case SPELL_CLOUD_OF_BEWILDERMENT: case SPELL_STONEHOLD: case SPELL_VINE_MINE: case SPELL_SPIKE_GROWTH: case SPELL_VINE_MINE_HAMPER_MOVEMENT: case SPELL_VINE_MINE_ENTANGLE: { // Pass though, unless we are totally immune. if(iImmuneLevel >= i9) { return; } } break; // Any other spell default: { return; } break; } // We do use intelligence here... int iAIInt = GetBoundriedAIInteger(AI_INTELLIGENCE); int iIgnoreSaves; int iIgnoreImmunities; object oAOE; // If it is low, we ignore all things that we could ignore with it... if(iAIInt <= i3) { iIgnoreSaves = TRUE; iIgnoreImmunities = TRUE; } // Average ignores saves else if(iAIInt <= i7) { iIgnoreSaves = TRUE; iIgnoreImmunities = FALSE; } // Else, we do both. else { iIgnoreSaves = FALSE; iIgnoreImmunities = FALSE; } int SetAOE = FALSE;// TRUE means set to timer int iSaveDC = i11; // Get the caster DC, the most out of WIS, INT or CHA... int iInt = GetAbilityModifier(ABILITY_INTELLIGENCE, oCaster); int iWis = GetAbilityModifier(ABILITY_WISDOM, oCaster); int iCha = GetAbilityModifier(ABILITY_CHARISMA, oCaster); if(iInt > iWis && iInt > iCha) { iSaveDC += iInt; } else if(iWis > iCha) { iSaveDC += iWis; } else { iSaveDC += iCha; } // Note: // - No reaction type/friendly checks. Signal Event is only fired if the // spell WILL pierce any PvP/Friendly/Area settings // We check immunities here, please note... switch(iSpellCast) { // First: IS GetIsReactionTypeHostile ones. case SPELL_EVARDS_BLACK_TENTACLES: // Fortitude save, but if we are immune to the hits, its impossible to hurt us { // If save immune OR AC immune, we ignore this. if(i25 >= GetAC(OBJECT_SELF) && iImmuneLevel < i4 && ((GetFortitudeSavingThrow(OBJECT_SELF) < iSaveDC + i2) || iIgnoreSaves)) { SetAOE = TRUE; // Nearest string of tag oAOE = GetNearestAOECastBy(AI_AOE_PER_EVARDS_BLACK_TENTACLES, oCaster); } } case SPELL_SPIKE_GROWTH: case SPELL_VINE_MINE_HAMPER_MOVEMENT: // d4 damage. LOTS of speed loss. // Reflex save, or immunity, would stop the speed { if(iImmuneLevel < i3 && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_MOVEMENT_SPEED_DECREASE) || iIgnoreImmunities) && ((GetReflexSavingThrow(OBJECT_SELF) < iSaveDC + i5) || iIgnoreSaves)) { SetAOE = TRUE; // Both use ENTANGLE AOE's oAOE = GetNearestAOECastBy(AI_AOE_PER_ENTANGLE, oCaster); } } break; case SPELL_ENTANGLE: case SPELL_VINE_MINE_ENTANGLE: { if(iImmuneLevel < i1 && (!GetHasFeat(FEAT_WOODLAND_STRIDE) || iIgnoreImmunities) && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_ENTANGLE) || iIgnoreImmunities) && ((GetReflexSavingThrow(OBJECT_SELF) < iSaveDC + i4) || iIgnoreSaves)) { SetAOE = TRUE; // Both use ENTANGLE AOE's oAOE = GetNearestAOECastBy(AI_AOE_PER_ENTANGLE, oCaster); } } break; case SPELL_WEB: { if(iImmuneLevel < i1 && (!GetHasFeat(FEAT_WOODLAND_STRIDE) || iIgnoreImmunities) && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_ENTANGLE) || iIgnoreImmunities) && ((GetReflexSavingThrow(OBJECT_SELF) < iSaveDC + i4) || iIgnoreSaves)) { SetAOE = TRUE; // Web AOE oAOE = GetNearestAOECastBy(AI_AOE_PER_WEB, oCaster); } } break; // Fort save case SPELL_STINKING_CLOUD: { if(iImmuneLevel < i3 && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_POISON) || iIgnoreImmunities) && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_DAZED) || iIgnoreImmunities) && ((GetFortitudeSavingThrow(OBJECT_SELF) < iSaveDC + i6) || iIgnoreSaves)) { SetAOE = TRUE; // Stinking cloud persistant AOE. oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGSTINK, oCaster); } } break; // Fort save case SPELL_CLOUD_OF_BEWILDERMENT: { if(iImmuneLevel < i2 && ((GetFortitudeSavingThrow(OBJECT_SELF) < iSaveDC + i7) || iIgnoreSaves)) { SetAOE = TRUE; // Bewilderment cloud persistant AOE. oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGBEWILDERMENT, oCaster); } } break; // Special: Mind save is the effect. case SPELL_STONEHOLD: { if(iImmuneLevel < i6 && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_MIND_SPELLS) || iIgnoreImmunities) && ((GetWillSavingThrow(OBJECT_SELF) < iSaveDC + i7) || iIgnoreSaves)) { SetAOE = TRUE; // Stonehold persistant AOE. oAOE = GetNearestAOECastBy(AI_AOE_PER_STONEHOLD, oCaster); } } break; // Special: EFFECT_TYPE_SAVING_THROW_DECREASE is the effect. case SPELL_MIND_FOG: { if(iImmuneLevel < i5 && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_SAVING_THROW_DECREASE) || iIgnoreImmunities) && ((GetWillSavingThrow(OBJECT_SELF) < iSaveDC + i6) || iIgnoreSaves)) { SetAOE = TRUE; // Mind fog oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGMIND, oCaster); } } break; // Special: Feats, knockdown case SPELL_GREASE: { if(iImmuneLevel < i1 && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_KNOCKDOWN) || iIgnoreImmunities) && (!GetHasFeat(FEAT_WOODLAND_STRIDE, OBJECT_SELF) || iIgnoreImmunities) && ((GetReflexSavingThrow(OBJECT_SELF) < iSaveDC + i2) || iIgnoreSaves)) { SetAOE = TRUE; // Grease oAOE = GetNearestAOECastBy(AI_AOE_PER_GREASE, oCaster); } } break; // All other ReactionType ones. Some have different saves though! case SPELL_BLADE_BARRIER: // Reflex case SPELL_INCENDIARY_CLOUD:// reflex case SPELL_WALL_OF_FIRE:// Reflex { if(iImmuneLevel < i6 && (((GetReflexSavingThrow(OBJECT_SELF) < iSaveDC + i6) && !GetHasFeat(FEAT_IMPROVED_EVASION) && !GetHasFeat(FEAT_EVASION)) || iIgnoreSaves)) { SetAOE = TRUE; if(iSpellCast == SPELL_BLADE_BARRIER) { // BB oAOE = GetNearestAOECastBy(AI_AOE_PER_WALLBLADE, oCaster); } else if(iSpellCast == SPELL_INCENDIARY_CLOUD) { // Fog of fire oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGFIRE, oCaster); } else if(iSpellCast == SPELL_WALL_OF_FIRE) { // Wall of fire oAOE = GetNearestAOECastBy(AI_AOE_PER_WALLFIRE, oCaster); } } } break; case SPELL_ACID_FOG: // Fort: Half. No check, always damages. case SPELL_CLOUDKILL:// No save! case SPELL_CREEPING_DOOM: // No save! { if(iImmuneLevel < i6) { SetAOE = TRUE; if(iSpellCast == SPELL_ACID_FOG) { // Acid fog oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGACID, oCaster); } else if(iSpellCast == SPELL_CLOUDKILL) { // Cloud Kill oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGKILL, oCaster); } else if(iSpellCast == SPELL_CREEPING_DOOM) { // Creeping doom oAOE = GetNearestAOECastBy(AI_AOE_PER_CREEPING_DOOM, oCaster); } } } // Storm - because the AI likes it, we stay in it if it is ours :-) case SPELL_STORM_OF_VENGEANCE: // Reflex partial. No check, always damages. { if(oCaster != OBJECT_SELF && iImmuneLevel < i9) { SetAOE = TRUE; // Storm of vengance oAOE = GetNearestAOECastBy(AI_AOE_PER_STORM, oCaster); } } } if(SetAOE) { if(!GetLocalTimer(AI_TIMER_AOE_SPELL_EVENT + IntToString(iSpellCast))) { SetLocalTimer(AI_TIMER_AOE_SPELL_EVENT + IntToString(iSpellCast), f18); // Set nearest AOE if(GetIsObjectValid(oAOE)) { // Set nearest AOE of this spell to the local object oNearest = GetAIObject(AI_TIMER_AOE_SPELL_EVENT + IntToString(iSpellCast)); if(GetDistanceToObject(oAOE) < GetDistanceToObject(oNearest) || !GetIsObjectValid(oNearest)) { SetAIObject(AI_TIMER_AOE_SPELL_EVENT + IntToString(iSpellCast), oAOE); } } } } } // Gets the nearest AOE cast by oCaster, of sTag. object GetNearestAOECastBy(string sTag, object oCaster) { int iCnt = i1; object oAOE = GetNearestObjectByTag(sTag, OBJECT_SELF, iCnt); object oReturn = OBJECT_INVALID; // Loop while(GetIsObjectValid(oAOE) && !GetIsObjectValid(oReturn)) { // Check creator if(GetAreaOfEffectCreator(oAOE) == oCaster) { oReturn = oAOE; } iCnt++; oAOE = GetNearestObjectByTag(sTag, OBJECT_SELF, iCnt); } return oReturn; } // Gets the amount of protections we have - IE globes int GetOurSpellLevelImmunity() { int iNatural = GetLocalInt(OBJECT_SELF, AI_SPELL_IMMUNE_LEVEL); // Stop here, if natural is over 4 if(iNatural > i4) return iNatural; // Big globe affects 4 or lower spells if(GetHasSpellEffect(SPELL_GLOBE_OF_INVULNERABILITY, OBJECT_SELF) || iNatural >= i4) return i4; // Minor globe is 3 or under if(GetHasSpellEffect(SPELL_MINOR_GLOBE_OF_INVULNERABILITY, OBJECT_SELF) || // Shadow con version GetHasSpellEffect(SPELL_GREATER_SHADOW_CONJURATION_MINOR_GLOBE, OBJECT_SELF) || iNatural >= i3) return i3; // 2 and under is ethereal visage. if(GetHasSpellEffect(SPELL_ETHEREAL_VISAGE, OBJECT_SELF) || iNatural >= i2) return i2; // Ghostly Visarge affects 1 or 0 level spells, and any spell immunity. if(GetHasSpellEffect(SPELL_GHOSTLY_VISAGE, OBJECT_SELF) || iNatural >= i1 || // Or shadow con version. GetHasSpellEffect(SPELL_GREATER_SHADOW_CONJURATION_MIRROR_IMAGE, OBJECT_SELF)) return i1; // Return iNatural, which is 0-9 return FALSE; }