• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[vJASS] (Yet another) Instancing Issue

Status
Not open for further replies.
Level 4
Joined
Sep 25, 2005
Messages
71
EDIT: Resolved. Apparently it's bad to RemoveLocation something you're going to use.

I've sort of coded myself into a weird position here. I tried using a correctly-functioning spell projectile framework and added on functionality (calls an aoe-based struct at the location of the projectile.)

The projectile framework worked fine, but the moment I added in if's for whether it's an aoe, or set it to aoe on death, something changed. I literally can't figure how this crap would affect my instancing.

[youtube]
http://www.youtube.com/watch?v=qx5AF8A3wyM
[/youtube]
JASS:
//==================================/ Projectile Framework /=================================================//
//                                                                                                           //
//                                                                                                           //
//                                                                                                           //
//===========================================================================================================//
scope CorrectFireballs initializer init

globals
//Number of missiles; must be 1 or greater
    private constant integer        MISSILE_NUMBER          = 3
//Which ability triggers the missiles
    private constant integer        ABIL_ID                 = 'A008'
//Which unit to use as a missile dummy
    private constant integer        DUMMY_ID                = 'o003'
//Which unit to use as a caster dummy if casts spell on hit units
    private constant integer        CASTER_DUMMY_ID         = 0
//Which ability do dummies cast
    private constant integer        CASTER_DUMMY_ABIL_ID    = 0
//Which buff to use for expiration timer on dummies
    private constant integer        DUMMY_EX_BUFF           = 'B001'
//If some missiles are hidden, sets interval for which are hidden; must be 1 or greater
    private constant integer        UNIT_HIDE_INTERVAL      = 1   
    
//Which FX to use on caster on cast
    private constant string         CAST_FX                 = null
//Which FX to use on dummy on cast
    private constant string         DUMMY_CAST_FX           = null
//Which FX to use when a target is hit
    private constant string         HIT_FX                  = null
//Which FX to use on the missile when killed
    private constant string         DEATH_MISSILE_FX        = null
//Which FX to use on the target when missile is killed
    private constant string         DEATH_ENEMY_FX          = null
//Which FX to use as a trail trail is enabled
    private constant string         TRAIL_FX                = "Abilities\\Spells\\Human\\FlakCannons\\FlakTarget.mdl"
//Which FX to use as lightning
    private constant string         LIGHTNING_FX            = null
//Which attach point to use for FX on targets
    private constant string         FX_ATTACH_POINT         = null
//Which attach point to use for FX on target when missile is killed
    private constant string         DEATH_FX_ATTACH_POINT   = null   
//Which order string to use for dummy casters
    private constant string         CASTER_DUMMY_ORDER      = null
    
//Projectile collision size
    private constant real           COLLISION_SIZE          = 128.
//Dummy expiration timer duration
    private constant real           DUMMY_EX_TIME           = 1.
//Colors dummy/lightning
    private constant real           FX_RED                  = 1.
    private constant real           FX_GREEN                = 1.
    private constant real           FX_BLUE                 = 1.
//Max distance
    private constant real           CAST_RANGE              = 650.
//Gained max distance per level
    private constant real           RANGE_PER_LEVEL         = 0.
//What distance percent necessary to start transparency
    private constant real           TRANSPARENCY_THRESH     = 1.
//How much transparency is added after threshold per tick
    private constant real           TRANSPARENCY_INCREMENT  = 0.1
//Angle between missiles
    private constant real           MISSILE_ANGLE           = 30.
//Reduction of angle between missiles per level
    private constant real           MISSILE_ANGLE_LVL_REDUC = 0.
//How far to move missiles per tick
    private constant real           MISSILE_MOVESPEED       = 33.
//Z height for missiles/lightning
    private constant real           FX_Z                    = 72.
//Damage dealt if damage is toggled
    private constant real           DAMAGE_AMOUNT           = 0.
//Damage dealt increase per level
    private constant real           DAMAGE_PER_LEVEL        = 0.
//Tick speed
	private constant real           TIMING_INTERVAL         = 0.02
//Sets the angle of the "initial" missile based off of missile number and angle
    private constant real           CAST_ANGLE_OFFSET       = ((MISSILE_NUMBER * MISSILE_ANGLE) * .5) + (.5 * MISSILE_ANGLE)
    
//Sets whether the missile kills trees
    private constant boolean        KILLS_TREES             = true
//Sets whether missile causes an AOE effect on contact
    private constant boolean        AOE                     = false //TBI
//Sets whether missile causes an AOE effect when reaching max distance or dying
    private constant boolean        AOE_ON_DEATH            = false //TBI
//Sets whether to use DUMMY_CAST_FX
    private constant boolean        HAS_DUMMY_CAST_FX       = false
//Sets whether to use TRAIL_FX
    private constant boolean        HAS_TRAIL_FX            = true
//Sets whether to use HIT_FX
    private constant boolean        FX_ON_COLLISION         = false
//Sets whether to use DEATH_MISSILE_FX when missile is killed
    private constant boolean        FX_ON_DEATH_MISSILE     = false
//Sets whether to use DEATH_ENEMY_FX on target when missile is killed
    private constant boolean        FX_ON_DEATH_ENEMY       = false
//Sets whether all missiles die when one collides with a valid target
    private constant boolean        DIES_ON_COLLISION       = false
//Sets whether individual missiles die when colliding with a valid target
    private constant boolean        INDIVID_DIE_ONHIT       = true
//Sets whether missiles are killed or removed
    private constant boolean        MISSILES_EXPLODE        = false
//Sets whether missiles deal damage on hit
    private constant boolean        DEALS_DAMAGE            = true
//Sets whether missiles create dummies that cast a spell on the target unit
    private constant boolean        CASTS_SPELL_ON_TARGET   = false
//Sets whether missiles create dummies that cast a spell on the point of the target unit
    private constant boolean        CASTS_SPELL_ON_POINT    = false
//Sets whether missiles create dummies that cast a spell without a target on the point of the target unit
    private constant boolean        CASTS_SPELL_NOTARGET    = false
//Sets whether the spell dummies cast levels with the triggering spell
    private constant boolean        SPELL_HAS_LEVELS        = false
//Sets whether the spell is targeted via point
    private constant boolean        IS_TARGETED             = true
//Sets whether the spell is targeted via unit
    private constant boolean        IS_UNIT_TARGETED        = false
//Sets whether the missiles have homing; only useful with IS_UNIT_TARGETED = true
    private constant boolean        HAS_HOMING              = false
//Sets whether some missiles are hidden
    private constant boolean        HIDING_UNITS            = false
//Sets whether missiles are hidden those every UNIT_HIDE_INTERVAL
    private constant boolean        HIDING_UNITS_INTERVAL   = false    
//Checks whether the missiles create a valid circle, allowing lightning FX to seal up properly
    private constant boolean        IS_CIRCLE               = MISSILE_ANGLE * MISSILE_NUMBER == 360.

	private timer CorrectFireballsTimer = CreateTimer()
	private integer CorrectFireballsInstances = 0
	private group CorrectFireballsEnumGroup = CreateGroup()
	private CorrectFireballsData array CorrectFireballsArray
endglobals

private function TreeFilter takes nothing returns boolean
    local integer d = GetDestructableTypeId(GetFilterDestructable())
    return d == 'ATtr' or d == 'BTtw' or d == 'KTtw' or d == 'YTft' or d == 'JTct' or d == 'YTst' or d == 'YTct' or d == 'YTwt' or d == 'JTwt' or d == 'JTwt' or d == 'FTtw' or d == 'CTtr' or d == 'ITtw' or d == 'NTtw' or d == 'OTtw' or d == 'ZTtw' or d == 'WTst' or d == 'LTlt' or d == 'GTsh' or d == 'Xtlt' or d == 'WTtw' or d == 'Attc' or d == 'BTtc' or d == 'CTtc' or d == 'ITtc' or d == 'NTtc' or d == 'ZTtc' /*
    */and GetDestructableLife(GetFilterDestructable()) > 0.405
endfunction

private function KillTree takes nothing returns nothing
	call KillDestructable(GetEnumDestructable())
