• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] Atomic Bomb v3.1.0.1

The documentation explains everything you need to know.

JASS:
/*************************************
*
*   AtomicBomb
*   v3.1.0.1
*   By Magtheridon96
*
*   - Creates a bombing plane behind the caster
*     that will drop an atomic bomb dealing damage
*     to enemy units in an area of effect. Deals
*     bonus damage to closer units.
*
*   Requires:
*   ---------
*
*       - CTL by Nestharus
*           - hiveworkshop.com/forums/jass-resources-412/snippet-constant-timer-loop-32-a-201381/
*       - SpellEffectEvent by Bribe
*           - hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
*       - Particle by Nestharus
*           - hiveworkshop.com/forums/submissions-414/snippet-needs-work-particle-206279/
*
*       Optional:
*       ---------
*
*           - TimerUtils by Vexorian (This also caters to my version of TimerUtils in the Hive Jass section Graveyard)
*               - wc3c.net/showthread.php?t=101322
*
*   Importing:
*   ----------
*
*       - Copy this code to a new trigger.
*       - Make sure you have all the requirements implemented too.
*       - Go the object editor and copy/paste the objects to your map (1 ability, 2 dummy units).
*       - There is also an object called Particle that you need which is part of the Particle library.
*       - Import the special effect and dummy.mdl to your map.
*       - Configure the data in the globals to your liking.
*       - Done!
*
*   Credits:
*   --------
*
*       - WILL THE ALMIGHTY (Explosion Effect)
*       - Bribe (SpellEffectEvent, Table)
*       - Nestharus (CTL, Particle)
*       - Vexorian (TimerUtils)
*
*************************************/
library AtomicBomb requires CTL, SpellEffectEvent, Particle, optional TimerUtils
    
    // Configuration
    globals
        // The ability raw code
        private constant integer ABIL_CODE = 'A000'
        // The plane dummy unit raw code
        private constant integer PLANE_CODE = 'h003'
        // The bomb dummy unit raw code
        private constant integer BOMB_CODE = 'h001'
        // Explosion Effect
        private constant string EXPLOSION_EFFECT = "war3mapImported\\NuclearExplosion.mdx"
        // The effect that is created when the plane is created.
        private constant string PLANE_IN_EFFECT = "Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl"
        // The effect that is created when the plane is destroyed.
        private constant string PLANE_OUT_EFFECT = "Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl"
        // How much time should we wait before destroying the explosion effect?
        private constant real DESTROY_EXPLOSION_AFTER = 15.
        // How much time should we wait before destroying the effect created when the plane is created?
        private constant real DESTROY_IN_EFFECT_PARTICLE_AFTER = 1.4
        // How much time should we wait before destroying the effect created when the plane is destroyed?
        private constant real DESTROY_OUT_EFFECT_PARTICLE_AFTER = 1.4
        // Bomb and Plane height
        private constant real BOMB_HEIGHT = 700.
        // Bomb Drop Speed Base (It will accelerate starting from this base-speed)
        private constant real BOMB_DROP_SPEED = 1.
        // Bomb Drop Acceleration
        private constant real BOMB_DROP_ACCEL = 1.
        // The dummy owning player
        private constant player DUMMY_OWNER = Player(13)
        // Destroy trees around the explosion?
        private constant boolean DESTROY_TREES = true
        // The offset from the point from the target point for plane creation
        private constant real PLANE_DISTANCE = 800.
        // How long does the plane stay after it drops the bomb?
        private constant real PLANE_AFTER_DROP = 2.
        // Attack type
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        // Damage type
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        // The largest collision a unit has in your map
        private constant real MAX_COLLISION = 197.
    endglobals
    // End Configuration
    
    // The speed of the plane per 1/32th of a second
    private function GetPlaneSpeedFactor takes integer level returns real
        return 9.
    endfunction
    
    // The damage dealed to each unit
    private function GetDamage takes integer level returns real
        return 150 + level * 75.
    endfunction
    
    // The damage radius
    private function GetDamageRadius takes integer level returns real
        return 275 + level * 75.
    endfunction
    
    // How close do units have to be for the spell to deal bonus damage?
    private function GetBonusDamageRadius takes integer level returns real
        return 125 + level * 50.
    endfunction
    
    // I want the spell to deal double the damage to units closer than the bonus damage radius.
    private function GetBonusDamageFactor takes integer level returns real
        return 1.3
    endfunction
    
    // This function will filter out some targets (Allied, dead, magic immune and structures)
    private function TargetFilter takes player owner, unit caster, unit target returns boolean
        return IsUnitEnemy(target, owner) and not (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) or IsUnitType(target, UNIT_TYPE_STRUCTURE) or IsUnitType(target, UNIT_TYPE_DEAD) or GetUnitTypeId(target) == 0)
    endfunction
    
    private struct Spell extends array
        private static group enumGroup = CreateGroup()
        
        private static unit array caster
        private static unit array plane
        private static unit array bomb
        private static real array xDis
        private static real array yDis
        private static real array planeX
        private static real array planeY
        private static real array dropX
        private static real array dropY
        private static real array damage
        private static real array radius
        private static real array bonusDamage
        private static real array bonusRadius
        private static real array speed
        private static real array height
        private static integer array counter
        private static integer array level
        private static boolean array dropped
        private static boolean array exploded
        private static player array owner
        
        static if DESTROY_TREES then
            private static rect treeRect = null
            private static unit treeDetector = null
            private static real treesRadius = 0
            private static real treesX = 0
            private static real treesY = 0
            private static method isTree takes destructable d returns boolean
                return IssueTargetOrderById(treeDetector, 852018, d) and IssueImmediateOrderById(treeDetector, 851972)
            endmethod
            private static method destroyTree takes nothing returns nothing
                local destructable d = GetEnumDestructable()
                local real x = GetWidgetX(d) - treesX
                local real y = GetWidgetY(d) - treesY
                // If the destructable is a tree and it is in the circle, kill it
                if x*x+y*y <= treesRadius and isTree(d) then
                    call KillDestructable(d)
                endif
                set d = null
            endmethod
        endif
        
        // You don't need a Table or a hashtable if you have TimerUtils
        static if not LIBRARY_TimerUtils then
            private static Table timerData
        endif
        
        // I want to murder this function
        private static method destroyParticle takes nothing returns nothing
            static if LIBRARY_TimerUtilsEx then
                call Particle(ReleaseTimer(GetExpiredTimer())).destroy()
            elseif LIBRARY_TimerUtils then
                call Particle(GetTimerData(GetExpiredTimer())).destroy()
                call ReleaseTimer(GetExpiredTimer())
            else
                call Particle(timerData[GetHandleId(GetExpiredTimer())]).destroy()
                call DestroyTimer(GetExpiredTimer())
            endif
        endmethod
        
        implement CTL
            local timer t
            local unit u
            local real x
            local real y
        implement CTLExpire
            // Set the planeX and planeY coordinates
            set planeX[this] = planeX[this] + xDis[this]
            set planeY[this] = planeY[this] + yDis[this]
            
            // Moving the plane out of the map bounds would crash the game, so we should make sure the coordinates are sound.
            if planeX[this] <= WorldBounds.maxX or planeX[this] >= WorldBounds.minX or planeY[this] <= WorldBounds.maxY or planeY[this] >= WorldBounds.minY then
                // Move the plane
                call SetUnitX(plane[this], planeX[this])
                call SetUnitY(plane[this], planeY[this])
            endif
            
            // If the bomb has been dropped
            if dropped[this] then
                // If the bomb exploded
                if exploded[this] then
                    
                    // Counter-based checking
                    set counter[this] = counter[this] - 1
                    if counter[this] == 0 then
                    
                        // Destroy the current instance of the spell
                        call this.destroy()
                        
                        // Remove the plane
                        call RemoveUnit(plane[this])
                        
                        // Timer Utils is optional :3
                        // This is a pretty ugly way of creating a special effect with a Z-coordinate.
                        static if LIBRARY_TimerUtils then
                            call TimerStart(NewTimerEx(Particle.createEx(Player(13), planeX[this], planeY[this], BOMB_HEIGHT, 0, 0, 1, 255, 255, 255, 255, PLANE_OUT_EFFECT, 0)), DESTROY_OUT_EFFECT_PARTICLE_AFTER, false, function thistype.destroyParticle)
                        else
                            set t = CreateTimer()
                            set timerData[GetHandleId(t)] = Particle.createEx(Player(13), planeX[this], planeY[this], BOMB_HEIGHT, 0, 0, 1, 255, 255, 255, 255, PLANE_OUT_EFFECT, 0)
                            call TimerStart(t, DESTROY_OUT_EFFECT_PARTICLE_AFTER, false, function thistype.destroyParticle)
                            set t = null
                        endif
                        
                        // Null variables
                        set caster[this] = null
                        set plane[this] = null
                        set bomb[this] = null
                        set owner[this] = null
                    endif
                else
                    // If the height of the bomb is close to 0
                    if height[this] <= 5 then
                        
                        // Destroy the bomb and create the explosion
                        call RemoveUnit(bomb[this])
                        
                        static if LIBRARY_TimerUtils then
                            call TimerStart(NewTimerEx(Particle.createEx(Player(13), dropX[this], dropY[this], 0, 0, 0, 1, 255, 255, 255, 255, EXPLOSION_EFFECT, 0)), DESTROY_EXPLOSION_AFTER, false, function thistype.destroyParticle)
                        else
                            set t = CreateTimer()
                            set timerData[GetHandleId(t)] = Particle.createEx(Player(13), dropX[this], dropY[this], 0, 0, 0, 1, 255, 255, 255, 255, EXPLOSION_EFFECT, 0)
                            call TimerStart(t, DESTROY_EXPLOSION_AFTER, false, function thistype.destroyParticle)
                            set t = null
                        endif
                        
                        // Loop through all units in the area
                        call GroupEnumUnitsInRange(enumGroup, dropX[this], dropY[this], radius[this] + MAX_COLLISION, null)
                        loop
                            set u = FirstOfGroup(enumGroup)
                            exitwhen u == null
                            call GroupRemoveUnit(enumGroup, u)
                            
                            // Check if the target unit passes the filter and make sure he's in the blast radius.
                            if TargetFilter(owner[this], caster[this], u) and IsUnitInRangeXY(u, dropX[this], dropY[this], radius[this]) then
                                
                                set x = GetUnitX(u) - dropX[this]
                                set y = GetUnitY(u) - dropY[this]
                                
                                // If the unit is closer than the bonusRange
                                if x*x+y*y <= bonusRadius[this] then
                                    // We deal the bonus damage
                                    call UnitDamageTarget(caster[this], u, bonusDamage[this], false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                                else
                                    // Else, we deal the normal damage
                                    call UnitDamageTarget(caster[this], u, damage[this], false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                                endif
                            endif
                        endloop
                        
                        static if DESTROY_TREES then
                            call SetRect(treeRect, dropX[this] - radius[this], dropY[this] - radius[this], dropX[this] + radius[this], dropY[this] + radius[this])
                            
                            // Cache tree destroy data
                            set treesRadius = radius[this]*radius[this]
                            set treesX = dropX[this]
                            set treesY = dropY[this]
                            
                            // Enumerate through all destructables in the rect
                            call EnumDestructablesInRect(treeRect, null, function thistype.destroyTree)
                        endif
                        
                        // The counter is now going to be used to determine
                        // the number of iterations before the spell ends.
                        set counter[this] = R2I(PLANE_AFTER_DROP * 32)
                        set exploded[this] = true
                    else
                        // Decrease the bomb's height a bit
                        set height[this] = height[this] - speed[this]
                        call SetUnitFlyHeight(bomb[this], height[this], 0)
                        
                        // Accelerate drop speed
                        set speed[this] = speed[this] + BOMB_DROP_ACCEL
                    endif
                endif
            else
                // Decrease the counter by 1
                set counter[this] = counter[this] - 1
                // When the counter is 0, the plane has reached it's destination.
                if counter[this] == 0 then
                    // Store the dropX and dropY coordinates
                    set dropX[this] = planeX[this]
                    set dropY[this] = planeY[this]
                    
                    // Create the bomb under the plane
                    set bomb[this] = CreateUnit(DUMMY_OWNER, BOMB_CODE, dropX[this], dropY[this], 0)
                    
                    // Set the bomb's height
                    call UnitAddAbility(bomb[this], 'Amrf')
                    call UnitRemoveAbility(bomb[this], 'Amrf')
                    call SetUnitFlyHeight(bomb[this], BOMB_HEIGHT, 0)
                    
                    // We have dropped the bomb
                    set dropped[this] = true
                    
                    // Set the bomb height and the bomb drop speed
                    set height[this] = BOMB_HEIGHT
                    set speed[this] = BOMB_DROP_SPEED
                endif
            endif
        implement CTLNull
            set u = null
        implement CTLEnd
        
        private static method run takes nothing returns nothing
            local thistype this = create()
            local real x = GetSpellTargetX()
            local real y = GetSpellTargetY()
            local real a
            local real s
            local real cos
            local real sin
            local timer t
            
            // Set the caster and the owner and the level of the ability
            set caster[this] = GetTriggerUnit()
            set owner[this] = GetTriggerPlayer()
            set level[this] = GetUnitAbilityLevel(caster[this], ABIL_CODE)
            
            // Cache angle data
            set a = Atan2(y - GetUnitY(caster[this]), x - GetUnitX(caster[this]))
            set cos = Cos(a)
            set sin = Sin(a)
            
            // Set the plane coordinates
            set planeX[this] = x - PLANE_DISTANCE * cos
            set planeY[this] = y - PLANE_DISTANCE * sin
            
            // Create the plane
            set plane[this] = CreateUnit(DUMMY_OWNER, PLANE_CODE, planeX[this], planeY[this], a * bj_RADTODEG)
            
            // Set the speed and x/yOffsets per timeout
            set s = GetPlaneSpeedFactor(level[this])
            set xDis[this] = cos * s
            set yDis[this] = sin * s
            
            // We haven't dropped or exploded the bomb
            set dropped[this] = false
            set exploded[this] = false
            
            // Number of iterations before the bomb drops
            set counter[this] = R2I(PLANE_DISTANCE / s)
            
            // Set the plane's height
            call UnitAddAbility(plane[this], 'Amrf')
            call UnitRemoveAbility(plane[this], 'Amrf')
            call SetUnitFlyHeight(plane[this], BOMB_HEIGHT, 0)
            
            // TimerUtils is optional
            // This is an ugly way of creating a special effect with Z
            static if LIBRARY_TimerUtils then
                call TimerStart(NewTimerEx(Particle.createEx(Player(13), planeX[this], planeY[this], BOMB_HEIGHT, 0, 0, 1, 255, 255, 255, 255, PLANE_IN_EFFECT, 0)), DESTROY_IN_EFFECT_PARTICLE_AFTER, false, function thistype.destroyParticle)
            else
                set t = CreateTimer()
                set timerData[GetHandleId(t)] = Particle.createEx(Player(13), planeX[this], planeY[this], BOMB_HEIGHT, 0, 0, 1, 255, 255, 255, 255, PLANE_IN_EFFECT, 0)
                call TimerStart(t, DESTROY_IN_EFFECT_PARTICLE_AFTER, false, function thistype.destroyParticle)
                set t = null
            endif
            
            // Set the damage and the radius
            set damage[this] = GetDamage(level[this])
            set radius[this] = GetDamageRadius(level[this])
            
            // Set the bonus damage and the bonus radius
            set a = GetBonusDamageRadius(level[this])
            set bonusRadius[this] = a*a
            set bonusDamage[this] = GetBonusDamageFactor(level[this])*damage[this]
        endmethod
        
        private static method onInit takes nothing returns nothing
            // Register the spell to SpellEffectEvent
            call RegisterSpellEffectEvent(ABIL_CODE, function thistype.run)
            
            static if DESTROY_TREES then
                set treeDetector = CreateUnit(Player(15), 'hfoo', 0, 0, 0)
                call UnitAddAbility(treeDetector, 'Ahrl')
                call UnitAddAbility(treeDetector, 'Aloc')
                call ShowUnit(treeDetector, false)
                set treeRect = Rect(0,0,0,0)
            endif
            
            // If you don't have TimerUtils, you need a Table
            static if not LIBRARY_TimerUtils then
                set timerData = Table.create()
            endif
        endmethod
    endstruct
endlibrary

Finally, after more than a year, I've updated this spell and it works perfectly :)
It used to be in GUI, but I finally had the time to rewrite and fix it.

Oh, and the testmap is a must-see ;)
I made it so that it's like a demonstration :D
Just type 'start', then sit back and watch the show 8)

