#include "util_inc" #include "dbg_inc" #include "spell_inc" #include "nwnx_creature" #include "nw_i0_spells" object GetClosestMissileTarget(location lTarget) { int nth = 1; object oTarget; while (1) { oTarget = GetNearestCreatureToLocation(CREATURE_TYPE_IS_ALIVE, TRUE, lTarget, nth++); if (oTarget == OBJECT_INVALID) return oTarget; if (GetPlotFlag(oTarget) || oTarget == OBJECT_SELF || !GetObjectSeen(oTarget) || !GetIsEnemy(oTarget)) continue; if (GetDistanceBetweenLocations(lTarget, GetLocation(oTarget)) <= RADIUS_SIZE_GARGANTUAN) return oTarget; else return OBJECT_INVALID; } return OBJECT_INVALID; } struct missiles { int vfx; int vfximpact; int count; int numdice; int whichdice; int dmgmod; int dmgtype; int continous; }; struct missiles GetMissiles(int nSpellId, object oCaster, int nMetaMagic) { struct missiles m; int nCasterLevel = GetCasterLevel(oCaster); switch (nSpellId) { case SPELL_MAGIC_MISSILE: case SPELL_SHADOW_CONJURATION_MAGIC_MISSILE: m.vfx = VFX_IMP_MIRV; m.vfximpact = VFX_IMP_MAGBLUE; m.count = PRCMin((1+nCasterLevel)/2, 5); m.numdice = 1; m.whichdice = 4; m.dmgmod = 1; m.dmgtype = DAMAGE_TYPE_MAGICAL; m.continous = 0; break; case SPELL_ISAACS_LESSER_MISSILE_STORM: m.vfx = VFX_IMP_MIRV; m.vfximpact = VFX_IMP_MAGBLUE; m.count = PRCMin(nCasterLevel, 10); m.numdice = 1; m.whichdice = 6; m.dmgmod = 0; m.dmgtype = DAMAGE_TYPE_MAGICAL; m.continous = 0; break; case SPELL_ISAACS_GREATER_MISSILE_STORM: m.vfx = VFX_IMP_MIRV; m.vfximpact = VFX_IMP_MAGBLUE; m.count = PRCMin(nCasterLevel, 20); m.numdice = 2; m.whichdice = 6; m.dmgmod = 0; m.dmgtype = DAMAGE_TYPE_MAGICAL; m.continous = 0; break; case SPELL_BALL_LIGHTNING: m.vfx = VFX_IMP_MIRV_ELECTRIC; m.vfximpact = VFX_IMP_LIGHTNING_S; m.count = PRCMin(nCasterLevel, 15); m.numdice = 1; m.whichdice = 6; m.dmgmod = 0; m.dmgtype = DAMAGE_TYPE_ELECTRICAL; m.continous = 0; break; case SPELL_FLAME_ARROW: m.vfx = VFX_IMP_MIRV_FLAME; m.vfximpact = VFX_IMP_FLAME_S; m.count = (3+nCasterLevel)/4; m.numdice = 4; m.whichdice = 6; m.dmgmod = 1; m.dmgtype = DAMAGE_TYPE_FIRE; m.continous = 0; break; case SPELL_FIREBRAND: m.vfx = VFX_IMP_MIRV_FLAME; m.vfximpact = VFX_IMP_FLAME_M; m.count = PRCMin(nCasterLevel, 15); m.numdice = 1; m.whichdice = 6; m.dmgmod = nCasterLevel; m.dmgtype = DAMAGE_TYPE_FIRE; m.continous = 0; break; case SPELL_MELFS_ACID_ARROW: case SPELL_GREATER_SHADOW_CONJURATION_ACID_ARROW: m.vfx = VFX_DUR_MIRV_ACID; m.vfximpact = VFX_IMP_ACID_L; m.count = 1; m.numdice = 3; m.whichdice = 6; m.dmgmod = 0; m.dmgtype = DAMAGE_TYPE_ACID; m.continous = 1 + (2+nCasterLevel)/3; break; default: dbg_ReportBug("Spell " + IntToString(nSpellId) + " calling into missile script", OBJECT_SELF); break; } return m; } int ApplyMissilesToTarget(object oCaster, object oTarget, struct missiles m, int nMetaMagic, int nMissiles=-1) { if (nMissiles != -1) m.count = nMissiles; if (m.count == 0) return 0; effect eMissile = EffectVisualEffect(m.vfx); effect eVis = EffectVisualEffect(m.vfximpact); int nHits = 0, nCrits = 0, i, d; float fDist = GetDistanceBetween(oCaster, oTarget); float fDelay = fDist / (3.0 * log(fDist) + 2.0); int nCasterBonus = 0; int nSpellSchool = spell_GetSchool(GetSpellId()); nCasterBonus += GetHasFeat(spell_GetSpellFocusFeat(nSpellSchool), oCaster)*2; nCasterBonus += GetHasFeat(spell_GetSpellFocusFeat(nSpellSchool, TRUE), oCaster)*3; nCasterBonus += GetHasFeat(FEAT_COMBAT_CASTING, oCaster)*4; nCasterBonus += GetLocalInt(oCaster, "SPELL_MISSILE_ATTACK_BOUNS"); int nOldBab = GetBaseAttackBonus(oCaster); NWNX_Creature_SetBaseAttackBonus(oCaster, nOldBab + nCasterBonus); for (i = 0; i < m.count; i++) { // try to hit. int nAttackResult = TouchAttackRanged(oTarget, TRUE); if (nAttackResult && !MyResistSpell(oCaster, oTarget, fDelay)) { int nDamage = m.dmgmod; if (nMetaMagic == METAMAGIC_MAXIMIZE) { nDamage += m.numdice * m.whichdice; } else { for (d = 0; d < m.numdice; d++) nDamage += Random(m.whichdice)+1; } if (nAttackResult == 2) nDamage *= 2; if (nMetaMagic == METAMAGIC_EMPOWER) nDamage = FloatToInt(1.5 * nDamage); //Apply the MIRV and damage effect effect eDam = EffectDamage(nDamage, m.dmgtype); DelayCommand(fDelay+0.1, ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eVis, oTarget)); DelayCommand(0.1, ApplyEffectToObject(DURATION_TYPE_INSTANT, eMissile, oTarget)); DelayCommand(fDelay+0.1, ApplyEffectToObject(DURATION_TYPE_INSTANT, eDam, oTarget)); } else if (nAttackResult > 0) // Resisted { ApplyEffectToObject(DURATION_TYPE_INSTANT, eMissile, oTarget); } else // missed { vector v = GetPosition(oTarget); v.x += IntToFloat(Random(100)-50)/25.0; v.y += IntToFloat(Random(100)-50)/25.0; ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eMissile, Location(GetArea(oTarget), v, 0.0)); } nHits += nAttackResult == 1; nCrits += nAttackResult == 2; } SignalEvent(oTarget, EventSpellCastAt(oCaster, GetSpellId())); NWNX_Creature_SetBaseAttackBonus(oCaster, nOldBab); return nCrits << 16 | nHits; } void main() { object oCaster = OBJECT_SELF; object oTarget = GetSpellTargetObject(); location lTarget = GetSpellTargetLocation(); int nSpell = GetSpellId(); int nMetaMagic = GetMetaMagicFeat(); if (GetIsObjectValid(oTarget)) oTarget = GetClosestMissileTarget(lTarget); if (oTarget == OBJECT_INVALID) { FloatingTextStringOnCreature("You cannot see a valid target in range", oCaster, FALSE); return; } struct missiles m = GetMissiles(nSpell, oCaster, nMetaMagic); if (GetObjectType(oTarget) != OBJECT_TYPE_CREATURE) { ApplyMissilesToTarget(oCaster, oTarget, m, nMetaMagic); return; } int nMissiles = m.count/2; // At least half of all missiles always go to primary target int nResults = ApplyMissilesToTarget(oCaster, oTarget, m, nMetaMagic, nMissiles); // For the rest, keep applying to nearby targets until we run out of missiles nMissiles = m.count - nMissiles; int nth = 1; while (nMissiles > 0) { oTarget = GetNearestCreatureToLocation(CREATURE_TYPE_IS_ALIVE, TRUE, lTarget, nth++); if (oTarget == OBJECT_INVALID) { nth = 1; continue; } if (GetPlotFlag(oTarget) || oTarget == oCaster || !GetObjectSeen(oTarget, oCaster) || !GetIsEnemy(oTarget, oCaster)) continue; if (GetDistanceBetweenLocations(lTarget, GetLocation(oTarget)) <= RADIUS_SIZE_GARGANTUAN) { nResults += ApplyMissilesToTarget(oCaster, oTarget, m, nMetaMagic, 1); nMissiles--; } else nth = 1; } // Display the hit data int nHits = nResults & 0xFFFF; int nCrits = nResults >> 16; int nMisses = m.count - nHits - nCrits; string sHitString = "You scored "; int bCrits = nCrits > 0; int bHits = nHits > 0; int bMisses = nMisses > 0; if (bHits || bCrits) { sHitString += IntToString(nCrits + nHits); if (!bHits) sHitString += " criticals"; else sHitString += " hits"; if (bHits && bCrits) sHitString += " (" + IntToString(nCrits) + " criticals)"; } if (bMisses) { if (bHits || bCrits) sHitString += " and "; sHitString += IntToString(nMisses) + " misses"; } DelayCommand(2.0, SendMessageToPC(oCaster, sHitString)); }