endfunction

private function MoveUnit takes unit movingUnit, real speed, real angle returns nothing
        local real x
        local real y
        set x = GetUnitX(movingUnit) + speed * Cos(angle * bj_DEGTORAD)
        set y = GetUnitY(movingUnit) + speed * Sin(angle * bj_DEGTORAD)
        call SetUnitBoundedX(movingUnit, x)
        call SetUnitBoundedY(movingUnit, y)
endfunction

private function ValidAliveTargetGroupedGround takes unit enemy, unit caster, group notInGroup returns boolean
        if IsUnitEnemy(enemy, GetOwningPlayer(caster)) == true /*
        */and IsUnitType(enemy, UNIT_TYPE_GROUND) == true /*
        */and IsUnitType(enemy,UNIT_TYPE_MAGIC_IMMUNE) == false /*
        */and IsUnitType(enemy,UNIT_TYPE_STRUCTURE) == false /*
        */and IsUnitType(enemy, UNIT_TYPE_MECHANICAL) == false /*
        */and GetWidgetLife(enemy) > 0.405 == true /*
        */and IsUnitInGroup(enemy, notInGroup) == false then
            return true
        endif
        return false
endfunction

struct CorrectFireballsData

    boolean Missilesexplode
    unit caster
    unit targetedunit
    unit array Missiles[MISSILE_NUMBER]
    player casterOwner
    lightning array beams[MISSILE_NUMBER]
    integer casterLevel
    integer dummySpellLevel
    real distance
    real maxDist
    real transparency
    group CorrectFireballsTargets
   
    private static method CorrectFireballsExecution takes nothing returns nothing
        local boolexpr TreeCheck
        local integer i = 0
        local integer d
        local integer Missilecount = 0
        local location zLoc
        local unit enemy
        local unit casterDummy
        local effect collisionFX
        local effect trailFX
        local effect dummycastFX
        local real x
        local real y
        local real array z
    	local rect area  
        local CorrectFireballsData localCorrectFireballs
        
        loop
            exitwhen i >= CorrectFireballsInstances
            set localCorrectFireballs = CorrectFireballsArray[i]
    
            if localCorrectFireballs.distance < localCorrectFireballs.maxDist then
            if localCorrectFireballs.transparency > 0. and (localCorrectFireballs.distance / localCorrectFireballs.maxDist) > TRANSPARENCY_THRESH then
                set localCorrectFireballs.transparency = localCorrectFireballs.transparency - TRANSPARENCY_INCREMENT
            endif
            loop
                exitwhen Missilecount == MISSILE_NUMBER    
                    call MoveUnit(localCorrectFireballs.Missiles[Missilecount], MISSILE_MOVESPEED, GetUnitFacing(localCorrectFireballs.Missiles[Missilecount]))
                   if HAS_HOMING == true and IS_UNIT_TARGETED == true then
                        call SetUnitFacing(localCorrectFireballs.Missiles[Missilecount], /*
                        */bj_RADTODEG*Atan2( (GetUnitY(localCorrectFireballs.targetedunit) ) - (GetUnitY ( localCorrectFireballs.Missiles[Missilecount] ) ), /*
                        */(GetUnitX(localCorrectFireballs.targetedunit)-(GetUnitX(localCorrectFireballs.Missiles[Missilecount])))))                    
                    endif
                    set x = GetUnitX(localCorrectFireballs.Missiles[Missilecount])
                    set y = GetUnitY(localCorrectFireballs.Missiles[Missilecount])
                    set TreeCheck = Filter(function TreeFilter)
                    set area =  Rect(x - COLLISION_SIZE, y - COLLISION_SIZE, x + COLLISION_SIZE, y + COLLISION_SIZE)
                    set zLoc = Location(x, y)
                    set z[Missilecount] = FX_Z + GetLocationZ(zLoc)
                    call SetUnitFlyHeight(localCorrectFireballs.Missiles[Missilecount], FX_Z, 0.)
                    if HAS_TRAIL_FX == true then
                        set trailFX = AddSpecialEffect(TRAIL_FX, GetUnitX(localCorrectFireballs.Missiles[Missilecount]), GetUnitY(localCorrectFireballs.Missiles[Missilecount]))
                        call DestroyEffect(trailFX)
                    endif
                    if ModuloInteger(Missilecount, UNIT_HIDE_INTERVAL) == 0 and HIDING_UNITS == true then
                        call SetUnitVertexColor(localCorrectFireballs.Missiles[Missilecount], R2I(255. * FX_RED), R2I(255. * FX_GREEN), R2I(255. * FX_BLUE), 0)
                    else 
                        if ModuloInteger(Missilecount, UNIT_HIDE_INTERVAL) != 0 and HIDING_UNITS_INTERVAL == true then
                            call SetUnitVertexColor(localCorrectFireballs.Missiles[Missilecount], R2I(255. * FX_RED), R2I(255. * FX_GREEN), R2I(255. * FX_BLUE), 0)
                        else
                            call SetUnitVertexColor(localCorrectFireballs.Missiles[Missilecount], R2I(255. * FX_RED), R2I(255. * FX_GREEN), R2I(255. * FX_BLUE), R2I(localCorrectFireballs.transparency*255))   
                        endif
                    endif
                    if Missilecount != MISSILE_NUMBER then
                        call MoveLightningEx(localCorrectFireballs.beams[Missilecount], true, GetUnitX(localCorrectFireballs.Missiles[Missilecount-1]), GetUnitY(localCorrectFireballs.Missiles[Missilecount-1]), z[Missilecount-1], GetUnitX(localCorrectFireballs.Missiles[Missilecount]), GetUnitY(localCorrectFireballs.Missiles[Missilecount]), z[Missilecount])
                        if localCorrectFireballs.beams[0] != null then
                            call MoveLightningEx(localCorrectFireballs.beams[0], true, GetUnitX(localCorrectFireballs.Missiles[0]), GetUnitY(localCorrectFireballs.Missiles[0]), z[0], GetUnitX(localCorrectFireballs.Missiles[Missilecount]), GetUnitY(localCorrectFireballs.Missiles[Missilecount]), z[Missilecount])
                        endif
                    endif
                    call SetLightningColor(localCorrectFireballs.beams[Missilecount],FX_RED,FX_GREEN,FX_BLUE,localCorrectFireballs.transparency)
                    if KILLS_TREES == true then
                        call EnumDestructablesInRect(area, TreeCheck, function KillTree)
                    endif
                    call GroupEnumUnitsInRange(CorrectFireballsEnumGroup, GetUnitX(localCorrectFireballs.Missiles[Missilecount]), GetUnitY(localCorrectFireballs.Missiles[Missilecount]), COLLISION_SIZE, null)
                    loop
                        set enemy = FirstOfGroup(CorrectFireballsEnumGroup)
                        exitwhen enemy == null
                        if ValidAliveTargetGroupedGround(enemy, localCorrectFireballs.caster, localCorrectFireballs.CorrectFireballsTargets) == true then
                            call GroupAddUnit(localCorrectFireballs.CorrectFireballsTargets, enemy)
                            if DEALS_DAMAGE == true then
                                call UnitDamageTarget(localCorrectFireballs.caster, enemy, DAMAGE_AMOUNT + (DAMAGE_PER_LEVEL * localCorrectFireballs.casterLevel), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, null)
                            endif
                            if CASTS_SPELL_ON_TARGET == true then
                                set casterDummy = CreateUnit(localCorrectFireballs.casterOwner,CASTER_DUMMY_ID,/*
                                */GetUnitX(enemy),/*
                                */GetUnitY(enemy),/*
                                */bj_RADTODEG*Atan2(GetUnitY(enemy)-GetUnitY(localCorrectFireballs.Missiles[Missilecount]),GetUnitX(enemy)-GetUnitX(localCorrectFireballs.Missiles[Missilecount])))
                                call UnitAddAbility(casterDummy, CASTER_DUMMY_ABIL_ID)
                                if SPELL_HAS_LEVELS == true then
                                    call SetUnitAbilityLevel(casterDummy, CASTER_DUMMY_ABIL_ID, localCorrectFireballs.dummySpellLevel)
                                endif
                                call IssueTargetOrder(casterDummy, CASTER_DUMMY_ORDER, enemy)
                                if HAS_DUMMY_CAST_FX == true then
                                    set dummycastFX = AddSpecialEffect(DUMMY_CAST_FX, GetUnitX(casterDummy),GetUnitY(casterDummy) )
                                    call DestroyEffect(dummycastFX)
                                endif
                                call UnitApplyTimedLife(casterDummy, DUMMY_EX_BUFF, DUMMY_EX_TIME)
                            endif
                            if CASTS_SPELL_ON_POINT == true then
                                set casterDummy = CreateUnit(localCorrectFireballs.casterOwner,CASTER_DUMMY_ID,/*
                                */GetUnitX(enemy),/*
                                */GetUnitY(enemy),/*
                                */bj_RADTODEG*Atan2(GetUnitY(enemy)-GetUnitY(localCorrectFireballs.Missiles[Missilecount]),GetUnitX(enemy)-GetUnitX(localCorrectFireballs.Missiles[Missilecount])))
                                call UnitAddAbility(casterDummy, CASTER_DUMMY_ABIL_ID)
                                if SPELL_HAS_LEVELS == true then
                                    call SetUnitAbilityLevel(casterDummy, CASTER_DUMMY_ABIL_ID, localCorrectFireballs.dummySpellLevel)
                                endif
                                call IssuePointOrder(casterDummy, CASTER_DUMMY_ORDER, GetUnitX(enemy), GetUnitY(enemy))
                                if HAS_DUMMY_CAST_FX == true then
                                    set dummycastFX = AddSpecialEffect(DUMMY_CAST_FX, GetUnitX(casterDummy),GetUnitY(casterDummy) )
                                    call DestroyEffect(dummycastFX)
                                endif
                                call UnitApplyTimedLife(casterDummy, DUMMY_EX_BUFF, DUMMY_EX_TIME)
                            endif
                            if CASTS_SPELL_NOTARGET == true then
                                set casterDummy = CreateUnit(localCorrectFireballs.casterOwner,CASTER_DUMMY_ID,/*
                                */GetUnitX(enemy),/*
                                */GetUnitY(enemy),/*
                                */bj_RADTODEG*Atan2(GetUnitY(enemy)-GetUnitY(localCorrectFireballs.Missiles[Missilecount]),GetUnitX(enemy)-GetUnitX(localCorrectFireballs.Missiles[Missilecount])))
                                call UnitAddAbility(casterDummy, CASTER_DUMMY_ABIL_ID)
                                if SPELL_HAS_LEVELS == true then
                                    call SetUnitAbilityLevel(casterDummy, CASTER_DUMMY_ABIL_ID, localCorrectFireballs.dummySpellLevel)
                                endif
                                call IssueImmediateOrder(casterDummy, CASTER_DUMMY_ORDER)
                                if HAS_DUMMY_CAST_FX == true then
                                    set dummycastFX = AddSpecialEffect(DUMMY_CAST_FX, GetUnitX(casterDummy),GetUnitY(casterDummy) )
                                    call DestroyEffect(dummycastFX)
                                endif
                                call UnitApplyTimedLife(casterDummy, DUMMY_EX_BUFF, DUMMY_EX_TIME)
                            endif
                            if FX_ON_COLLISION == true then
                                set collisionFX = AddSpecialEffectTarget(HIT_FX, enemy, FX_ATTACH_POINT)
                                call DestroyEffect(collisionFX)
                            endif
                            if DIES_ON_COLLISION == true then
                                if FX_ON_DEATH_MISSILE == true then
                                    set collisionFX = AddSpecialEffect(DEATH_MISSILE_FX, GetUnitX(localCorrectFireballs.Missiles[Missilecount]),GetUnitY(localCorrectFireballs.Missiles[Missilecount]) )
                                    call DestroyEffect(collisionFX)
                                endif
                                if FX_ON_DEATH_ENEMY == true then
                                    set collisionFX = AddSpecialEffectTarget(DEATH_ENEMY_FX, enemy, DEATH_FX_ATTACH_POINT)
                                    call DestroyEffect(collisionFX)
                                endif
                                set localCorrectFireballs.distance = localCorrectFireballs.maxDist
                            endif
                            if INDIVID_DIE_ONHIT == true then
                                if FX_ON_DEATH_MISSILE == true then
                                    set collisionFX = AddSpecialEffect(DEATH_MISSILE_FX, GetUnitX(localCorrectFireballs.Missiles[Missilecount]),GetUnitY(localCorrectFireballs.Missiles[Missilecount]) )
                                    call DestroyEffect(collisionFX)
                                endif
                                if FX_ON_DEATH_ENEMY == true then
                                    set collisionFX = AddSpecialEffectTarget(DEATH_ENEMY_FX, enemy, DEATH_FX_ATTACH_POINT)
                                    call DestroyEffect(collisionFX)
                                endif
                                
                                if localCorrectFireballs.beams[Missilecount] != null then
                                    call DestroyLightning(localCorrectFireballs.beams[Missilecount])
                                    set localCorrectFireballs.beams[Missilecount] = null
                                endif
                                
                                if Missilecount + 1 <= MISSILE_NUMBER then
                                
                                    if localCorrectFireballs.beams[Missilecount+1] != null then
                                        call DestroyLightning(localCorrectFireballs.beams[Missilecount+1])
                                        set localCorrectFireballs.beams[Missilecount+1] = null
                                    endif
                                    
                                endif                                
                                if localCorrectFireballs.Missilesexplode == false then
                                    call RemoveUnit(localCorrectFireballs.Missiles[Missilecount])
                                endif
                                if localCorrectFireballs.Missilesexplode == true then
                                    call KillUnit(localCorrectFireballs.Missiles[Missilecount])
                                endif
                                set localCorrectFireballs.Missiles[Missilecount] = null
                            endif
                        endif
                        call GroupRemoveUnit(CorrectFireballsEnumGroup, enemy)
                    endloop
                                  
               set Missilecount = Missilecount + 1
            endloop
                set Missilecount = 0
                set localCorrectFireballs.distance = localCorrectFireballs.distance + MISSILE_MOVESPEED
            endif
            
            if localCorrectFireballs.distance >= localCorrectFireballs.maxDist then
                set CorrectFireballsInstances = CorrectFireballsInstances - 1
                set CorrectFireballsArray[i] = CorrectFireballsArray[CorrectFireballsInstances]
                call DestroyGroup(localCorrectFireballs.CorrectFireballsTargets)
                call localCorrectFireballs.destroy()
            endif	
            call RemoveRect(area)
            call DestroyBoolExpr(TreeCheck)
            set area = null
            set casterDummy = null
            set zLoc = null
            set trailFX = null
            set collisionFX = null
            set dummycastFX = null
            set i = i + 1
        endloop

        if CorrectFireballsInstances == 0 then
            call PauseTimer(CorrectFireballsTimer)
        endif
    endmethod
    
    static method create takes unit whichUnit, unit whichTarget, location whichPoint, integer level returns CorrectFireballsData
        local integer i = 0
        local CorrectFireballsData data = CorrectFireballsData.allocate()
        local effect casterFX
        local location targetLoc
        local real targetLocX
        local real targetLocY
        local real MissileFacing
        if IS_TARGETED == true then
            set targetLoc = whichPoint
            set targetLocX = GetLocationX(targetLoc)
            set targetLocY = GetLocationY(targetLoc)
            set MissileFacing = (bj_RADTODEG*Atan2(targetLocY-GetUnitY(whichUnit),targetLocX-GetUnitX(whichUnit))) - CAST_ANGLE_OFFSET
        endif
        if IS_TARGETED == false then
            set targetLoc = Location(GetUnitX(whichTarget), GetUnitY(whichTarget))
            set targetLocX = GetLocationX(targetLoc)
            set targetLocY = GetLocationY(targetLoc)
            set MissileFacing = (bj_RADTODEG*Atan2(targetLocY-GetUnitY(whichUnit),targetLocX-GetUnitX(whichUnit))) - CAST_ANGLE_OFFSET
        endif
        if IS_UNIT_TARGETED == true then
            set data.targetedunit = whichTarget
            set targetLoc = Location(GetUnitX(data.targetedunit), GetUnitY(data.targetedunit))
            set targetLocX = GetLocationX(targetLoc)
            set targetLocY = GetLocationY(targetLoc)
            set MissileFacing = (bj_RADTODEG*Atan2(targetLocY-GetUnitY(whichUnit),targetLocX-GetUnitX(whichUnit))) - CAST_ANGLE_OFFSET
        endif
        set data.CorrectFireballsTargets = CreateGroup()
        set data.caster =  whichUnit
        set data.casterLevel = level
        if SPELL_HAS_LEVELS == true then
            set data.dummySpellLevel = level
        endif
        set data.casterOwner = GetOwningPlayer(data.caster)
        set data.distance = 0.
        set data.maxDist = CAST_RANGE + (RANGE_PER_LEVEL * data.casterLevel)
        set data.transparency = 1.
        set data.Missilesexplode = MISSILES_EXPLODE
        
        set casterFX = AddSpecialEffect(CAST_FX, GetUnitX(whichUnit), GetUnitY(whichUnit))
        call DestroyEffect(casterFX)
        set casterFX = null     
        loop
            exitwhen i == MISSILE_NUMBER
            
            call SetUnitPathing(data.caster, false)
            set data.Missiles[i] = CreateUnit(data.casterOwner, DUMMY_ID, GetUnitX(data.caster), GetUnitY(data.caster), (MissileFacing))
            call SetUnitPathing(data.Missiles[i], false)
            call SetUnitX(data.Missiles[i], GetUnitX(data.caster))
            call SetUnitY(data.Missiles[i], GetUnitY(data.caster))
            call SetUnitPathing(data.caster, true)
            call UnitAddAbility(data.Missiles[i], 'Amrf')
            call UnitRemoveAbility(data.Missiles[i], 'Amrf')
            call SetUnitVertexColor(data.Missiles[i], 255, 255, 255, 255)
            set MissileFacing = MissileFacing+(MISSILE_ANGLE - (MISSILE_ANGLE_LVL_REDUC * data.casterLevel))
            call SetUnitFacing(data.Missiles[i], MissileFacing)
            if IS_CIRCLE == true then
                if data.beams[0] == null then
                    set data.beams[0] = AddLightningEx(LIGHTNING_FX, true, GetUnitX(data.Missiles[0]), GetUnitY(data.Missiles[0]), FX_Z, GetUnitX(data.Missiles[i]), GetUnitY(data.Missiles[i]), FX_Z)
                endif
            endif
            if i != 0 then
                set data.beams[i] = AddLightningEx(LIGHTNING_FX, true, GetUnitX(data.Missiles[i-1]), GetUnitY(data.Missiles[i-1]), FX_Z, GetUnitX(data.Missiles[i]), GetUnitY(data.Missiles[i]), FX_Z)
            endif
            
            set i = i + 1
        endloop
                
        set CorrectFireballsArray[CorrectFireballsInstances] = data        
        
    	if CorrectFireballsInstances <= 0 then
            call TimerStart(CorrectFireballsTimer, TIMING_INTERVAL, true, function CorrectFireballsData.CorrectFireballsExecution)
        endif
        
        set CorrectFireballsInstances = CorrectFireballsInstances + 1
        
        call RemoveLocation(targetLoc)
        set targetLoc = null
        
        return data
    endmethod
    
    method onDestroy takes nothing returns nothing
    local integer i = 0
    if .Missilesexplode == false then
    loop
        exitwhen i == MISSILE_NUMBER
        call RemoveUnit(.Missiles[i])
        set .Missiles[i] = null
        set i = i + 1
    endloop
    endif
    if .Missilesexplode == true then
    loop
        exitwhen i == MISSILE_NUMBER
        call KillUnit(.Missiles[i])
        set .Missiles[i] = null
        set i = i + 1
    endloop
    endif
    set i = 0
    loop
        exitwhen i == MISSILE_NUMBER
        if .beams[i] != null then
            call DestroyLightning(.beams[i])
            set .beams[i] = null
        endif
        set i = i + 1
    endloop
    set .caster = null
    set .targetedunit = null
    set .casterOwner = null
    set .CorrectFireballsTargets = null
    endmethod
    