Keywords:
Bomb, Bomber, Nuke, Nuclear, Explosion, Magtheridon96, Explode, Atom, Atomic, Naruto, DotA, Kamehameha, Omnislash, Meathook, Meat hook.
Contents

new map (Map)

Reviews
6 April 2012 Bribe: This is a very good example of well-written, readable code, and since it works flawlessly from what I can tell from my tests, I will go for approving this.

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Why are you creating a local rect and removing it right after? There is the SetRect native to avoid this very thing, so you could technically use a global rect here.

I thought you had a trick that did this all in one line?:

JASS:
        private static method isTree takes destructable d returns boolean
            local boolean result = false
            call PauseUnit(treeDetector, false)
            set result = IssueTargetOrder(treeDetector, "harvest", d)
            call PauseUnit(treeDetector, true)
            return result
        endmethod
 
Yeah, I know, but it wasn't working, and I was debugging this for 3 days in a row, so I decided to upload it as is.

I'll optimize it right now :p

edit
I found a bunch of other errors too. I just fixed them.
I'm going to review it one more time before I update it.

By the way, did you like the test-map? :D
A pretty cool way of presenting a spell, right? ^_^

edit
Updated.
Optimized and made the Demo slightly better.
 
Last edited:
Level 15
Joined
Jul 6, 2009
Messages
889
But it would be a minor improvement nonetheless. I've also found that the the ability hotkey for usage in the testmap is displayed as Q but the learn hotkey is set as R. Also:

