/////////////////////////////////////////////////////////////////////////
//
// DoBolt - Function to apply an elemental bolt damage effect given
// the following arguments:
//
//        nDieSize - die size to roll (d4, d6, or d8)
//        nBonusDam - bonus damage per die, or 0 for none
//        nDice = number of dice to roll.
//        nBoltEffect - visual effect to use for bolt(s)
//        nVictimEffect - visual effect to apply to target(s)
//        nDamageType - elemental damage type of the cone (DAMAGE_TYPE_xxx)
//        nSaveType - save type used for cone (SAVING_THROW_TYPE_xxx)
//        nSchool - spell school, defaults to SPELL_SCHOOL_EVOCATION.
//        fDoKnockdown - flag indicating whether spell does knockdown, defaults to FALSE.
//        nSpellID - spell ID to use for events
//
/////////////////////////////////////////////////////////////////////////

#include "prc_inc_spells"
#include "prc_add_spell_dc"

//* fires a storm of nCap missiles at targets in area
void PRCDoMissileStorm(int nD6Dice, int nCap, int nSpell, int nMIRV = VFX_IMP_MIRV, int nVIS = VFX_IMP_MAGBLUE, int nDAMAGETYPE = DAMAGE_TYPE_MAGICAL, int nONEHIT = FALSE, int nReflexSave = FALSE);

float GetVFXLength(location lCaster, float fLength, float fAngle);