endstruct

//===================================/ Condition /===========================================================

private function checkCorrectFireballs takes nothing returns boolean
    local CorrectFireballsData localCorrectFireballs
    local unit caster = GetTriggerUnit()
    local unit target = GetSpellTargetUnit()
    local location targetpoint = GetSpellTargetLoc()
	if GetSpellAbilityId() == ABIL_ID then
        set localCorrectFireballs = CorrectFireballsData.create(caster, target, targetpoint, GetUnitAbilityLevel(caster, ABIL_ID))
	endif
    call RemoveLocation(targetpoint)
    set caster = null
    set target = null
    set targetpoint = null
	return false
endfunction

//===================================/ Init /================================================================

private function init takes nothing returns nothing
	local trigger localTrigVar = CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(localTrigVar, EVENT_PLAYER_UNIT_SPELL_EFFECT)
	call TriggerAddCondition( localTrigVar, Condition(function checkCorrectFireballs))
	set localTrigVar = null
endfunction

endscope

JASS:
//==================================/ Projectile Framework /=================================================//
//                                                                                                           //
//                                                                                                           //
//                                                                                                           //
//===========================================================================================================//
scope IncorrectFireballs initializer init

globals
//Number of missiles; must be 1 or greater
    private constant integer        MISSILE_NUMBER          = 3