WANTED: EXPLOSION
DEAD OR ALIVE


No explosions in the testmap :( You set the constant integer PARTICLE_ID as 'h000' instead of as 'n000'. I was thinking why the spell looked different from what was shown on the image and why no explosion was evident.
 
Oh Magtheridon, you silly willy man, why are you using bj_lastCreatedGroup?

--- To save handles dude...

But have you ever considered the worst-case scenario?

--- What?

What if a unit dies due to damage from this spell, and there's a function registered to a death event using group enumeration with bj_lastCreatedGroup. It would totally bug the entire thing!

--- Oh my... I feel so stupid... I'm going to update this spell and all the others.
Thank you Magtheridon you awesome man <3

No problem bro <3
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
Just things I want to know:
1) Why are you destroying timers and using TU instead of just creating 1 timer for all and just loop it?
2) What is the difference between static member array and just non-static, I mean:
yours is static >>> caster[this] instead of a non-static >>> this.caster

EDIT:
Im rating it 5/5 btw coz it's cool...
 
1) Why are you destroying timers and using TU instead of just creating 1 timer for all and just loop it?

I am doing that, but the thing is, the effects are not synced with the spell loop, so I'm creating a timer to expire after 1 second (configurable) so I can remove the "Particle", which is like an effect with Z (A dummy unit, pretty much)

The reason I've added the Timer destroying is because I'm TimerUtils an optional requirement :D
If you don't have it, the spell destroys timers, else, it recycles them.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
if you're talking aboout the SFX is destroyed immediately without smoke, then I'm sure you can create it a small snippet inside the code itself, coz IMO, the used library requirement is overkill for a single spell...

also I suggest to make a radiation of the bomb, like make a DamageOverTime after the radiation is in effect...
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
why make a snippet for it if the system is already existent and you can just import it? the thing here is that we don't like redoing things that are already done...

as for the suggestion, I'd like to see that too...

This >>>...coz IMO, the used library requirement is overkill for a single spell, Particle library for example requires at least 4 additional add-ons...

EDIT:
I may also add a suggestion to use TimerTools which will replace CTL and TimerUtils...take note that this is just my own opinion and maybe ignored :)...
 
Top