/*~~~~~~~~~~~~~~~~~ OMEGA WAVE v1.04 by Maker ~~~~~~~~~~~~~~~~~~~~~*/
/* |
| Creates rotating circles around the caster. Part of the circle |
| damages and part destroys mana. |
| */
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
scope OmegaWave initializer Omega_Wave
globals
private constant integer ABILCODE = 'A000'
private constant integer DUMMY_TYPE = 'h000'
private constant integer ORDERID = 852600
// Of how many lightnings the circle consist
private constant integer LIGHTNINGS = 16
// How many of the lightnings do not cause damage
// They drain mana instead
private constant integer MANA_LIGHTNINGS = 6
// How many circle waves there are
private constant integer WAVES = 3
// How many more waves per ability level. Does not apply at lvl 1
private constant integer WAVES_BONUS = 1
// How often a new circle is spawned
// Match this with Data - Art duration
// in object editor
private constant real INTERVAL = 2.0
// Height offset from ground
private constant real HEIGHT = 60.
// How fast the circle expands
private constant real SPEED = 3.
// Initial radius of the circle
private constant real INIT_DIST = 32.
// Circle position update interval
private constant real TIMEOUT = 0.03
// How far the circle expands
private constant real MAX_OFFSET = 400.
// Base damage per second
private constant real DMG_BASE = 100. * TIMEOUT
// Bonus damage per ability level per second.
// Does not apply at level 1
private constant real DMG_BONUS = 30. * TIMEOUT
// How much mana is destroyed per second
private constant real MANA_BASE = 40. * TIMEOUT
// BOnus mana destroyed per ability level per second.
// Does not apply at level 1
private constant real MANA_BONUS = 20. * TIMEOUT
// How wide the lightning is. Used for detecting
// collision with units
private constant real LIGHTNING_WIDTH = 28.
// Does the circle rotate
private constant boolean ROTATES = TRUE
// Min and max speed for the rotation
private constant real MIN_ROT_SPD = 15 * TIMEOUT * bj_DEGTORAD
private constant real MAX_ROT_SPD = 30 * TIMEOUT * bj_DEGTORAD
// Lightning types
private constant string TYPESAFE = "DRAM"
private constant string TYPEDANGER = "DRAL"
// RGB values of the mana lightnings, adjusts colour
// Adjust the first 255 between 0 and 255
private constant real MRED = 255. / 255.
private constant real MGREEN = 255. / 255.
private constant real MBLUE = 255. / 255.
// RGB values of the health lightnings, adjusts colour
private constant real HRED = 255. / 255.
private constant real HGREEN = 255. / 255.
private constant real HBLUE = 255. / 255.
// Transparency of the lightnings
private constant real MALPHA = 1.
private constant real HALPHA = 1.
// Effect when damaging lightning hits
private constant string DMG_EFFECT = "Abilities\\Weapons\\FaerieDragonMissile\\FaerieDragonMissile.mdl"
// Effect when mana destroying lightning hits
private constant string MANA_EFFECT = "Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl"
// Attack- and damage types
private constant attacktype ATKTYPE = ATTACK_TYPE_NORMAL
private constant damagetype DMGTYPE = DAMAGE_TYPE_NORMAL
/*~~~~~~~~~~~~~ Global variables, don't change these ~~~~~~~~~~~~~*/
private real r1
private real r2
private real r3
private real r4
private real r5
private real r6
private real r7
private player plr
private unit un
private integer casts = 0
private location l1 = Location(0,0)
private location l2 = Location(0,0)
private group grp1 = CreateGroup()
private group grp2 = CreateGroup()
private group casters = CreateGroup()
private timer tmr = CreateTimer()
private hashtable hash = InitHashtable()
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
endglobals
/*~The hash functions return the child hash integer for hashtable~*/
private constant function HashCircles takes nothing returns integer
return 1
endfunction
private constant function HashActiveCircles takes nothing returns integer
return 2
endfunction
private constant function HashTime takes nothing returns integer
return 3
endfunction
private constant function HashInterval takes nothing returns integer
return 4
endfunction
private constant function HashDamage takes nothing returns integer
return 5
endfunction
private constant function HashSafeWidth takes nothing returns integer
return 6
endfunction
private constant function HashWaves takes nothing returns integer
return 7
endfunction
private constant function HashFirstFree takes nothing returns integer
return 8
endfunction
private constant function HashFirst takes nothing returns integer
return 9
endfunction
private constant function HashLast takes nothing returns integer
return 10
endfunction
private constant function HashMana takes nothing returns integer
return 11
endfunction
private function HashAngle takes integer value returns integer
return 10 * LIGHTNINGS + value
endfunction
private function HashDist takes integer value returns integer
return 20 * LIGHTNINGS + value
endfunction
private function HashDestroyed takes integer value returns integer
return 30 * LIGHTNINGS + value
endfunction
private function HashSafeAngle takes integer value returns integer
return 40 * LIGHTNINGS + value
endfunction
private function HashRotSpeed takes integer value returns integer
return 50 * LIGHTNINGS + value
endfunction
private function HashRotDir takes integer value returns integer
return 60 * LIGHTNINGS + value
endfunction
private function HashDummy takes integer value1, integer value2 returns integer
return 1000 * LIGHTNINGS + value1 * 100 + value2
endfunction
private function HashLightning takes integer value1, integer value2 returns integer
return 5000 * LIGHTNINGS + value1 * 100 + value2
endfunction
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~Deals damage to units~~~~~~~~~~~~~~~~~~~~~*/
private function Pick_Filter_2 takes nothing returns boolean
local unit u = GetFilterUnit()
if not(IsUnitInGroup(u, grp1)) and /*
*/ IsUnitEnemy(u, plr) and /*
*/ GetWidgetLife(u) > 0.405 and /*
*/ GetUnitFlyHeight(u) < HEIGHT then
if Cos(r1 - Atan2(GetUnitY(u) - r4, GetUnitX(u) - r3)) < r7 then
call UnitDamageTarget(un, u, r5, false, true, ATKTYPE, DMGTYPE, null)
call DestroyEffect(AddSpecialEffectTarget(DMG_EFFECT, u, "chest"))
elseif GetUnitState(u, UNIT_STATE_MANA) > 0 then
call DestroyEffect(AddSpecialEffectTarget(MANA_EFFECT, u, "chest"))
call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MANA) - r6)
endif
endif
set u = null
return FALSE
endfunction
private function Pick_Filter_1 takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(), plr)
endfunction
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~Adds an expanding circle for the caster~~~~~~~~~~~~*/
private function Add_Circle takes unit u, integer ID returns nothing
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real angle = GetRandomReal(-bj_PI, bj_PI)
local real x1
local real y1
local real x2
local real y2
local integer i = 0
local integer first = LoadInteger(hash, ID, HashFirst())
local integer last = LoadInteger(hash, ID, HashLast())
local integer free = LoadInteger(hash, ID, HashFirstFree())
local integer circles = LoadInteger(hash, ID, HashCircles())
local integer waves = LoadInteger(hash, ID, HashWaves())
local lightning l
// Saves the angle where the first lightning starts from
call SaveReal(hash, ID, HashAngle(free), angle)
// Saves the angle at the center of the safe lightnings
call SaveReal(hash, ID, HashSafeAngle(free), angle + (I2R(MANA_LIGHTNINGS) / I2R(LIGHTNINGS))* bj_PI)
// Randomizes the rotation angle
if ROTATES then
if GetRandomInt(1,2) == 1 then
call SaveReal(hash, ID, HashRotDir(free), 1)
else
call SaveReal(hash, ID, HashRotDir(free), -1)
endif
endif
// The loop creates a circle of lightnings and
// dummies around the caster
loop
set x1 = x + INIT_DIST * Cos(angle)
set y1 = y + INIT_DIST * Sin(angle)
set angle = angle + 2 * bj_PI / LIGHTNINGS
set x2 = x + INIT_DIST * Cos(angle)
set y2 = y + INIT_DIST * Sin(angle)
if i < MANA_LIGHTNINGS then
set l = AddLightningEx(TYPESAFE , true , x1 , y1 , HEIGHT , x2 , y2 , HEIGHT)
call SetLightningColor(l, MRED, MGREEN, MBLUE, MALPHA)
else
set l = AddLightningEx(TYPEDANGER , true , x1 , y1 , HEIGHT , x2 , y2 , HEIGHT)
call SetLightningColor(l, HRED, HGREEN, HBLUE, HALPHA)
endif
set bj_lastCreatedUnit = CreateUnit(Player(15), DUMMY_TYPE, x1, y1, angle*bj_RADTODEG)
call SetUnitFlyHeight(bj_lastCreatedUnit, HEIGHT, 0)
// Remove Move ability from dummy so it won't try to return
// to the pointwhere it was created at
call UnitRemoveAbility(bj_lastCreatedUnit, 'Amov')
call SaveUnitHandle(hash, ID, HashDummy(free,i), bj_lastCreatedUnit)
call SaveLightningHandle(hash, ID, HashLightning(free,i), l)
set i = i + 1
exitwhen i >= LIGHTNINGS
endloop
// Saves the distance of the circle from the caster
call SaveReal(hash, ID, HashDist(free), INIT_DIST)
call SaveBoolean(hash, ID, HashDestroyed(free), false)
call SaveInteger(hash, ID, HashCircles(), circles + 1)
call SaveInteger(hash, ID, HashFirstFree(), free + 1)
call SaveInteger(hash, ID, HashActiveCircles(), LoadInteger(hash, ID, HashActiveCircles()) + 1)
call SaveReal(hash, ID, HashRotSpeed(free), GetRandomReal(MIN_ROT_SPD, MAX_ROT_SPD))
// Update the smallest index to loop through
if free < first then
call SaveInteger(hash, ID, HashFirst(), free + 1)
endif
// Update the largest index to loop through
if free > last then
call SaveInteger(hash, ID, HashLast(), free)
endif
set l = null
endfunction
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
private function Loop takes nothing returns nothing
local unit u = GetEnumUnit()
local integer ID = GetHandleId(u)
local integer i = 0
local integer j = LoadInteger(hash, ID, HashFirst())
local integer k = 1
local integer oid = GetUnitCurrentOrder(u)
local integer free = LoadInteger(hash, ID, HashFirstFree())
local integer circles = LoadInteger(hash, ID, HashCircles())
local integer activeCircles = LoadInteger(hash, ID, HashActiveCircles())
local integer maxloop = LoadInteger(hash, ID, HashLast())
local real time = LoadReal(hash, ID, HashTime())+ TIMEOUT
local real interv = LoadReal(hash, ID, HashInterval()) + TIMEOUT
local real rotdir
local real offset
local real angle
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real x1
local real x2
local real y1
local real y2
local unit dummy
local lightning l
// Caster must be alive, waves left to cast and at least one active circle
if not(IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) != 0) and (circles < LoadInteger(hash, ID, HashWaves()) or activeCircles != 0) and oid == ORDERID then
loop
set rotdir = LoadReal(hash, ID, HashRotDir(j))
set angle = LoadReal(hash, ID, HashAngle(j))
set offset = LoadReal(hash, ID, HashDist(j)) + SPEED
// Has the circle not reached max dist
if offset < MAX_OFFSET then
set i = 0
loop
set x1 = x + offset * Cos(angle)
set y1 = y + offset * Sin(angle)
set angle = angle + 2 * bj_PI / LIGHTNINGS
set x2 = x + offset * Cos(angle)
set y2 = y + offset * Sin(angle)
call MoveLocation(l1, x1, y1)
call MoveLocation(l2, x2, y2)
set l = LoadLightningHandle(hash, ID, HashLightning(j,i))
// Makes the lightnings to "flow" the right direction
if rotdir == 1 then
call MoveLightningEx(l, false, x2, y2, HEIGHT + GetLocationZ(l2), x1, y1, HEIGHT + GetLocationZ(l1))
else
call MoveLightningEx(l, false, x1, y1, HEIGHT + GetLocationZ(l1), x2, y2, HEIGHT + GetLocationZ(l2))
endif
set dummy = LoadUnitHandle(hash, ID, HashDummy(j,i))
call SetUnitX(dummy, x1)
call SetUnitY(dummy, y1)
set i = i + 1
exitwhen i > LIGHTNINGS
endloop
set l = null
set dummy = null
set un = u
set plr = GetOwningPlayer(u)
set r1 = LoadReal(hash, ID, HashSafeAngle(j))
set r2 = LoadReal(hash, ID, HashSafeWidth())
set r7 = Cos(r2)
set r3 = x
set r4 = y
set r5 = LoadReal(hash, ID, HashDamage())
set r6 = LoadReal(hash, ID, HashMana())
call GroupEnumUnitsInRange(grp1, x, y, offset - LIGHTNING_WIDTH, function Pick_Filter_1)
call GroupEnumUnitsInRange(grp2, x, y, offset + LIGHTNING_WIDTH, function Pick_Filter_2)
call SaveReal(hash, ID, HashDist(j), offset)
// Updates the rotation angle and the safe angle
static if ROTATES then
call SaveReal(hash, ID, HashAngle(j), LoadReal(hash, ID, HashAngle(j)) + LoadReal(hash, ID, HashRotSpeed(j)) * LoadReal(hash, ID, HashRotDir(j)))
call SaveReal(hash, ID, HashSafeAngle(j), LoadReal(hash, ID, HashAngle(j)) + r2)
endif
else
// Checks whether the circle has been destroyed already or not
if LoadBoolean(hash, ID, HashDestroyed(j)) != true then
// The loop destroys all dummies and lightnings of the circle
set k = 0
loop
// Checks that the lightning exists, can produce fatal error without this check
if HaveSavedHandle(hash, ID, HashLightning(j,k)) then
call DestroyLightning(LoadLightningHandle(hash, ID, HashLightning(j,k)))
call UnitApplyTimedLife(LoadUnitHandle(hash, ID, HashDummy(j,k)), 1 , 0.01)
call RemoveSavedHandle(hash, ID, HashLightning(j,k))
endif
set k = k + 1
exitwhen k > LIGHTNINGS
endloop
// Marks the circle as been destroyed
call SaveBoolean(hash, ID, HashDestroyed(j), true)
// Reduces the number of active circles for the caster
call SaveInteger(hash, ID, HashActiveCircles(), activeCircles - 1)
// Updates the first free index
if j < free then
call SaveInteger(hash, ID, HashFirstFree(), j)
endif
endif
endif
set j = j + 1
exitwhen j > maxloop
endloop
// Updates the interval time used for creating a new circle
call SaveReal(hash, ID, HashTime(), interv)
if circles < LoadInteger(hash, ID, HashWaves()) then
if interv >= INTERVAL then
call Add_Circle(u, ID)
call SaveReal(hash, ID, HashInterval(), 0)
else
call SaveReal(hash, ID, HashInterval(), interv)
endif
endif
else
loop
set k = 0
loop
if HaveSavedHandle(hash, ID, HashLightning(j,k)) then
call DestroyLightning(LoadLightningHandle(hash, ID, HashLightning(j,k)))
call UnitApplyTimedLife(LoadUnitHandle(hash, ID, HashDummy(j,k)), 1 , 0.01)
endif
set k = k + 1
exitwhen k > LIGHTNINGS
endloop
set j = j + 1
exitwhen j > maxloop
endloop
call GroupRemoveUnit(casters, u)
if oid == ORDERID then
call IssueImmediateOrder(u, "stop")
endif
call FlushChildHashtable(hash, ID)
set casts = casts - 1
if casts == 0 then
call PauseTimer(tmr)
endif
endif
set u = null
endfunction
private function Timer_Expire takes nothing returns nothing
call ForGroup(casters, function Loop)
endfunction
private function Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer ID = GetHandleId(u)
local integer level = GetUnitAbilityLevel(u, ABILCODE) - 1
/*~~~ 0 value doesn't need to be saved but I do it to remember ~~~*/
/*~~~~~~~~~~~~~~~ the things that need to be saved ~~~~~~~~~~~~~~~*/
// call SaveReal(hash, ID, HashTime(), 0)
// call SaveReal(hash, ID, HashInterval(), 0)
// call SaveInteger(hash, ID, HashLast(), 0)
// call SaveInteger(hash, ID, HashFirst(), 0)
// call SaveInteger(hash, ID, HashCircles(), 0)
// call SaveInteger(hash, ID, HashFirstFree(), 0)
// call SaveInteger(hash, ID, HashActiveCircles(), 0)
call SaveInteger(hash, ID, HashWaves(), WAVES + WAVES_BONUS * level)
call SaveReal(hash, ID, HashDamage(), DMG_BASE + DMG_BONUS * level)
call SaveReal(hash, ID, HashMana(), MANA_BASE + MANA_BONUS * level)
call SaveReal(hash, ID, HashSafeWidth(), I2R(MANA_LIGHTNINGS) / I2R(LIGHTNINGS)* bj_PI)
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
call Add_Circle(u, ID)
call GroupAddUnit(casters, u)
if casts == 0 then
call TimerStart(tmr , TIMEOUT , true , function Timer_Expire)
endif
set casts = casts + 1
set u = null
endfunction
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == ABILCODE
endfunction
private function Omega_Wave takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(t, function Conditions )
call TriggerAddAction(t, function Actions )
endfunction
endscope