//Which ability triggers the missiles
    private constant integer        ABIL_ID                 = 'A008'
//Which unit to use as a missile dummy
    private constant integer        DUMMY_ID                = 'o003'
//Which unit to use as a caster dummy if casts spell on hit units
    private constant integer        CASTER_DUMMY_ID         = 0
//Which ability do dummies cast
    private constant integer        CASTER_DUMMY_ABIL_ID    = 0
//Which buff to use for expiration timer on dummies
    private constant integer        DUMMY_EX_BUFF           = 'B001'
//If some missiles are hidden, sets interval for which are hidden; must be 1 or greater
    private constant integer        UNIT_HIDE_INTERVAL      = 1   
    
//Which FX to use on caster on cast
    private constant string         CAST_FX                 = null
//Which FX to use on dummy on cast
    private constant string         DUMMY_CAST_FX           = null
//Which FX to use when a target is hit
    private constant string         HIT_FX                  = null
//Which FX to use on the missile when killed
    private constant string         DEATH_MISSILE_FX        = null
//Which FX to use on the target when missile is killed
    private constant string         DEATH_ENEMY_FX          = null
//Which FX to use as a trail trail is enabled
    private constant string         TRAIL_FX                = "Abilities\\Spells\\Human\\FlakCannons\\FlakTarget.mdl"
//Which FX to use as lightning
    private constant string         LIGHTNING_FX            = null
//Which attach point to use for FX on targets
    private constant string         FX_ATTACH_POINT         = null
//Which attach point to use for FX on target when missile is killed
    private constant string         DEATH_FX_ATTACH_POINT   = null   
//Which order string to use for dummy casters
    private constant string         CASTER_DUMMY_ORDER      = null
    
//Projectile collision size
    private constant real           COLLISION_SIZE          = 128.
//Dummy expiration timer duration
    private constant real           DUMMY_EX_TIME           = 1.
//Colors dummy/lightning
    private constant real           FX_RED                  = 1.
    private constant real           FX_GREEN                = 1.
    private constant real           FX_BLUE                 = 1.
//Max distance
    private constant real           CAST_RANGE              = 650.
//Gained max distance per level
    private constant real           RANGE_PER_LEVEL         = 0.
//What distance percent necessary to start transparency
    private constant real           TRANSPARENCY_THRESH     = 1.
//How much transparency is added after threshold per tick
    private constant real           TRANSPARENCY_INCREMENT  = 0.1
