- Joined
- May 2, 2015
- Messages
- 109
I think the spell looks a bit plain. My suggestion is add some more effect,
like when the units cross the wall and when the wall expires.
like when the units cross the wall and when the wall expires.
scope WallOfDeath /*
Wall of Death v1.12
by Flux
Raises a wall of warping light that damages units who crosses it.
OPTIONALLY REQUIRES:
Table - if not found, this spell will resort to creating a hashtable.
Hashtables are limited to 255 per map.
RegisterPlayerUnitEvent/RegisterEventPack - If not found, an extra trigger is created.
SpellEffectEvent - If not found, an extra trigger is created and GetSpellAbilityId() check will occur.
CREDITS:
Bannar - RegisterEvent Pack
Bribe - Table, SpellEffectEvent
Magtheridon96 - RegisterPlayerUnitEvent
*/
globals
//Rawcode of the Spell
private constant integer SPELL_ID = 'AWoD'
//How high is the Wall Visual Line.
private constant real WALL_HEIGHT = 500.0
//When a unit gets a certain distance to the wall, it gets monitored whether it crosses the wall or not
private constant real MONITOR_RANGE = 50.0
//Periodic Timeout
private constant real TIMEOUT = 0.03125
//Determines whether the owner of the wall changes when the owner of the caster changes
private constant boolean CHANGE_WITH_OWNER = false
//Wall Visual Line appearance
private constant string LIGHTNING_ID = "DRAL"
//Wall Effecs
private constant string WALL_SFX = "Abilities\\Spells\\Undead\\Unsummon\\UnsummonTarget.mdl"
//Wall Effect distance to each other
private constant real WALL_SFX_INTERVAL = 150.0
//Note that you have to choose a scalable effect in WALL_SFX
private constant real WALL_SFX_SCALE = 0.75
//Height of Visual Effects
private constant real SFX_HEIGHT = 0
//When DummyRecycler is not found, this configuration will be used
private constant player NEUTRAL_PLAYER = Player(14)
private constant integer DUMMY_ID = 'dumi'
endglobals
native UnitAlive takes unit u returns boolean
private function WallLength takes integer level returns real
return 100.0*level + 700.0
endfunction
private function Duration takes integer level returns real
return 5.0*level + 10.0
endfunction
private function TargetFilter takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitEnemy(target, owner) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)
endfunction
//What happens when a unit crosses the wall
private function WallAction takes unit caster, unit target, integer level returns nothing
call UnitDamageTarget(caster, target, 100.0*level, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
//You can also put all kinds of other stuffs here like stun/silence/root/create illusion of the target
endfunction
private struct WallSfx
private unit u
private effect sfx
private thistype next
private thistype prev
private method remove takes nothing returns nothing
set this.prev.next = this.next
set this.next.prev = this.prev
static if LIBRARY_DummyRecycler then
call DummyAddRecycleTimer(this.u, 3.0)
else
call UnitApplyTimedLife(this.u, 'BTLF', 3.0)
endif
call DestroyEffect(this.sfx)
set this.sfx = null
call this.deallocate()
endmethod
method destroy takes nothing returns nothing
local thistype node = this.next
loop
exitwhen node == this
call node.remove()
set node = node.next
endloop
call this.remove()
endmethod
private method add takes thistype head, real x, real y returns nothing
set this.next = head
set this.prev = head.prev
set this.next.prev = this
set this.prev.next = this
static if LIBRARY_DummyRecycler then
set this.u = GetRecycledDummyAnyAngle(x, y, SFX_HEIGHT)
else
set this.u = CreateUnit(NEUTRAL_PLAYER, DUMMY_ID, x, y, 0)
call SetUnitFlyHeight(this.u, SFX_HEIGHT, 0)
endif
call SetUnitScale(this.u, WALL_SFX_SCALE, 0, 0)
set this.sfx = AddSpecialEffectTarget(WALL_SFX, this.u, "origin")
endmethod
static method ceil takes real r returns integer
local integer i = R2I(r)
if I2R(i) == r then
return i
endif
return i + 1
endmethod
static method create takes real x1, real y1, real length, real angle returns thistype
local thistype head = thistype.allocate()
local thistype node
local real x = x1
local real y = y1
local integer i = thistype.ceil(length/WALL_SFX_INTERVAL)
local real dist = length/i
set head.next = head
set head.prev = head
static if LIBRARY_DummyRecycler then
set head.u = GetRecycledDummyAnyAngle(x, y, SFX_HEIGHT)
else
set head.u = CreateUnit(NEUTRAL_PLAYER, DUMMY_ID, x, y, 0)
call SetUnitFlyHeight(head.u, SFX_HEIGHT, 0)
endif
call SetUnitScale(head.u, WALL_SFX_SCALE, 0, 0)
set head.sfx = AddSpecialEffectTarget(WALL_SFX, head.u, "origin")
loop
exitwhen i == 0
set x = x + dist*Cos(angle)
set y = y + dist*Sin(angle)
set node = thistype.allocate()
call node.add(head, x, y)
set i = i - 1
endloop
return head
endmethod
endstruct
private struct Wall
//Spell Data
private unit caster
private group g
private integer lvl
private real radius
private real duration
private real x
private real y
static if not CHANGE_WITH_OWNER then
private player owner
endif
//Visuals
private lightning topL
private lightning botL
private lightning leftL
private lightning rightL
private WallSfx sfx
//Wall Line Data
private real m //slope
private real b //slope-intercept
//List
private thistype next
private thistype prev
//Unit Data
static if LIBRARY_Table then
private Table pos
else
private static hashtable hash = InitHashtable()
endif
private static group search = CreateGroup()
private static timer t = CreateTimer()
private static thistype global
private method destroy takes nothing returns nothing
set this.prev.next = this.next
set this.next.prev = this.prev
if thistype(0).next == 0 then
call PauseTimer(thistype.t)
endif
//Remove saved positions
static if LIBRARY_Table then
call this.pos.destroy()
else
call FlushChildHashtable(thistype.hash, this)
endif
static if not CHANGE_WITH_OWNER then
set this.owner = null
endif
//Destroy Handles
call this.sfx.destroy()
call DestroyLightning(this.botL)
call DestroyLightning(this.topL)
call DestroyLightning(this.leftL)
call DestroyLightning(this.rightL)
call DestroyGroup(this.g)
set this.caster = null
set this.botL = null
set this.topL = null
set this.leftL = null
set this.rightL = null
set this.g = null
call this.deallocate()
endmethod
private method isAbove takes real x, real y returns boolean
return y > this.m*x + this.b
endmethod
private method distance takes real x, real y returns real
return (this.m*x - y + b)*(this.m*x - y + b)/(this.m*this.m + 1)
endmethod
private static method scanGroup takes nothing returns nothing
local thistype this = thistype.global
local unit u = GetEnumUnit()
local integer id = GetHandleId(u)
if this.distance(GetUnitX(u), GetUnitY(u)) > MONITOR_RANGE*MONITOR_RANGE or not IsUnitInRangeXY(u, this.x, this.y, this.radius)then
call GroupRemoveUnit(this.g, u)
if IsUnitInGroup(u, thistype.search) then
call GroupRemoveUnit(thistype.search, u)
endif
static if LIBRARY_Table then
if this.pos.boolean.has(id) then
call this.pos.boolean.remove(id)
endif
elsea
if HaveSavedBoolean(thistype.hash, this, id) then
call RemoveSavedBoolean(thistype.hash, this, id)
endif
endif
endif
set u = null
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = thistype(0).next
local unit u
local real x
local real y
local integer id
local boolean newPos
static if CHANGE_WITH_OWNER then
local player p = GetOwningPlayer(this.caster)
else
local player p = this.owner
endif
loop
exitwhen this == 0
set this.duration = this.duration - TIMEOUT
if this.duration > 0 then
call GroupEnumUnitsInRange(thistype.search, this.x, this.y, this.radius, null)
set thistype.global = this
call ForGroup(this.g, function thistype.scanGroup)
loop
set u = FirstOfGroup(thistype.search)
exitwhen u == null
call GroupRemoveUnit(thistype.search, u)
if TargetFilter(u, p) then
set id = GetHandleId(u)
set x = GetUnitX(u)
set y = GetUnitY(u)
if this.distance(x, y) <= MONITOR_RANGE*MONITOR_RANGE then
static if LIBRARY_Table then
if this.pos.boolean.has(id) then
set newPos = this.isAbove(x, y)
if this.pos.boolean[id] != newPos then
call WallAction(this.caster, u, this.lvl)
endif
set this.pos.boolean[id] = newPos
else
set this.pos.boolean[id] = this.isAbove(x, y)
call GroupAddUnit(this.g, u)
endif
else
if HaveSavedBoolean(thistype.hash, this, id) then
set newPos = this.isAbove(x, y)
if LoadBoolean(thistype.hash, this, id) != newPos then
call WallAction(this.caster, u, this.lvl)
endif
call SaveBoolean(thistype.hash, this, id, newPos)
else
call SaveBoolean(thistype.hash, this, id, this.isAbove(x, y))
call GroupAddUnit(this.g, u)
endif
endif
endif
endif
endloop
else
call this.destroy()
endif
set this = this.next
endloop
set p = null
endmethod
private static method onCast takes nothing returns boolean
local thistype this = thistype.allocate()
local real angle
local real ex1
local real ex2
local real ey1
local real ey2
//Get Spell Data
set this.caster = GetTriggerUnit()
set this.g = CreateGroup()
set this.lvl = GetUnitAbilityLevel(this.caster, SPELL_ID)
set this.x = GetSpellTargetX()
set this.y = GetSpellTargetY()
set this.radius = 0.5*WallLength(this.lvl)
set this.duration = Duration(this.lvl)
static if LIBRARY_Table then
set this.pos = Table.create()
endif
static if not CHANGE_WITH_OWNER then
set this.owner = GetTriggerPlayer()
endif
//Get Line Data
set angle = GetUnitFacing(this.caster)*bj_DEGTORAD - 0.5*bj_PI
set ex1 = this.x + this.radius*Cos(angle)
set ey1 = this.y + this.radius*Sin(angle)
set angle = angle + bj_PI
set ex2 = this.x + this.radius*Cos(angle)
set ey2 = this.y + this.radius*Sin(angle)
set this.m = (ey2 - ey1)/(ex2 - ex1)
set this.b = ey1 - this.m*ex1
//Create Lightning effects
set this.botL = AddLightningEx(LIGHTNING_ID, true, ex1, ey1, 0, ex2, ey2, 0)
set this.topL = AddLightningEx(LIGHTNING_ID, true, ex1, ey1, WALL_HEIGHT, ex2, ey2, WALL_HEIGHT)
set this.leftL = AddLightningEx(LIGHTNING_ID, true, ex2, ey2, 0, ex2, ey2, WALL_HEIGHT)
set this.rightL = AddLightningEx(LIGHTNING_ID, true, ex1, ey1, 0, ex1, ey1, WALL_HEIGHT)
//Create Sfx
set this.sfx = WallSfx.create(ex1, ey1, 2*this.radius, angle)
//List insertion
set this.next = thistype(0)
set this.prev = thistype(0).prev
set this.next.prev = this
set this.prev.next = this
if this.prev == 0 then
call TimerStart(thistype.t, TIMEOUT, true, function thistype.onPeriod)
endif
return false
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method cond takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID and thistype.onCast()
endmethod
endif
private static method onInit takes nothing returns nothing
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
elseif RPUE_VERSION_NEW then
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.cond)
elseif LIBRARY_RegisterPlayerUnitEvent then
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.cond)
else
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.cond))
endif
endmethod
endstruct
endscope