Updated Release Archive. Fixed Mage-killer prereqs. Removed old LETO & ConvoCC related files. Added organized spell scroll store. Fixed Gloura spellbook. Various TLK fixes. Reorganized Repo. Removed invalid user folders. Added DocGen back in.
293 lines
11 KiB
Plaintext
293 lines
11 KiB
Plaintext
/*:://////////////////////////////////////////////
|
|
//:: Spell Name Force Missiles
|
|
//:: Spell FileName XXX_S_ForceMissi
|
|
//:://////////////////////////////////////////////
|
|
//:: In Game Spell desctiption
|
|
//:://////////////////////////////////////////////
|
|
Evocation [Force]
|
|
Also known as: Mordenkainen's Force Missiles
|
|
Level: Sor/Wiz 4
|
|
Components: V, S
|
|
Casting Time: 1 standard action
|
|
Range: Medium (20M)
|
|
Targets: Up to four enemy creatures in a 1.67M-radius sphere (5ft)
|
|
Duration: Instantaneous
|
|
Saving Throw: None or Reflex half (see text)
|
|
Spell Resistance: Yes
|
|
Source: Various (WotC Spellbook)
|
|
|
|
You create a powerful missile of magical force, which darts from your
|
|
fingertips and unerringly strikes its target, dealing 2d6 points of damage.
|
|
The missile then bursts in a 1.67-meter blast of force that inflicts half
|
|
this amount of damage to any creatures in the area (other than the primary
|
|
target). The primary target is not entitled to a saving throw against the
|
|
burst, but creatures affected by the burst may attempt a Reflex save for
|
|
half damage.
|
|
|
|
If the missiles' burst areas overlap, secondary targets make only one saving
|
|
throw attempt (and only one SR check, if applicable). A character can be
|
|
struck by one missile (or more) and also be caught in the burst of another
|
|
missile. In such a case, the character may attempt a Reflex save to halve
|
|
the burst damage, and SR might apply.
|
|
|
|
The missile strikes unerringly, even if the target is in melee or has
|
|
anything less than total cover or concealment.
|
|
|
|
For every five caster levels, the caster gains one missile. A caster has two
|
|
missiles at 9th level or lower, three missiles from 10th to 14th level, and
|
|
four missiles at 15th level or higher. If you can fire more then one
|
|
missile, and target one person, all missiles fly towards that target. If you
|
|
target the ground, the nearest 2 or more enemies will get hit in a 3M
|
|
radius sphere.
|
|
//:://////////////////////////////////////////////
|
|
//:: Spell Effects Applied / Notes
|
|
//:://////////////////////////////////////////////
|
|
Using the magic missile effect, it'll do burst damage against those in
|
|
the area *at the time of casting*, thus it might look a little odd, oh well.
|
|
|
|
The way the "Only one save and SR check" is done is that a local is set
|
|
and checked for each target each blast.
|
|
//:://////////////////////////////////////////////
|
|
//:: Created By: Jasperre
|
|
//::////////////////////////////////////////////*/
|
|
|
|
#include "SMP_INC_SPELLS"
|
|
|
|
// Do the AOE blast using oOriginalTarget's location
|
|
// * Damages oOriginalTarget fully.
|
|
// * Uses the visuals eMissile and eVis.
|
|
void DoMissileAndBlast(object oOriginalTarget, object oCaster, int nSpellSaveDC, string sSRLocal, string sSaveLocal, int nMissiles, int nMetaMagic, effect eMissile, effect eVis);
|
|
|
|
// Reflex adjust nDamage, using stored stuff.
|
|
int ReturnReflexDamage(int nDamage, string sSaveLocal, object oTarget, int nDC, object oSaveVersus, float fDelay);
|
|
// Check SR, using stored stuff.
|
|
// * TRUE is resisted, FALSE isn't.
|
|
int ReturnSRCheck(object oTarget, object oCaster, string sSRLocal, float fDelay = 0.0);
|
|
|
|
void main()
|
|
{
|
|
// Spell Hook Check.
|
|
if(!SMP_SpellHookCheck(SMP_SPELL_FORCE_MISSILES)) return;
|
|
|
|
//Declare major variables
|
|
object oCaster = OBJECT_SELF;
|
|
object oTarget = GetSpellTargetObject();
|
|
location lTarget = GetSpellTargetLocation();
|
|
int nMetaMagic = SMP_GetMetaMagicFeat();
|
|
int nCasterLevel = SMP_GetCasterLevel();
|
|
int nSpellSaveDC = SMP_GetSpellSaveDC();
|
|
int nPeopleDone;
|
|
string sSRLocal = IntToString(SMP_SPELL_FORCE_MISSILES) + ObjectToString(oCaster) + "SR";
|
|
string sSaveLocal = IntToString(SMP_SPELL_FORCE_MISSILES) + ObjectToString(oCaster) + "SAVE";
|
|
|
|
// For every five caster levels, the caster gains one missile.
|
|
int nMissiles = SMP_LimitInteger(nCasterLevel/5, 4, 1);
|
|
|
|
// Declare Effects
|
|
effect eMissile = EffectVisualEffect(VFX_IMP_MIRV);
|
|
effect eVis = EffectVisualEffect(VFX_IMP_MAGBLUE);
|
|
|
|
// Is it one target?
|
|
if(GetIsObjectValid(oTarget) && GetObjectType(oTarget) == OBJECT_TYPE_CREATURE)
|
|
{
|
|
// Check PvP
|
|
if(!GetIsReactionTypeFriendly(oTarget))
|
|
{
|
|
// Use function
|
|
DoMissileAndBlast(oTarget, oCaster, nSpellSaveDC, sSRLocal, sSaveLocal, nMissiles, nMetaMagic, eMissile, eVis);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We loop targets in the LOS of 2M sphere
|
|
oTarget = GetFirstObjectInShape(SHAPE_SPHERE, 2.0, lTarget, TRUE, OBJECT_TYPE_CREATURE);
|
|
while(GetIsObjectValid(oTarget) && nPeopleDone < nMissiles)
|
|
{
|
|
// Check if an enemy and is seen
|
|
if(GetIsReactionTypeHostile(oTarget) && GetObjectSeen(oTarget))
|
|
{
|
|
// Use function - 1 missile blast
|
|
DoMissileAndBlast(oTarget, oCaster, nSpellSaveDC, sSRLocal, sSaveLocal, 1, nMetaMagic, eMissile, eVis);
|
|
// Add one to done list
|
|
nPeopleDone++;
|
|
}
|
|
oTarget = GetNextObjectInShape(SHAPE_SPHERE, 2.0, lTarget, TRUE, OBJECT_TYPE_CREATURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do the AOE blast using oOriginalTarget's location
|
|
// * Damages oOriginalTarget fully.
|
|
// * Uses the visuals eMissile and eVis.
|
|
void DoMissileAndBlast(object oOriginalTarget, object oCaster, int nSpellSaveDC, string sSRLocal, string sSaveLocal, int nMissiles, int nMetaMagic, effect eMissile, effect eVis)
|
|
{
|
|
// Get delay for hitting.
|
|
float fDist = GetDistanceBetween(oCaster, oOriginalTarget);
|
|
float fDelay = fDist/(3.0 * log(fDist) + 2.0);
|
|
float fDelay2;
|
|
float fAOEDelay;
|
|
object oAOETarget;
|
|
int nCnt, nDam, nSR, nSave;
|
|
location lTarget;
|
|
|
|
// Apply visual effect (Magic Missiles)
|
|
for(nCnt = 1; nCnt <= nMissiles; nCnt++)
|
|
{
|
|
fDelay2 += 0.1;
|
|
DelayCommand(fDelay2, SMP_ApplyVFX(oOriginalTarget, eMissile));
|
|
|
|
// Signal Spell cast at, once
|
|
if(GetLocalInt(oOriginalTarget, sSRLocal) != FALSE)
|
|
{
|
|
SMP_SignalSpellCastAt(oOriginalTarget, SMP_SPELL_FORCE_MISSILES);
|
|
}
|
|
|
|
// Do damage to oOriginalTarget, if they fail thier SR. No save!
|
|
// Check SR
|
|
if(!ReturnSRCheck(oOriginalTarget, oCaster, sSRLocal, fDelay))
|
|
{
|
|
// Do damage
|
|
nDam = SMP_MaximizeOrEmpower(6, 2, nMetaMagic);
|
|
DelayCommand(fDelay, SMP_ApplyDamageVFXToObject(oOriginalTarget, eVis, nDam));
|
|
}
|
|
|
|
// For each blast, we loop targets in the AOE
|
|
// We loop targets in the LOS of 1.67M sphere
|
|
lTarget = GetLocation(oOriginalTarget);
|
|
oAOETarget = GetFirstObjectInShape(SHAPE_SPHERE, 1.67, lTarget, TRUE, OBJECT_TYPE_CREATURE);
|
|
while(GetIsObjectValid(oAOETarget))
|
|
{
|
|
// PvP Check and is NOT original target
|
|
if(!GetIsReactionTypeFriendly(oAOETarget) &&
|
|
oAOETarget != oOriginalTarget)
|
|
{
|
|
// Signal Spell cast at, once
|
|
if(GetLocalInt(oAOETarget, sSRLocal) != FALSE)
|
|
{
|
|
// Signal Spell cast at
|
|
SMP_SignalSpellCastAt(oAOETarget, SMP_SPELL_FORCE_MISSILES);
|
|
}
|
|
// Make sure we check SR as the original target
|
|
fAOEDelay = fDelay + GetDistanceBetweenLocations(lTarget, GetLocation(oAOETarget))/20;
|
|
|
|
// Do damage to oAOETarget, if they fail thier SR. No save!
|
|
if(!ReturnSRCheck(oAOETarget, oCaster, sSRLocal, fAOEDelay))
|
|
{
|
|
// Get damage to do
|
|
nDam = SMP_MaximizeOrEmpower(6, 1, nMetaMagic);
|
|
|
|
// Adjust it due to saves, done already or not.
|
|
nDam = ReturnReflexDamage(nDam, sSaveLocal, oAOETarget, nSpellSaveDC, oCaster, fAOEDelay);
|
|
|
|
// Do the damage, if any
|
|
if(nDam > 0)
|
|
{
|
|
DelayCommand(fDelay, SMP_ApplyDamageVFXToObject(oOriginalTarget, eVis, nDam));
|
|
}
|
|
}
|
|
}
|
|
oAOETarget = GetNextObjectInShape(SHAPE_SPHERE, 1.67, lTarget, TRUE, OBJECT_TYPE_CREATURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reflex adjust nDamage, using stored stuff.
|
|
int ReturnReflexDamage(int nDamage, string sSaveLocal, object oTarget, int nDC, object oSaveVersus, float fDelay)
|
|
{
|
|
// Check if saved already
|
|
int nSave = GetLocalInt(oTarget, sSaveLocal);
|
|
|
|
// 0 = not attempted
|
|
// 1 = saved
|
|
// 2 = Not saved, failed in the attempt.
|
|
|
|
if(nSave > 0)
|
|
{
|
|
if(nSave == 1)
|
|
{
|
|
// Saved
|
|
return SMP_ReflexAdjustDamage(TRUE, nDamage, oTarget);
|
|
}
|
|
else//if(nSave == 2)
|
|
{
|
|
// Failed
|
|
return SMP_ReflexAdjustDamage(FALSE, nDamage, oTarget);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// New save
|
|
nSave = SMP_SavingThrow(SAVING_THROW_REFLEX, oTarget, nDC, SAVING_THROW_TYPE_NONE, oSaveVersus, fDelay);
|
|
|
|
// Saved?
|
|
if(nSave == FALSE)
|
|
{
|
|
// Didn't save. Set and return.
|
|
SetLocalInt(oTarget, sSaveLocal, 2);
|
|
DelayCommand(0.1, DeleteLocalInt(oTarget, sSaveLocal));
|
|
|
|
// Failed
|
|
return SMP_ReflexAdjustDamage(FALSE, nDamage, oTarget);
|
|
}
|
|
else// is over 1
|
|
{
|
|
// Passed
|
|
SetLocalInt(oTarget, sSaveLocal, 1);
|
|
DelayCommand(0.1, DeleteLocalInt(oTarget, sSaveLocal));
|
|
|
|
// Passed
|
|
return SMP_ReflexAdjustDamage(TRUE, nDamage, oTarget);
|
|
}
|
|
}
|
|
return nDamage;
|
|
}
|
|
|
|
// Check SR, using stored stuff.
|
|
// * TRUE is resisted, FALSE isn't.
|
|
int ReturnSRCheck(object oTarget, object oCaster, string sSRLocal, float fDelay = 0.0)
|
|
{
|
|
// Get old result
|
|
int nResult = GetLocalInt(oTarget, sSRLocal);
|
|
if(nResult != TRUE)// Resisted already
|
|
{
|
|
// TRUE means we pass, and no damage, else we check if we need to do it.
|
|
// 1 Means we have passed, and do not need to do one.
|
|
// 2 means we failed the check, no more checks.
|
|
// 0 Means an absense of a check
|
|
if(nResult == 2)
|
|
{
|
|
// Failed already.
|
|
return FALSE;
|
|
}
|
|
else //if(nSR == 0)
|
|
{
|
|
// Make a new SR check - our first one!
|
|
nResult = SMP_SpellResistanceCheck(oCaster, oTarget, fDelay);
|
|
|
|
if(nResult == FALSE)
|
|
{
|
|
// Failed
|
|
// Set we always failed - 2
|
|
SetLocalInt(oTarget, sSRLocal, 2);
|
|
DelayCommand(0.1, DeleteLocalInt(oTarget, sSRLocal));
|
|
return FALSE;
|
|
}
|
|
else //must have passed somehow
|
|
{
|
|
// Passed/resisted always. Set always pass - 1
|
|
SetLocalInt(oTarget, sSRLocal, 1);
|
|
DelayCommand(0.1, DeleteLocalInt(oTarget, sSRLocal));
|
|
// Return TRUE, passed
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Resisted, it was a 1
|
|
return TRUE;
|
|
}
|
|
// Return DIDN'T resist, as a defualt.
|
|
return FALSE;
|
|
}
|