//Angle between missiles
    private constant real           MISSILE_ANGLE           = 30.
//Reduction of angle between missiles per level
    private constant real           MISSILE_ANGLE_LVL_REDUC = 0.
//How far to move missiles per tick
    private constant real           MISSILE_MOVESPEED       = 33.
//Z height for missiles/lightning
    private constant real           FX_Z                    = 72.
//Damage dealt if damage is toggled
    private constant real           DAMAGE_AMOUNT           = 0.
//Damage dealt increase per level
    private constant real           DAMAGE_PER_LEVEL        = 0.
//Tick speed
	private constant real           TIMING_INTERVAL         = 0.02
//Sets the angle of the "initial" missile based off of missile number and angle
    private constant real           CAST_ANGLE_OFFSET       = ((MISSILE_NUMBER * MISSILE_ANGLE) * .5) + (.5 * MISSILE_ANGLE)
    
//Sets whether the missile kills trees
    private constant boolean        KILLS_TREES             = true
//Sets whether missile causes an AOE effect on contact
    private constant boolean        AOE                     = false //TBI
//Sets whether missile causes an AOE effect when reaching max distance or dying
    private constant boolean        AOE_ON_DEATH            = true //TBI
//Sets whether to use DUMMY_CAST_FX
    private constant boolean        HAS_DUMMY_CAST_FX       = false
//Sets whether to use TRAIL_FX
    private constant boolean        HAS_TRAIL_FX            = true
//Sets whether to use HIT_FX
    private constant boolean        FX_ON_COLLISION         = false
//Sets whether to use DEATH_MISSILE_FX when missile is killed
    private constant boolean        FX_ON_DEATH_MISSILE     = false
//Sets whether to use DEATH_ENEMY_FX on target when missile is killed
    private constant boolean        FX_ON_DEATH_ENEMY       = false
//Sets whether all missiles die when one collides with a valid target
    private constant boolean        DIES_ON_COLLISION       = false
//Sets whether individual missiles die when colliding with a valid target
    private constant boolean        INDIVID_DIE_ONHIT       = true
//Sets whether missiles are killed or removed
    private constant boolean        MISSILES_EXPLODE        = false
//Sets whether missiles deal damage on hit
    private constant boolean        DEALS_DAMAGE            = true
//Sets whether missiles create dummies that cast a spell on the target unit
    private constant boolean        CASTS_SPELL_ON_TARGET   = false
//Sets whether missiles create dummies that cast a spell on the point of the target unit
    private constant boolean        CASTS_SPELL_ON_POINT    = false
//Sets whether missiles create dummies that cast a spell without a target on the point of the target unit
    private constant boolean        CASTS_SPELL_NOTARGET    = false
//Sets whether the spell dummies cast levels with the triggering spell
    private constant boolean        SPELL_HAS_LEVELS        = false
//Sets whether the spell is targeted via point
    private constant boolean        IS_TARGETED             = true
//Sets whether the spell is targeted via unit
    private constant boolean        IS_UNIT_TARGETED        = false
//Sets whether the missiles have homing; only useful with IS_UNIT_TARGETED = true
    private constant boolean        HAS_HOMING              = false
//Sets whether some missiles are hidden
    private constant boolean        HIDING_UNITS            = false
//Sets whether missiles are hidden those every UNIT_HIDE_INTERVAL
    private constant boolean        HIDING_UNITS_INTERVAL   = false    
//Checks whether the missiles create a valid circle, allowing lightning FX to seal up properly
    private constant boolean        IS_CIRCLE               = MISSILE_ANGLE * MISSILE_NUMBER == 360.

	private timer IncorrectFireballsTimer = CreateTimer()
	private integer IncorrectFireballsInstances = 0
	private group IncorrectFireballsEnumGroup = CreateGroup()
	private IncorrectFireballsData array IncorrectFireballsArray
endglobals

private function TreeFilter takes nothing returns boolean
    local integer d = GetDestructableTypeId(GetFilterDestructable())
    return d == 'ATtr' or d == 'BTtw' or d == 'KTtw' or d == 'YTft' or d == 'JTct' or d == 'YTst' or d == 'YTct' or d == 'YTwt' or d == 'JTwt' or d == 'JTwt' or d == 'FTtw' or d == 'CTtr' or d == 'ITtw' or d == 'NTtw' or d == 'OTtw' or d == 'ZTtw' or d == 'WTst' or d == 'LTlt' or d == 'GTsh' or d == 'Xtlt' or d == 'WTtw' or d == 'Attc' or d == 'BTtc' or d == 'CTtc' or d == 'ITtc' or d == 'NTtc' or d == 'ZTtc' /*
    */and GetDestructableLife(GetFilterDestructable()) > 0.405
endfunction

private function KillTree takes nothing returns nothing
	call KillDestructable(GetEnumDestructable())
endfunction

private function MoveUnit takes unit movingUnit, real speed, real angle returns nothing
        local real x
        local real y
        set x = GetUnitX(movingUnit) + speed * Cos(angle * bj_DEGTORAD)
        set y = GetUnitY(movingUnit) + speed * Sin(angle * bj_DEGTORAD)
        call SetUnitBoundedX(movingUnit, x)
        call SetUnitBoundedY(movingUnit, y)
endfunction

private function ValidAliveTargetGroupedGround takes unit enemy, unit caster, group notInGroup returns boolean
        if IsUnitEnemy(enemy, GetOwningPlayer(caster)) == true /*
        */and IsUnitType(enemy, UNIT_TYPE_GROUND) == true /*
        */and IsUnitType(enemy,UNIT_TYPE_MAGIC_IMMUNE) == false /*
        */and IsUnitType(enemy,UNIT_TYPE_STRUCTURE) == false /*
        */and IsUnitType(enemy, UNIT_TYPE_MECHANICAL) == false /*
        */and GetWidgetLife(enemy) > 0.405 == true /*
        */and IsUnitInGroup(enemy, notInGroup) == false then
            return true
        endif
        return false
endfunction

