//------- Spell **Mountain Boulder** ver1.01 by ZibiTheWand3r3r
// spell type: not channeling, unit-target, MUI
//
// ------- Description:
// Hero throws a boulder which follows the target. On its way boulder damages enemies by 1/4 damage,
// additionally pieces of rock break off and damage random enemy by 10dmg every 1sec.
// If boulder reach the target it explodes and deals 100/200/300 damage to all enemies (and buildings) in 250aoe
// and immobilize target (only) for 2/3/4sec. If boulder doesn't reach the target in 20/22/24sec (or target dies)
// boulder will explode and not deals damage. Damage type: physical (can attack magic-immune units, applies armor value)
// note: spell based on "channel" orderID: 'thunderbolt' /can be changed/
//
//------- Requirements:
// GUI Unit Indexer 1.2.0.2 by Bribe (or other)
// http://www.hiveworkshop.com/forums/spells-569/gui-unit-indexer-1-2-0-2-a-197329/
// WorldBounds
// https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
//
//-------- Instalation:
// copy & paste: 2 units (DUMMY, BOULDER_MISSLE)
// copy & paste: 2 abilities (ABI_MOUNTAIN_BOULDER, ABI_SHARDS)
// copy & paste this trigger, change settings
//--------Spell cancellation:
// You can cancel all spell instances any time by calling this function:
// call CancelSpell_MountainBoulder()
// It may be useful for Cinematics, when moving heroes in arena etc.
//
library MountainBoulder initializer Init uses WorldBounds
private function IsUnitAlive takes unit u returns boolean
return not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) != 0
endfunction
// -----start of user configuration ---------------------------
globals
// please make sure these raw codes are the same as copied into your map:
private constant integer DUMMY = 'n000'
// main ability id :
private constant integer ABI_MOUNTAIN_BOULDER = 'A000'
private constant integer BOULDER_MISSLE = 'e000'
// additional ability ABI_SHARDS is based on PhoenixFire: it shoots random enemy every 1sec/10dmg
// attack speed and damage can be changed in OE :
private constant integer ABI_SHARDS = 'A001'
// by default missle deals physical damage (can attack magic-immune units, applies armor value)
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MELEE
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
// effect: enemies hit during movement of the missle
private constant string ON_IMPACT_FX = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl"
// effect: immobilize main target on hit
private constant string IMMOBILIZE_FX = "Abilities\\Spells\\Orc\\StasisTrap\\StasisTotemTarget.mdl"
// effect: when missle hit main target and explodes
private constant string ON_EXPLOSION_FX = "Abilities\\Spells\\Human\\ThunderClap\\ThunderClapCaster.mdl"
endglobals
// this damage is deal upon impact, by default it is 100/200/300dmg for 3 levels spell
private constant function Damage takes integer level returns real
return level * 100.00
endfunction
// this damage is deal when missle travels, by default it is 25/50/75dmg for 3 levels
private constant function PartialDamage takes integer level returns real
return level * 25.00
endfunction
//set how long missle exists (by default it is: 20/22/24sec for 3 levels)
private constant function MissleDuration takes integer level returns real
return (2.00*level) + 18.00
endfunction
// set immobilize duration; when target is hit he won't be able to move for this amout of time
// by default it is 2/3/4sec for 3 levels:
private constant function ImmobilizeDuration takes integer level returns real
return level + 1.00
endfunction
// missle speed, suggested range: 7-25 (example: value 8.44 is equal to unit 270 speed)
// higher value=faster speed; recomended: 11.00
private constant function MissleSpeed takes nothing returns real
return 11.00
endfunction
// visual effect: missle scale; on higher spell levels missle will be bigger
// for high level spell (~10 or more levels) use 0.05 instead of 0.10
private constant function MissleScale takes integer level returns real
return (0.10*level) + 1.50
endfunction
//when missle reach its target and explode, it deals aoe damage
private constant function ExplosionRadius takes nothing returns real
return 250.00
endfunction
//during movement missle deals aoe damage
private constant function MissleOnMoveDmgRadius takes nothing returns real
return 200.00
endfunction
//filter target units
private function TargetUnitFilter takes unit u, player pla returns boolean
return IsUnitEnemy(u, pla) and IsUnitAlive(u)
endfunction
// -----end of user configuration ----------------------------
globals
private unit array spellMissle
private unit array spellTargetUnit
private real array spellDuration
private integer array spellLevel
private effect array spellEffect
private group array spellGroupVictims
private group spellGroup
private group ug
private timer spellTimer
endglobals
private function Cancel takes nothing returns nothing
set spellDuration[GetUnitUserData(GetEnumUnit())] = 0.00
endfunction
function CancelSpell_MountainBoulder takes nothing returns nothing
call ForGroup(spellGroup, function Cancel)
endfunction
//-----------------------------------------------------------
// ----- START MOUNTAIN BOULDER SPELL ------------
//-----------------------------------------------------------
private function LoopEnum takes nothing returns nothing
local unit d = GetEnumUnit()
local integer id = GetUnitUserData(d)
local player pla = GetOwningPlayer(d)
local unit u
local real x = GetUnitX(spellMissle[id])
local real y = GetUnitY(spellMissle[id])
local real Tx = GetUnitX(spellTargetUnit[id])
local real Ty = GetUnitY(spellTargetUnit[id])
local real angle = Atan2(Ty-y, Tx-x)
local real Tx1 = x + MissleSpeed() * Cos(angle)
local real Ty1 = y + MissleSpeed() * Sin(angle)
if not IsUnitAlive(spellTargetUnit[id]) then
set spellDuration[id] = 0.00
elseif IsUnitAlive(spellMissle[id]) then
call SetUnitFacing(spellMissle[id], bj_RADTODEG * angle)
if IsUnitInRangeXY(spellMissle[id], Tx, Ty, 64.00) then // missle reach its target:
call DestroyEffect(AddSpecialEffect(ON_EXPLOSION_FX, Tx, Ty))
call GroupEnumUnitsInRange(ug, x, y, ExplosionRadius(), null)
loop
set u = FirstOfGroup(ug)
exitwhen u == null
if TargetUnitFilter(u, pla) then
call UnitDamageTarget(d, u, Damage(spellLevel[id]), true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
if u == spellTargetUnit[id] and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
call SetUnitPropWindow(spellTargetUnit[id], 0.00) //immobilize
set spellEffect[id] = AddSpecialEffectTarget(IMMOBILIZE_FX, spellTargetUnit[id], "overhead")
endif
endif
call GroupRemoveUnit(ug, u)
endloop
call KillUnit(spellMissle[id])
call UnitRemoveAbility(d, ABI_SHARDS)
if spellDuration[id] > 0.00 then
set spellDuration[id] = ImmobilizeDuration(spellLevel[id])
endif
// move missle, if coordinates are inside map:
elseif Tx1 >= WorldBounds.minX and Tx1 <= WorldBounds.maxX and Ty1 >= WorldBounds.minY and Ty1 <= WorldBounds.maxY then
call SetUnitX(spellMissle[id], Tx1)
call SetUnitY(spellMissle[id], Ty1)
call SetUnitX(d, Tx1)
call SetUnitY(d, Ty1)
call GroupEnumUnitsInRange(ug, Tx1, Ty1, MissleOnMoveDmgRadius(), null) // make damage while missle moving
loop
set u = FirstOfGroup(ug)
exitwhen u == null
if TargetUnitFilter(u, pla) and (not IsUnitInGroup(u, spellGroupVictims[id])) and u != spellTargetUnit[id] then
call DestroyEffect(AddSpecialEffectTarget(ON_IMPACT_FX, u, "origin"))
call UnitDamageTarget(d, u, PartialDamage(spellLevel[id]), true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
call GroupAddUnit(spellGroupVictims[id], u)
endif
call GroupRemoveUnit(ug, u)
endloop
else // new target point outside map:
set spellDuration[id] = 0.00
endif
endif
set spellDuration[id] = spellDuration[id] - 0.03125
if spellDuration[id] <= 0.00 then
// Clean up any data attached to this spell
call SetUnitPropWindow(spellTargetUnit[id], GetUnitDefaultPropWindow(spellTargetUnit[id]) * bj_DEGTORAD) //allow move
call DestroyGroup(spellGroupVictims[id])
set spellGroupVictims[id] = null
call DestroyEffect(spellEffect[id])
call GroupRemoveUnit(spellGroup, d)
call KillUnit(spellMissle[id])
call KillUnit(d)
set spellMissle[id] = null
set spellTargetUnit[id] = null
if FirstOfGroup(spellGroup) == null then
call PauseTimer(spellTimer)
endif
endif
set u=null
set d=null
endfunction
//---------------------
private function Loop takes nothing returns nothing
call ForGroup(spellGroup, function LoopEnum)
endfunction
//---------------------
private function OnCast takes nothing returns nothing
local unit u = GetTriggerUnit()
local player pla = GetTriggerPlayer()
local unit d = CreateUnit(pla, DUMMY, GetUnitX(u), GetUnitY(u), 0.00)
local integer id = GetUnitUserData(d)
call UnitAddAbility(d, ABI_SHARDS)
set spellMissle[id] = CreateUnit(pla, BOULDER_MISSLE, GetUnitX(u), GetUnitY(u), 0.00)
set spellTargetUnit[id] = GetSpellTargetUnit()
set spellLevel[id] = GetUnitAbilityLevel(u, ABI_MOUNTAIN_BOULDER)
set spellDuration[id] = MissleDuration(spellLevel[id])
call SetUnitScale(spellMissle[id], MissleScale(spellLevel[id]), 0.00, 0.00)
set spellGroupVictims[id] = CreateGroup()
if FirstOfGroup(spellGroup) == null then
call TimerStart(spellTimer, 0.03125, true, function Loop)
endif
call GroupAddUnit(spellGroup, d)
set u=null
set d=null
endfunction
//---------------------
private function Cond takes nothing returns boolean
if GetSpellAbilityId() == ABI_MOUNTAIN_BOULDER then
call OnCast()
endif
return false
endfunction
//-----------------------------------------------------------
// ----- END MOUNTAIN BOULDER SPELL --------------
//-----------------------------------------------------------
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function Cond))
set t=null
set ug = CreateGroup()
set spellGroup = CreateGroup()
set spellTimer = CreateTimer()
endfunction
endlibrary