void DoBolt(int nCasterLevel, int nDieSize, int nBonusDam, int nDice, int nBoltEffect,
     int nVictimEffect, int nDamageType, int nSaveType,
     int nSchool = SPELL_SCHOOL_EVOCATION, int nDoKnockdown = FALSE, int nSpellID = -1, float fRangeFt = 120.0f)
{
     // If code within the PreSpellCastHook (i.e. UMD) reports FALSE, do not run this spell
     //if (!X2PreSpellCastCode()) return;

     PRCSetSchool(nSchool);
     
     object oCaster = OBJECT_SELF;

     // Get the spell ID if it was not given.
     if (-1 == nSpellID) nSpellID = PRCGetSpellId();

     // Adjust the damage type if necessary.
     nDamageType = PRCGetElementalDamageType(nDamageType, OBJECT_SELF);

    int nDamage;
    int nSaveDC;
    int bKnockdownTarget;
    float fDelay;

    int nPenetr = nCasterLevel + SPGetPenetr();

    // individual effect
    effect eVis  = EffectVisualEffect(nVictimEffect);
    effect eKnockdown = EffectKnockdown();
    effect eDamage;
    
    // where is the caster?
    location lCaster = GetLocation(oCaster);

    // where is the target?
    location lTarget = PRCGetSpellTargetLocation();
    vector vOrigin = GetPosition(oCaster);
    float fLength = FeetToMeters(fRangeFt);
    
    // run away! Vector maths coming up...
    // VFX length
    //float fAngle             = GetRelativeAngleBetweenLocations(lCaster, lTarget);
    //float fVFXLength         = GetVFXLength(lCaster, fLength, fAngle);
    //float fDuration          = 3.0f;
    

    /*BeamLineFromCenter(DURATION_TYPE_TEMPORARY, nBoltEffect, lCaster, fVFXLength, fAngle, fDuration, "prc_invisobj", 0.0f, "z", 0.0f, 0.0f,
                      -1, -1, 0.0f, 1.0f, // no secondary VFX
                      fDuration);
    */
        // Do VFX. This is moderately heavy, so it isn't duplicated by Twin Power
        float fAngle             = GetRelativeAngleBetweenLocations(lCaster, lTarget);
        float fSpiralStartRadius = FeetToMeters(1.0f);
        float fRadius            = FeetToMeters(5.0f);
        float fDuration          = 4.5f;
        float fVFXLength         = GetVFXLength(lCaster, fLength, GetRelativeAngleBetweenLocations(lCaster, lTarget));
        // A tube of beams, radius 5ft, starting 1m from manifester and running for the length of the line
        BeamGengon(DURATION_TYPE_TEMPORARY, nBoltEffect, lCaster, fRadius, fRadius,
                   1.0f, fVFXLength, // Start 1m from the manifester, end at LOS end
                   8, // 8 sides
                   fDuration, "prc_invisobj",
                   0.0f, // Drawn instantly
                   0.0f, 0.0f, 45.0f, "y", fAngle, 0.0f,
                   -1, -1, 0.0f, 1.0f, // No secondary VFX
                   fDuration
                   );    
    // spell damage effects
    // Loop over targets in the spell shape
    object oTarget = MyFirstObjectInShape(SHAPE_SPELLCYLINDER, fLength, lTarget, TRUE, OBJECT_TYPE_CREATURE | OBJECT_TYPE_DOOR | OBJECT_TYPE_PLACEABLE, vOrigin);
    while(GetIsObjectValid(oTarget))
    {
        if(oTarget != oCaster && spellsIsTarget(oTarget, SPELL_TARGET_STANDARDHOSTILE, oCaster))
        {
            // Let the AI know
            PRCSignalSpellEvent(oTarget, TRUE, nSpellID, oCaster);
            // Reset the knockdown target flag.
            bKnockdownTarget = FALSE;
            // Make an SR check
            if(!PRCDoResistSpell(oCaster, oTarget, nPenetr))
            {
                // Roll damage
                nDamage = PRCGetMetaMagicDamage(nDamageType, nDice, nDieSize, nBonusDam);
                // Acid Sheath adds +1 damage per die to acid descriptor spells
                if (GetHasDescriptor(nSpellID, DESCRIPTOR_ACID) && GetHasSpellEffect(SPELL_MESTILS_ACID_SHEATH, oCaster))
                	nDamage += nDice;  
				// Adds damage per dice
				nDamage += SpellDamagePerDice(oCaster, nDice);
                int nFullDamage = nDamage;
                
                // Do save
                nSaveDC = PRCGetSaveDC(oTarget,OBJECT_SELF);
                if(nSaveType == SAVING_THROW_TYPE_COLD)
                {
                    // Cold has a fort save for half
                    if(PRCMySavingThrow(SAVING_THROW_FORT, oTarget, nSaveDC, nSaveType))
                    {
                        if (GetHasMettle(oTarget, SAVING_THROW_FORT))
                            nDamage = 0;
                        nDamage /= 2;
                    }
                }
                else
                    // Adjust damage according to Reflex Save, Evasion or Improved Evasion
                    nDamage = PRCGetReflexAdjustedDamage(nDamage, oTarget, nSaveDC, nSaveType);

                if(nDamage > 0)
                {
                    fDelay = GetDistanceBetweenLocations(lCaster, GetLocation(oTarget)) / 20.0f;
                    eDamage = PRCEffectDamage(oTarget, nDamage, nDamageType);
                    DelayCommand(1.0f + fDelay, SPApplyEffectToObject(DURATION_TYPE_INSTANT, eDamage, oTarget));
                    DelayCommand(1.0f + fDelay, PRCBonusDamage(oTarget));
                    DelayCommand(1.0f + fDelay, SPApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oTarget));
                }// end if - There was still damage remaining to be dealt after adjustments
                                        
                // Determine if the target needs to be knocked down.  The target is knocked down
                // if all of the following criteria are met:
                //    - Knockdown is enabled.
                //    - The damage from the spell didn't kill the creature
                //    - The creature is large or smaller
                //    - The creature failed it's reflex save.
                // If the spell does knockdown we need to figure out whether the target made or failed
                // the reflex save.  If the target doesn't have improved evasion this is easy, if the
                // damage is the same as the original damage then the target failed it's save.  If the
                // target has improved evasion then it's harder as the damage is halved even on a failed
                // save, so we have to catch that case.
                bKnockdownTarget = nDoKnockdown && !GetIsDead(oTarget) &&
                           PRCGetCreatureSize(oTarget) <= CREATURE_SIZE_LARGE &&
                           (nFullDamage == nDamage || (0 != nDamage && GetHasFeat(FEAT_IMPROVED_EVASION, oTarget)));
                // If we're supposed to apply knockdown then do so for 1 round.
                 if (bKnockdownTarget)
                      SPApplyEffectToObject(DURATION_TYPE_TEMPORARY, eKnockdown, oTarget, RoundsToSeconds(1),TRUE,-1,nCasterLevel);
                
            }// end if - SR check
        }// end if - Target validity check

       // Get next target
        oTarget = MyNextObjectInShape(SHAPE_SPELLCYLINDER, fLength, lTarget, TRUE, OBJECT_TYPE_CREATURE | OBJECT_TYPE_DOOR | OBJECT_TYPE_PLACEABLE, vOrigin);
    }// end while - Target loop

     PRCSetSchool();
}

// taken with minor modification from  psi_power_enbolt

float GetVFXLength(location lCaster, float fLength, float fAngle)
{
    float fLowerBound = 0.0f;
    float fUpperBound = fLength;
    float fVFXLength  = fLength / 2;
    vector vVFXOrigin = GetPositionFromLocation(lCaster);
    vector vAngle     = AngleToVector(fAngle);
    vector vVFXEnd;
    int bConverged    = FALSE;
    while(!bConverged)
    {
        // Create the test vector for this loop
        vVFXEnd = vVFXOrigin + (fVFXLength * vAngle);

        // Determine which bound to move.
        if(LineOfSightVector(vVFXOrigin, vVFXEnd))
            fLowerBound = fVFXLength;
        else
            fUpperBound = fVFXLength;

        // Get the new middle point
        fVFXLength = (fUpperBound + fLowerBound) / 2;

        // Check if the locations have converged
        if(fabs(fUpperBound - fLowerBound) < 2.5f)
            bConverged = TRUE;
    }

    return fVFXLength;
}