struct IncorrectFireballsData

    boolean Missilesexplode
    unit caster
    unit targetedunit
    unit array Missiles[MISSILE_NUMBER]
    player casterOwner
    lightning array beams[MISSILE_NUMBER]
    integer casterLevel
    integer dummySpellLevel
    real distance
    real maxDist
    real transparency
    group IncorrectFireballsTargets
   
    private static method IncorrectFireballsExecution takes nothing returns nothing
        local boolexpr TreeCheck
        local integer i = 0
        local integer d
        local integer Missilecount = 0
        local location zLoc
        local location aoeloc
        local unit enemy
        local unit casterDummy
        local effect collisionFX
        local effect trailFX
        local effect dummycastFX
        local real x
        local real y
        local real array z
    	local rect area  
        local IncorrectFireballsData localIncorrectFireballs
        local FireblastData localFireblast
        
        loop
            exitwhen i >= IncorrectFireballsInstances
            set localIncorrectFireballs = IncorrectFireballsArray[i]
    
            if localIncorrectFireballs.distance < localIncorrectFireballs.maxDist then
            if localIncorrectFireballs.transparency > 0. and (localIncorrectFireballs.distance / localIncorrectFireballs.maxDist) > TRANSPARENCY_THRESH then
                set localIncorrectFireballs.transparency = localIncorrectFireballs.transparency - TRANSPARENCY_INCREMENT
            endif
            loop
                exitwhen Missilecount == MISSILE_NUMBER    
                    call MoveUnit(localIncorrectFireballs.Missiles[Missilecount], MISSILE_MOVESPEED, GetUnitFacing(localIncorrectFireballs.Missiles[Missilecount]))
                   if HAS_HOMING == true and IS_UNIT_TARGETED == true then
                        call SetUnitFacing(localIncorrectFireballs.Missiles[Missilecount], /*
                        */bj_RADTODEG*Atan2( (GetUnitY(localIncorrectFireballs.targetedunit) ) - (GetUnitY ( localIncorrectFireballs.Missiles[Missilecount] ) ), /*
                        */(GetUnitX(localIncorrectFireballs.targetedunit)-(GetUnitX(localIncorrectFireballs.Missiles[Missilecount])))))                    
                    endif
                    set x = GetUnitX(localIncorrectFireballs.Missiles[Missilecount])
                    set y = GetUnitY(localIncorrectFireballs.Missiles[Missilecount])
                    set TreeCheck = Filter(function TreeFilter)
                    set area =  Rect(x - COLLISION_SIZE, y - COLLISION_SIZE, x + COLLISION_SIZE, y + COLLISION_SIZE)
                    set zLoc = Location(x, y)
                    set z[Missilecount] = FX_Z + GetLocationZ(zLoc)
                    call SetUnitFlyHeight(localIncorrectFireballs.Missiles[Missilecount], FX_Z, 0.)
                    if HAS_TRAIL_FX == true then
                        set trailFX = AddSpecialEffect(TRAIL_FX, GetUnitX(localIncorrectFireballs.Missiles[Missilecount]), GetUnitY(localIncorrectFireballs.Missiles[Missilecount]))
                        call DestroyEffect(trailFX)
                    endif
                    if ModuloInteger(Missilecount, UNIT_HIDE_INTERVAL) == 0 and HIDING_UNITS == true then
                        call SetUnitVertexColor(localIncorrectFireballs.Missiles[Missilecount], R2I(255. * FX_RED), R2I(255. * FX_GREEN), R2I(255. * FX_BLUE), 0)
                    else 
                        if ModuloInteger(Missilecount, UNIT_HIDE_INTERVAL) != 0 and HIDING_UNITS_INTERVAL == true then
                            call SetUnitVertexColor(localIncorrectFireballs.Missiles[Missilecount], R2I(255. * FX_RED), R2I(255. * FX_GREEN), R2I(255. * FX_BLUE), 0)
                        else
                            call SetUnitVertexColor(localIncorrectFireballs.Missiles[Missilecount], R2I(255. * FX_RED), R2I(255. * FX_GREEN), R2I(255. * FX_BLUE), R2I(localIncorrectFireballs.transparency*255))   
                        endif
                    endif
                    if Missilecount != MISSILE_NUMBER then
                        call MoveLightningEx(localIncorrectFireballs.beams[Missilecount], true, GetUnitX(localIncorrectFireballs.Missiles[Missilecount-1]), GetUnitY(localIncorrectFireballs.Missiles[Missilecount-1]), z[Missilecount-1], GetUnitX(localIncorrectFireballs.Missiles[Missilecount]), GetUnitY(localIncorrectFireballs.Missiles[Missilecount]), z[Missilecount])
                        if localIncorrectFireballs.beams[0] != null then
                            call MoveLightningEx(localIncorrectFireballs.beams[0], true, GetUnitX(localIncorrectFireballs.Missiles[0]), GetUnitY(localIncorrectFireballs.Missiles[0]), z[0], GetUnitX(localIncorrectFireballs.Missiles[Missilecount]), GetUnitY(localIncorrectFireballs.Missiles[Missilecount]), z[Missilecount])
                        endif
                    endif
                    call SetLightningColor(localIncorrectFireballs.beams[Missilecount],FX_RED,FX_GREEN,FX_BLUE,localIncorrectFireballs.transparency)
                    if KILLS_TREES == true then
                        call EnumDestructablesInRect(area, TreeCheck, function KillTree)
                    endif
                    call GroupEnumUnitsInRange(IncorrectFireballsEnumGroup, GetUnitX(localIncorrectFireballs.Missiles[Missilecount]), GetUnitY(localIncorrectFireballs.Missiles[Missilecount]), COLLISION_SIZE, null)
                    loop
                        set enemy = FirstOfGroup(IncorrectFireballsEnumGroup)
                        exitwhen enemy == null
                        if ValidGroundTarget(enemy, localIncorrectFireballs.caster) == true then
                            call GroupAddUnit(localIncorrectFireballs.IncorrectFireballsTargets, enemy)
                            if DEALS_DAMAGE == true then
                                call UnitDamageTarget(localIncorrectFireballs.caster, enemy, DAMAGE_AMOUNT + (DAMAGE_PER_LEVEL * localIncorrectFireballs.casterLevel), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, null)
                            endif
                            if AOE == true then
                                set aoeloc = Location(GetUnitX(localIncorrectFireballs.Missiles[Missilecount]), GetUnitY(localIncorrectFireballs.Missiles[Missilecount]))
                                set localFireblast = FireblastData.create(localIncorrectFireballs.caster, aoeloc, GetUnitAbilityLevel(localIncorrectFireballs.caster, ABIL_ID) )
                            endif
                            if CASTS_SPELL_ON_TARGET == true then
                                set casterDummy = CreateUnit(localIncorrectFireballs.casterOwner,CASTER_DUMMY_ID,/*
                                */GetUnitX(enemy),/*
                                */GetUnitY(enemy),/*
                                */bj_RADTODEG*Atan2(GetUnitY(enemy)-GetUnitY(localIncorrectFireballs.Missiles[Missilecount]),GetUnitX(enemy)-GetUnitX(localIncorrectFireballs.Missiles[Missilecount])))
                                call UnitAddAbility(casterDummy, CASTER_DUMMY_ABIL_ID)
                                if SPELL_HAS_LEVELS == true then
                                    call SetUnitAbilityLevel(casterDummy, CASTER_DUMMY_ABIL_ID, localIncorrectFireballs.dummySpellLevel)
                                endif
                                call IssueTargetOrder(casterDummy, CASTER_DUMMY_ORDER, enemy)
                                if HAS_DUMMY_CAST_FX == true then
                                    set dummycastFX = AddSpecialEffect(DUMMY_CAST_FX, GetUnitX(casterDummy),GetUnitY(casterDummy) )
                                    call DestroyEffect(dummycastFX)
                                endif
                                call UnitApplyTimedLife(casterDummy, DUMMY_EX_BUFF, DUMMY_EX_TIME)
                            endif
                            if CASTS_SPELL_ON_POINT == true then
                                set casterDummy = CreateUnit(localIncorrectFireballs.casterOwner,CASTER_DUMMY_ID,/*
                                */GetUnitX(enemy),/*
                                */GetUnitY(enemy),/*
                                */bj_RADTODEG*Atan2(GetUnitY(enemy)-GetUnitY(localIncorrectFireballs.Missiles[Missilecount]),GetUnitX(enemy)-GetUnitX(localIncorrectFireballs.Missiles[Missilecount])))
                                call UnitAddAbility(casterDummy, CASTER_DUMMY_ABIL_ID)
                                if SPELL_HAS_LEVELS == true then
                                    call SetUnitAbilityLevel(casterDummy, CASTER_DUMMY_ABIL_ID, localIncorrectFireballs.dummySpellLevel)
                                endif
                                call IssuePointOrder(casterDummy, CASTER_DUMMY_ORDER, GetUnitX(enemy), GetUnitY(enemy))
                                if HAS_DUMMY_CAST_FX == true then
                                    set dummycastFX = AddSpecialEffect(DUMMY_CAST_FX, GetUnitX(casterDummy),GetUnitY(casterDummy) )
                                    call DestroyEffect(dummycastFX)
                                endif
                                call UnitApplyTimedLife(casterDummy, DUMMY_EX_BUFF, DUMMY_EX_TIME)
                            endif
                            if CASTS_SPELL_NOTARGET == true then
                                set casterDummy = CreateUnit(localIncorrectFireballs.casterOwner,CASTER_DUMMY_ID,/*
                                */GetUnitX(enemy),/*
                                */GetUnitY(enemy),/*
                                */bj_RADTODEG*Atan2(GetUnitY(enemy)-GetUnitY(localIncorrectFireballs.Missiles[Missilecount]),GetUnitX(enemy)-GetUnitX(localIncorrectFireballs.Missiles[Missilecount])))
                                call UnitAddAbility(casterDummy, CASTER_DUMMY_ABIL_ID)
                                if SPELL_HAS_LEVELS == true then
                                    call SetUnitAbilityLevel(casterDummy, CASTER_DUMMY_ABIL_ID, localIncorrectFireballs.dummySpellLevel)
                                endif
                                call IssueImmediateOrder(casterDummy, CASTER_DUMMY_ORDER)
                                if HAS_DUMMY_CAST_FX == true then
                                    set dummycastFX = AddSpecialEffect(DUMMY_CAST_FX, GetUnitX(casterDummy),GetUnitY(casterDummy) )
                                    call DestroyEffect(dummycastFX)
                                endif
                                call UnitApplyTimedLife(casterDummy, DUMMY_EX_BUFF, DUMMY_EX_TIME)
                            endif
                            if FX_ON_COLLISION == true then
                                set collisionFX = AddSpecialEffectTarget(HIT_FX, enemy, FX_ATTACH_POINT)
                                call DestroyEffect(collisionFX)
                            endif
                            if DIES_ON_COLLISION == true then
                                if FX_ON_DEATH_MISSILE == true then
                                    set collisionFX = AddSpecialEffect(DEATH_MISSILE_FX, GetUnitX(localIncorrectFireballs.Missiles[Missilecount]),GetUnitY(localIncorrectFireballs.Missiles[Missilecount]) )
                                    call DestroyEffect(collisionFX)
                                endif
                                if FX_ON_DEATH_ENEMY == true then
                                    set collisionFX = AddSpecialEffectTarget(DEATH_ENEMY_FX, enemy, DEATH_FX_ATTACH_POINT)
                                    call DestroyEffect(collisionFX)
                                endif
                                set localIncorrectFireballs.distance = localIncorrectFireballs.maxDist
                            endif
                            if INDIVID_DIE_ONHIT == true then
                                if FX_ON_DEATH_MISSILE == true then
                                    set collisionFX = AddSpecialEffect(DEATH_MISSILE_FX, GetUnitX(localIncorrectFireballs.Missiles[Missilecount]),GetUnitY(localIncorrectFireballs.Missiles[Missilecount]) )
                                    call DestroyEffect(collisionFX)
                                endif
                                if FX_ON_DEATH_ENEMY == true then
                                    set collisionFX = AddSpecialEffectTarget(DEATH_ENEMY_FX, enemy, DEATH_FX_ATTACH_POINT)
                                    call DestroyEffect(collisionFX)
                                endif
                                
                                if localIncorrectFireballs.beams[Missilecount] != null then
                                    call DestroyLightning(localIncorrectFireballs.beams[Missilecount])
                                    set localIncorrectFireballs.beams[Missilecount] = null
                                endif
                                
                                if Missilecount + 1 <= MISSILE_NUMBER then
                                
                                    if localIncorrectFireballs.beams[Missilecount+1] != null then
                                        call DestroyLightning(localIncorrectFireballs.beams[Missilecount+1])
                                        set localIncorrectFireballs.beams[Missilecount+1] = null
                                    endif
                                    
                                endif                                
                                if localIncorrectFireballs.Missilesexplode == false then
                                    if AOE_ON_DEATH == true then
                                        set aoeloc = Location(GetUnitX(localIncorrectFireballs.Missiles[Missilecount]), GetUnitY(localIncorrectFireballs.Missiles[Missilecount]))
                                        set localFireblast = FireblastData.create(localIncorrectFireballs.caster, aoeloc, GetUnitAbilityLevel(localIncorrectFireballs.caster, ABIL_ID) )
                                    endif
                                    call RemoveUnit(localIncorrectFireballs.Missiles[Missilecount])
                                endif
                                if localIncorrectFireballs.Missilesexplode == true then
                                    if AOE_ON_DEATH == true then
                                        set aoeloc = Location(GetUnitX(localIncorrectFireballs.Missiles[Missilecount]), GetUnitY(localIncorrectFireballs.Missiles[Missilecount]))
                                        set localFireblast = FireblastData.create(localIncorrectFireballs.caster, aoeloc, GetUnitAbilityLevel(localIncorrectFireballs.caster, ABIL_ID) )
                                    endif
                                    call KillUnit(localIncorrectFireballs.Missiles[Missilecount])
                                endif
                                set localIncorrectFireballs.Missiles[Missilecount] = null
                            endif
                        endif
                        call GroupRemoveUnit(IncorrectFireballsEnumGroup, enemy)
                    endloop
                                  
               set Missilecount = Missilecount + 1
            endloop
                set Missilecount = 0
                set localIncorrectFireballs.distance = localIncorrectFireballs.distance + MISSILE_MOVESPEED
            endif
            
            if localIncorrectFireballs.distance >= localIncorrectFireballs.maxDist then
                set IncorrectFireballsInstances = IncorrectFireballsInstances - 1
                set IncorrectFireballsArray[i] = IncorrectFireballsArray[IncorrectFireballsInstances]
                call DestroyGroup(localIncorrectFireballs.IncorrectFireballsTargets)
                call localIncorrectFireballs.destroy()
            endif	
            call RemoveRect(area)
            call DestroyBoolExpr(TreeCheck)
            call RemoveLocation(aoeloc)
            set area = null
            set casterDummy = null
            set zLoc = null
            set aoeloc = null
            set trailFX = null
            set collisionFX = null
            set dummycastFX = null
            set i = i + 1
        endloop

        if IncorrectFireballsInstances == 0 then
            call PauseTimer(IncorrectFireballsTimer)
        endif
    endmethod
    
    static method create takes unit whichUnit, unit whichTarget, location whichPoint, integer level returns IncorrectFireballsData
        local integer i = 0
        local IncorrectFireballsData data = IncorrectFireballsData.allocate()
        local effect casterFX
        local location targetLoc
        local real targetLocX
        local real targetLocY
        local real MissileFacing
        if IS_TARGETED == true then
            set targetLoc = whichPoint
            set targetLocX = GetLocationX(targetLoc)
            set targetLocY = GetLocationY(targetLoc)
            set MissileFacing = (bj_RADTODEG*Atan2(targetLocY-GetUnitY(whichUnit),targetLocX-GetUnitX(whichUnit))) - CAST_ANGLE_OFFSET
        endif
        if IS_TARGETED == false then
            set targetLoc = Location(GetUnitX(whichTarget), GetUnitY(whichTarget))
            set targetLocX = GetLocationX(targetLoc)
            set targetLocY = GetLocationY(targetLoc)
            set MissileFacing = (bj_RADTODEG*Atan2(targetLocY-GetUnitY(whichUnit),targetLocX-GetUnitX(whichUnit))) - CAST_ANGLE_OFFSET
        endif
        if IS_UNIT_TARGETED == true then
            set data.targetedunit = whichTarget
            set targetLoc = Location(GetUnitX(data.targetedunit), GetUnitY(data.targetedunit))
            set targetLocX = GetLocationX(targetLoc)
            set targetLocY = GetLocationY(targetLoc)
            set MissileFacing = (bj_RADTODEG*Atan2(targetLocY-GetUnitY(whichUnit),targetLocX-GetUnitX(whichUnit))) - CAST_ANGLE_OFFSET
        endif
        set data.IncorrectFireballsTargets = CreateGroup()
        set data.caster =  whichUnit
        set data.casterLevel = level
        if SPELL_HAS_LEVELS == true then
            set data.dummySpellLevel = level
        endif
        set data.casterOwner = GetOwningPlayer(data.caster)
        set data.distance = 0.
        set data.maxDist = CAST_RANGE + (RANGE_PER_LEVEL * data.casterLevel)
        set data.transparency = 1.
        set data.Missilesexplode = MISSILES_EXPLODE
        
        set casterFX = AddSpecialEffect(CAST_FX, GetUnitX(whichUnit), GetUnitY(whichUnit))
        call DestroyEffect(casterFX)
        set casterFX = null     
        loop
            exitwhen i == MISSILE_NUMBER
            
            call SetUnitPathing(data.caster, false)
            set data.Missiles[i] = CreateUnit(data.casterOwner, DUMMY_ID, GetUnitX(data.caster), GetUnitY(data.caster), (MissileFacing))
            call SetUnitPathing(data.Missiles[i], false)
            call SetUnitX(data.Missiles[i], GetUnitX(data.caster))
            call SetUnitY(data.Missiles[i], GetUnitY(data.caster))
            call SetUnitPathing(data.caster, true)
            call UnitAddAbility(data.Missiles[i], 'Amrf')
            call UnitRemoveAbility(data.Missiles[i], 'Amrf')
            call SetUnitVertexColor(data.Missiles[i], 255, 255, 255, 255)
            set MissileFacing = MissileFacing+(MISSILE_ANGLE - (MISSILE_ANGLE_LVL_REDUC * data.casterLevel))
            call SetUnitFacing(data.Missiles[i], MissileFacing)
            if IS_CIRCLE == true then
                if data.beams[0] == null then
                    set data.beams[0] = AddLightningEx(LIGHTNING_FX, true, GetUnitX(data.Missiles[0]), GetUnitY(data.Missiles[0]), FX_Z, GetUnitX(data.Missiles[i]), GetUnitY(data.Missiles[i]), FX_Z)
                endif
            endif
            if i != 0 then
                set data.beams[i] = AddLightningEx(LIGHTNING_FX, true, GetUnitX(data.Missiles[i-1]), GetUnitY(data.Missiles[i-1]), FX_Z, GetUnitX(data.Missiles[i]), GetUnitY(data.Missiles[i]), FX_Z)
            endif
            
            set i = i + 1
        endloop
                
        set IncorrectFireballsArray[IncorrectFireballsInstances] = data        
        
    	if IncorrectFireballsInstances <= 0 then
            call TimerStart(IncorrectFireballsTimer, TIMING_INTERVAL, true, function IncorrectFireballsData.IncorrectFireballsExecution)
        endif
        
        set IncorrectFireballsInstances = IncorrectFireballsInstances + 1
        
        call RemoveLocation(targetLoc)
        set targetLoc = null
        
        return data
    endmethod
    
    method onDestroy takes nothing returns nothing
    local integer i = 0
    local location aoeloc
    local FireblastData localFireblast
    if .Missilesexplode == false then
    loop
        exitwhen i == MISSILE_NUMBER
        if AOE_ON_DEATH == true then
            set aoeloc = Location(GetUnitX(.Missiles[i]), GetUnitY(.Missiles[i]))
            set localFireblast = FireblastData.create(.caster, aoeloc, GetUnitAbilityLevel(.caster, ABIL_ID) )
        endif
        call RemoveUnit(.Missiles[i])
        set .Missiles[i] = null
        set i = i + 1
    endloop
    endif
    if .Missilesexplode == true then
    loop
        exitwhen i == MISSILE_NUMBER
        if AOE_ON_DEATH == true then
            set aoeloc = Location(GetUnitX(.Missiles[i]), GetUnitY(.Missiles[i]))
            set localFireblast = FireblastData.create(.caster, aoeloc, GetUnitAbilityLevel(.caster, ABIL_ID) )
        endif
        call KillUnit(.Missiles[i])
        set .Missiles[i] = null
        set i = i + 1
    endloop
    endif
    set i = 0
    loop
        exitwhen i == MISSILE_NUMBER
        if .beams[i] != null then
            call DestroyLightning(.beams[i])
            set .beams[i] = null
        endif
        set i = i + 1
    endloop
    call RemoveLocation(aoeloc)
    set aoeloc = null
    set .caster = null
    set .targetedunit = null
    set .casterOwner = null
    set .IncorrectFireballsTargets = null
    endmethod
    
