/*************************************
*
* 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