//::///////////////////////////////////////////////
//:: PRCDoMissileStorm
//:: Copyright (c) 2002 Bioware Corp.
//:://////////////////////////////////////////////
/*
    Fires a volley of missiles around the area
    of the object selected.

    Each missiles (nD6Dice)d6 damage.
    There are casterlevel missiles (to a cap as specified)
*/
//:://////////////////////////////////////////////
//:: Created By: Brent
//:: Created On: July 31, 2002
//:://////////////////////////////////////////////
//:: Modified March 14 2003: Removed the option to hurt chests/doors
//::  was potentially causing bugs when no creature targets available.
void PRCDoMissileStorm(int nD6Dice, int nCap, int nSpell, int nMIRV = VFX_IMP_MIRV, int nVIS = VFX_IMP_MAGBLUE, int nDAMAGETYPE = DAMAGE_TYPE_MAGICAL, int nONEHIT = FALSE, int nReflexSave = FALSE)
{
    object oTarget = OBJECT_INVALID;
    int nCasterLvl = PRCGetCasterLevel(OBJECT_SELF);
//    int nDamage = 0;
    int nMetaMagic = PRCGetMetaMagicFeat();
    int nCnt = 1;
    effect eMissile = EffectVisualEffect(nMIRV);
    effect eVis = EffectVisualEffect(nVIS);
    float fDist = 0.0;
    float fDelay = 0.0;
    float fDelay2, fTime;
    location lTarget = PRCGetSpellTargetLocation(); // missile spread centered around caster
    int nMissiles = nCasterLvl;

    nCasterLvl +=SPGetPenetr();

    if (nMissiles > nCap)
    {
        nMissiles = nCap;
    }

        /* New Algorithm
            1. Count # of targets
            2. Determine number of missiles
            3. First target gets a missile and all Excess missiles
            4. Rest of targets (max nMissiles) get one missile
       */
    int nEnemies = 0;

    oTarget = GetFirstObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_GARGANTUAN, lTarget, TRUE, OBJECT_TYPE_CREATURE);
    //Cycle through the targets within the spell shape until an invalid object is captured.
    while (GetIsObjectValid(oTarget) )
    {
        // * caster cannot be harmed by this spell
        if (spellsIsTarget(oTarget, SPELL_TARGET_SELECTIVEHOSTILE, OBJECT_SELF) && (oTarget != OBJECT_SELF))
        {
            // GZ: You can only fire missiles on visible targets
            // 1.69 change
            // If the firing object is a placeable (such as a projectile trap),
            // we skip the line of sight check as placeables can't "see" things.
            if ( ( GetObjectType(OBJECT_SELF) == OBJECT_TYPE_PLACEABLE ) ||
                GetObjectSeen(oTarget,OBJECT_SELF))
            {
                nEnemies++;
            }
        }
        oTarget = GetNextObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_GARGANTUAN, lTarget, TRUE, OBJECT_TYPE_CREATURE);
     }

     if (nEnemies == 0) return; // * Exit if no enemies to hit
     int nExtraMissiles = nMissiles / nEnemies;

     // April 2003
     // * if more enemies than missiles, need to make sure that at least
     // * one missile will hit each of the enemies
     if (nExtraMissiles <= 0)
     {
        nExtraMissiles = 1;
     }

     // by default the Remainder will be 0 (if more than enough enemies for all the missiles)
     int nRemainder = 0;

     if (nExtraMissiles >0)
        nRemainder = nMissiles % nEnemies;

     if (nEnemies > nMissiles)
        nEnemies = nMissiles;

    oTarget = GetFirstObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_GARGANTUAN, lTarget, TRUE, OBJECT_TYPE_CREATURE);
    //Cycle through the targets within the spell shape until an invalid object is captured.
    while (GetIsObjectValid(oTarget) && nCnt <= nEnemies)
    {
        // * caster cannot be harmed by this spell
        if (spellsIsTarget(oTarget, SPELL_TARGET_SELECTIVEHOSTILE, OBJECT_SELF) && (oTarget != OBJECT_SELF) && 
            (( GetObjectType(OBJECT_SELF) == OBJECT_TYPE_PLACEABLE ) || 
            (GetObjectSeen(oTarget,OBJECT_SELF))))
        {
                //Fire cast spell at event for the specified target
                SignalEvent(oTarget, EventSpellCastAt(OBJECT_SELF, nSpell));

                // * recalculate appropriate distances
                fDist = GetDistanceBetween(OBJECT_SELF, oTarget);
                fDelay = fDist/(3.0 * log(fDist) + 2.0);

                // Firebrand.
                // It means that once the target has taken damage this round from the
                // spell it won't take subsequent damage
                if (nONEHIT == TRUE)
                {
                    nExtraMissiles = 1;
                    nRemainder = 0;
                }

                int i = 0;
                //--------------------------------------------------------------
                // GZ: Moved SR check out of loop to have 1 check per target
                //     not one check per missile, which would rip spell mantels
                //     apart
                //--------------------------------------------------------------
                if (!PRCDoResistSpell(OBJECT_SELF, oTarget,nCasterLvl, fDelay))
                {
                    for (i=1; i <= nExtraMissiles + nRemainder; i++)
                    {
                        //Roll damage
                        int nDam = d6(nD6Dice);
                        //Enter Metamagic conditions
                        if ((nMetaMagic & METAMAGIC_MAXIMIZE))
                        {
                             nDam = nD6Dice*6;//Damage is at max
                        }
                        if ((nMetaMagic & METAMAGIC_EMPOWER))
                        {
                              nDam = nDam + nDam/2; //Damage/Healing is +50%
                        }
                        
                        // Acid Sheath adds +1 damage per die to acid descriptor spells
                        if (GetHasDescriptor(nSpell, DESCRIPTOR_ACID) && GetHasSpellEffect(SPELL_MESTILS_ACID_SHEATH, OBJECT_SELF))
                        	nDam += nD6Dice;
                        nDam += SpellDamagePerDice(OBJECT_SELF, nD6Dice);	

                        if(i == 1)
                        {
                            //nDam += ApplySpellBetrayalStrikeDamage(oTarget, OBJECT_SELF);
                            DelayCommand(fDelay, PRCBonusDamage(oTarget));
                        }

                        // Jan. 29, 2004 - Jonathan Epp
                        // Reflex save was not being calculated for Firebrand
                        if(nReflexSave)
                        {
                            if(nDAMAGETYPE == DAMAGE_TYPE_FIRE)
                                nDam = PRCGetReflexAdjustedDamage(nDam, oTarget, PRCGetSaveDC(oTarget, OBJECT_SELF), SAVING_THROW_TYPE_FIRE);
                            else if(nDAMAGETYPE == DAMAGE_TYPE_ELECTRICAL)
                                nDam = PRCGetReflexAdjustedDamage(nDam, oTarget, PRCGetSaveDC(oTarget, OBJECT_SELF), SAVING_THROW_TYPE_ELECTRICITY);
                            else if(nDAMAGETYPE == DAMAGE_TYPE_COLD)
                                nDam = PRCGetReflexAdjustedDamage(nDam, oTarget, PRCGetSaveDC(oTarget, OBJECT_SELF), SAVING_THROW_TYPE_COLD);
                            else if(nDAMAGETYPE == DAMAGE_TYPE_ACID)
                                nDam = PRCGetReflexAdjustedDamage(nDam, oTarget, PRCGetSaveDC(oTarget, OBJECT_SELF), SAVING_THROW_TYPE_ACID);
                            else if(nDAMAGETYPE == DAMAGE_TYPE_SONIC)
                                nDam = PRCGetReflexAdjustedDamage(nDam, oTarget, PRCGetSaveDC(oTarget, OBJECT_SELF), SAVING_THROW_TYPE_SONIC);
                        }

                        fTime = fDelay;
                        fDelay2 += 0.1;
                        fTime += fDelay2;

                        //Set damage effect
                        effect eDam = PRCEffectDamage(oTarget, nDam, nDAMAGETYPE);
                        //Apply the MIRV and damage effect
                        DelayCommand(fTime, ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eVis, oTarget));
                        DelayCommand(fDelay2, ApplyEffectToObject(DURATION_TYPE_INSTANT, eMissile, oTarget));
                        DelayCommand(fTime, ApplyEffectToObject(DURATION_TYPE_INSTANT, eDam, oTarget));
                    }
                } // for
                else
                {  // * apply a dummy visual effect
                 ApplyEffectToObject(DURATION_TYPE_INSTANT, eMissile, oTarget);
                }
                nCnt++;// * increment count of missiles fired
                nRemainder = 0;
        }
        oTarget = GetNextObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_GARGANTUAN, lTarget, TRUE, OBJECT_TYPE_CREATURE);
    }

}

// Test main
//void main(){}