endstruct

//===================================/ Condition /===========================================================

private function checkIncorrectFireballs takes nothing returns boolean
    local IncorrectFireballsData localIncorrectFireballs
    local unit caster = GetTriggerUnit()
    local unit target = GetSpellTargetUnit()
    local location targetpoint = GetSpellTargetLoc()
	if GetSpellAbilityId() == ABIL_ID then
        set localIncorrectFireballs = IncorrectFireballsData.create(caster, target, targetpoint, GetUnitAbilityLevel(caster, ABIL_ID))
	endif
    call RemoveLocation(targetpoint)
    set caster = null
    set target = null
    set targetpoint = null
	return false
endfunction

//===================================/ Init /================================================================

private function init takes nothing returns nothing
	local trigger localTrigVar = CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(localTrigVar, EVENT_PLAYER_UNIT_SPELL_EFFECT)
	call TriggerAddCondition( localTrigVar, Condition(function checkIncorrectFireballs))
	set localTrigVar = null
endfunction

endscope

For the sake of thoroughness, here's the Fireblast:

JASS:
//==================================/ AOE /==================================================================
scope Fireblast initializer init

globals
    private constant integer                    ABIL_ID            = 'A00L'
    
    private constant string                     FX_UNITS_AFFECTED  = null
    private constant string                     FX_UNITS_ATTACH    = null
    private constant string                     FX_POINT           = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
    
    private constant real                       AOE_SIZE           = 200.
    private constant real                       AOE_PER_LVL        = 0.
    private constant real                       DAMAGE_AMOUNT      = 45.
    private constant real                       DAMAGE_PER_LVL     = 20.
    
    private constant boolean                    DEALS_DAMAGE       = true
    private constant boolean                    HAS_UNIT_FX        = false
    private constant boolean                    HAS_POINT_FX       = true
    
    private group FireblastEnumGroup = CreateGroup()
