/*/////////////////////// [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. 1.4 - Added more silent shouts. Edited the formatting. Moved a few things around. ///////////////////////// [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 nSpellCast, 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-spell cast at-event. Returns TRUE if we interrupt this script call. if(FirePreUserEvent(AI_FLAG_UDE_SPELL_CAST_AT_PRE_EVENT, EVENT_SPELL_CAST_AT_PRE_EVENT)) return; // AI status check. Is the AI on? if(GetAIOff()) return; object oCaster = GetLastSpellCaster(); int bHarmful = GetLastSpellHarmful(); int nSpellCast = GetLastSpell(); object oAttackerOfCaster; // If harmful, we set the spell to a timer, if an AOE one. if(bHarmful && GetIsObjectValid(oCaster)) { // Might set AOE spell to cast. SetAOESpell(nSpellCast, 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); // Shout to allies to attack, or be prepared. AISpeakString(AI_SHOUT_CALL_TO_ARMS); // Attack anyone else around if(!CannotPerformCombatRound()) { // Determine Combat Round DetermineCombatRound(); } } // If a friend, or dead, or a DM, or invalid, or self, we ignore them. else if(!GetIgnoreNoFriend(oCaster) && oCaster != OBJECT_SELF) { // 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 bHarmful for speakstrings. // If harmful, we attack anyone! (and if is enemy) // 1.4: Faction equal check in GetIgnoreNoFriend() if(bHarmful || GetIsEnemy(oCaster)) { // 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); } } if(bHarmful) { // * Don't speak when dead. 1.4 change (an obvious one to make) if(CanSpeak()) { // Hostile spell speaksting, if set. SpeakArrayString(AI_TALK_ON_HOSTILE_SPELL_CAST_AT); } } // Turn of hiding check TurnOffHiding(oCaster); // We attack if(!CannotPerformCombatRound()) { // 68: "[Spell:Enemy/Hostile] Not in combat. Attacking: [Caster] " + GetName(oCaster) DebugActionSpeakByInt(68, oCaster); DetermineCombatRound(oCaster); } // Shout to allies to attack the enemy who attacked me, got via. Last Hostile Actor. AISpeakString(AI_SHOUT_I_WAS_ATTACKED); } // Else, was neutral perhaps. Don't attack them anyway. else { // 69: "[Spell] (ally). Not in combat. May Attack/Move [Caster] " + GetName(oCaster) DebugActionSpeakByInt(69, oCaster); // Set special action to investigate - as if this event was triggered // by I_WAS_ATTACKED. // If we are already attacking, we do not move if(CannotPerformCombatRound()) { // Shout to allies to attack, or be prepared. AISpeakString(AI_SHOUT_CALL_TO_ARMS); } else { // We react as if the caster, a neutral, called for help ala // I_WAS_ATTACKED (they might not have, might just be // preperation for something), but normally, this is a neutral // casting a spell. Do not respond to PC's. if(!GetIsPC(oCaster)) { IWasAttackedResponse(oCaster); } } } } // If they are not a faction equal, and valid, we help them. else if(GetIsObjectValid(oCaster) && GetFactionEqual(oCaster)) { IWasAttackedResponse(oCaster); } // 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 nSpellCast, object oCaster) { // Check it is one we can check int bStop = TRUE; switch(nSpellCast) { 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: { bStop = FALSE; } break; } // Check immune level int nImmuneLevel = GetOurSpellLevelImmunity(); if(nImmuneLevel >= 9) { bStop = TRUE; } // Check if(bStop == TRUE) { return; } // We do use intelligence here... int nAIInt = GetBoundriedAIInteger(AI_INTELLIGENCE); int bIgnoreSaves; int bIgnoreImmunities; object oAOE; // If it is low, we ignore all things that we could ignore with it... if(nAIInt <= 3) { bIgnoreSaves = TRUE; bIgnoreImmunities = TRUE; } // Average ignores saves else if(nAIInt <= 7) { bIgnoreSaves = TRUE; bIgnoreImmunities = FALSE; } // Else, we do both. else { bIgnoreSaves = FALSE; bIgnoreImmunities = FALSE; } int bSetAOE = FALSE;// TRUE means set to timer int nSaveDC = 11; // Get the caster DC, the most out of WIS, INT or CHA... int nInt = GetAbilityModifier(ABILITY_INTELLIGENCE, oCaster); int nWis = GetAbilityModifier(ABILITY_WISDOM, oCaster); int nCha = GetAbilityModifier(ABILITY_CHARISMA, oCaster); if(nInt > nWis && nInt > nCha) { nSaveDC += nInt; } else if(nWis > nCha) { nSaveDC += nWis; } else { nSaveDC += nCha; } // 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(nSpellCast) { // 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(25 >= GetAC(OBJECT_SELF) && nImmuneLevel < 4 && ((GetFortitudeSavingThrow(OBJECT_SELF) < nSaveDC + 2) || bIgnoreSaves)) { bSetAOE = 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(nImmuneLevel < 3 && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_MOVEMENT_SPEED_DECREASE) || bIgnoreImmunities) && ((GetReflexSavingThrow(OBJECT_SELF) < nSaveDC + 5) || bIgnoreSaves)) { bSetAOE = TRUE; // Both use ENTANGLE AOE's oAOE = GetNearestAOECastBy(AI_AOE_PER_ENTANGLE, oCaster); } } break; case SPELL_ENTANGLE: case SPELL_VINE_MINE_ENTANGLE: { if(nImmuneLevel < 1 && (!GetHasFeat(FEAT_WOODLAND_STRIDE) || bIgnoreImmunities) && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_ENTANGLE) || bIgnoreImmunities) && ((GetReflexSavingThrow(OBJECT_SELF) < nSaveDC + 4) || bIgnoreSaves)) { bSetAOE = TRUE; // Both use ENTANGLE AOE's oAOE = GetNearestAOECastBy(AI_AOE_PER_ENTANGLE, oCaster); } } break; case SPELL_WEB: { if(nImmuneLevel < 1 && (!GetHasFeat(FEAT_WOODLAND_STRIDE) || bIgnoreImmunities) && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_ENTANGLE) || bIgnoreImmunities) && ((GetReflexSavingThrow(OBJECT_SELF) < nSaveDC + 4) || bIgnoreSaves)) { bSetAOE = TRUE; // Web AOE oAOE = GetNearestAOECastBy(AI_AOE_PER_WEB, oCaster); } } break; // Fort save case SPELL_STINKING_CLOUD: { if(nImmuneLevel < 3 && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_POISON) || bIgnoreImmunities) && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_DAZED) || bIgnoreImmunities) && ((GetFortitudeSavingThrow(OBJECT_SELF) < nSaveDC + 6) || bIgnoreSaves)) { bSetAOE = TRUE; // Stinking cloud persistant AOE. oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGSTINK, oCaster); } } break; // Fort save case SPELL_CLOUD_OF_BEWILDERMENT: { if(nImmuneLevel < 2 && ((GetFortitudeSavingThrow(OBJECT_SELF) < nSaveDC + 7) || bIgnoreSaves)) { bSetAOE = TRUE; // Bewilderment cloud persistant AOE. oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGBEWILDERMENT, oCaster); } } break; // Special: Mind save is the effect. case SPELL_STONEHOLD: { if(nImmuneLevel < 6 && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_MIND_SPELLS) || bIgnoreImmunities) && ((GetWillSavingThrow(OBJECT_SELF) < nSaveDC + 7) || bIgnoreSaves)) { bSetAOE = 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(nImmuneLevel < 5 && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_SAVING_THROW_DECREASE) || bIgnoreImmunities) && ((GetWillSavingThrow(OBJECT_SELF) < nSaveDC + 6) || bIgnoreSaves)) { bSetAOE = TRUE; // Mind fog oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGMIND, oCaster); } } break; // Special: Feats, knockdown case SPELL_GREASE: { if(nImmuneLevel < 1 && (!GetIsImmune(OBJECT_SELF, IMMUNITY_TYPE_KNOCKDOWN) || bIgnoreImmunities) && (!GetHasFeat(FEAT_WOODLAND_STRIDE, OBJECT_SELF) || bIgnoreImmunities) && ((GetReflexSavingThrow(OBJECT_SELF) < nSaveDC + 2) || bIgnoreSaves)) { bSetAOE = 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(nImmuneLevel < 6 && (((GetReflexSavingThrow(OBJECT_SELF) < nSaveDC + 6) && !GetHasFeat(FEAT_IMPROVED_EVASION) && !GetHasFeat(FEAT_EVASION)) || bIgnoreSaves)) { bSetAOE = TRUE; if(nSpellCast == SPELL_BLADE_BARRIER) { // BB oAOE = GetNearestAOECastBy(AI_AOE_PER_WALLBLADE, oCaster); } else if(nSpellCast == SPELL_INCENDIARY_CLOUD) { // Fog of fire oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGFIRE, oCaster); } else if(nSpellCast == 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(nImmuneLevel < 6) { bSetAOE = TRUE; if(nSpellCast == SPELL_ACID_FOG) { // Acid fog oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGACID, oCaster); } else if(nSpellCast == SPELL_CLOUDKILL) { // Cloud Kill oAOE = GetNearestAOECastBy(AI_AOE_PER_FOGKILL, oCaster); } else if(nSpellCast == 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 && nImmuneLevel < 9) { bSetAOE = TRUE; // Storm of vengance oAOE = GetNearestAOECastBy(AI_AOE_PER_STORM, oCaster); } } } if(bSetAOE) { if(!GetLocalTimer(AI_TIMER_AOE_SPELL_EVENT + IntToString(nSpellCast))) { SetLocalTimer(AI_TIMER_AOE_SPELL_EVENT + IntToString(nSpellCast), 18.0); // Set nearest AOE if(GetIsObjectValid(oAOE)) { // Set nearest AOE of this spell to the local object oNearest = GetAIObject(AI_TIMER_AOE_SPELL_EVENT + IntToString(nSpellCast)); if(GetDistanceToObject(oAOE) < GetDistanceToObject(oNearest) || !GetIsObjectValid(oNearest)) { SetAIObject(AI_TIMER_AOE_SPELL_EVENT + IntToString(nSpellCast), oAOE); } } } } } // Gets the nearest AOE cast by oCaster, of sTag. object GetNearestAOECastBy(string sTag, object oCaster) { int nCnt = 1; object oAOE = GetNearestObjectByTag(sTag, OBJECT_SELF, nCnt); object oReturn = OBJECT_INVALID; // Loop while(GetIsObjectValid(oAOE) && !GetIsObjectValid(oReturn)) { // Check creator if(GetAreaOfEffectCreator(oAOE) == oCaster) { oReturn = oAOE; } nCnt++; oAOE = GetNearestObjectByTag(sTag, OBJECT_SELF, nCnt); } return oReturn; } // Gets the amount of protections we have - IE globes int GetOurSpellLevelImmunity() { int nNatural = GetLocalInt(OBJECT_SELF, AI_SPELL_IMMUNE_LEVEL); // Stop here, if natural is over 4 if(nNatural > 4) return nNatural; // Big globe affects 4 or lower spells if(GetHasSpellEffect(SPELL_GLOBE_OF_INVULNERABILITY, OBJECT_SELF) || nNatural >= 4) return 4; // 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) || nNatural >= 3) return 3; // 2 and under is ethereal visage. if(GetHasSpellEffect(SPELL_ETHEREAL_VISAGE, OBJECT_SELF) || nNatural >= 2) return 2; // Ghostly Visarge affects 1 or 0 level spells, and any spell immunity. if(GetHasSpellEffect(SPELL_GHOSTLY_VISAGE, OBJECT_SELF) || nNatural >= 1 || // Or shadow con version. GetHasSpellEffect(SPELL_GREATER_SHADOW_CONJURATION_MIRROR_IMAGE, OBJECT_SELF)) return 1; // Return nNatural, which is 0-9 return FALSE; }