endglobals

struct FireblastData

    integer level
    unit caster
    group Fireblasttargets
    real aoe

    static method create takes unit caster, location spelltarget, integer spelllevel returns FireblastData
        local FireblastData this = FireblastData.allocate()
        local unit enemy
        local effect pointFX
        local effect unitFX
        
        set .level = spelllevel
        set .caster = caster
        set .Fireblasttargets = CreateGroup()
        set .aoe = AOE_SIZE + (AOE_PER_LVL*.level)
        
        if HAS_POINT_FX == true then
            set pointFX = AddSpecialEffectLoc(FX_POINT, spelltarget)
            call DestroyEffect(pointFX)
        endif
        
        call GroupEnumUnitsInRangeOfLoc(FireblastEnumGroup, spelltarget, .aoe, null)
        loop   
            set enemy = FirstOfGroup(FireblastEnumGroup)
            exitwhen enemy == null
                if ValidAliveTargetGroupedGround(enemy, .caster, .Fireblasttargets) == true then
                    call GroupAddUnit(.Fireblasttargets, enemy)
                   /* if HAS_UNIT_FX == true then
                        set unitFX = AddSpecialEffectTarget(FX_UNITS_AFFECTED, enemy, FX_UNITS_ATTACH)
                        call DestroyEffect(unitFX)
                    endif */
                    if DEALS_DAMAGE == true then
                        call UnitDamageTarget(.caster, enemy, DAMAGE_AMOUNT + (DAMAGE_PER_LVL*.level), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, null)
                    endif
                endif
            call GroupRemoveUnit(FireblastEnumGroup, enemy)
            call DestroyGroup(.Fireblasttargets)
        endloop
        
        set pointFX = null
        set unitFX =null
        set enemy = null
        call .destroy()
        return this
    endmethod
    
    method onDestroy takes nothing returns nothing
        set .Fireblasttargets = null
        set .caster = null
    endmethod

endstruct

private function checkFireblast takes nothing returns boolean
    local FireblastData localFireblast
    local unit casterunit = GetTriggerUnit()
    local location targetloc = GetSpellTargetLoc()
	if GetSpellAbilityId() == ABIL_ID then
        set localFireblast = FireblastData.create(casterunit, targetloc, GetUnitAbilityLevel(casterunit, ABIL_ID))
	endif
    call RemoveLocation(targetloc)
    set casterunit = null
    set targetloc = null
	return false
endfunction

//===================================/ Init /================================================================

private function init takes nothing returns nothing
	local trigger localTrigVar = CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(localTrigVar, EVENT_PLAYER_UNIT_SPELL_EFFECT)
	call TriggerAddCondition( localTrigVar, Condition(function checkFireblast))
	set localTrigVar = null
endfunction

endscope

Though considering the blasts work, not sure how it'd affect the fireballs unless something overlapped.
 
Last edited:
Status
Not open for further replies.
Top