Name | Type | is_array | initial_value |
--[[
function list:
- print_after(string)
prints string after a zero-second timer.
- is_function(func or table)
Returns true if it is a function
or if it is a table and its'
metatable has a __call metamethod.
- readonly_t(opt o)
Returns a readonly table.
Readonly tables as metatables
will allow assignments of
entries which do not exist
in said table.
- protected_t(opt o)
Returns a protected table.
Protected tables as metatables
do not permit assignments of
entries which do not exist in
said table.
- is_readonly_table(o)
Returns true if the table is readonly
- is_protected_table(o)
Returns true if the table is protected
]]
do
do
local WeakTable = {keys = {__mode='k'}, values = {__mode='v'}, keyvalues = {__mode='kv'}}
local TablePointers = {k = WeakTable.keys, v = WeakTable.values, kv = WeakTable.keyvalues}
-- Due to how wc3 works, returning a function directly
-- may not correctly compile
-- local f
local Container = {
index = function(mt)
return --[[f =]] function(t, k)
return mt[k]
end
-- return f
end,
newindex = function(mt, doNotWrite)
return --[[f =]] function(t, k)
if mt[k] then return end
if doNotWrite then return end
rawset(t, k, v)
end
-- return f
end,
is_readonly = {},
is_protected = {}
}
_G['WeakTable'] = WeakTable
function weaktable(t, mode)
t, mode = t or {}, mode or "k";
return setmetatable(t, TablePointers[mode])
end
function readonly_t(o)
o = o or {}
o.__index = Container.index(o)
o.__newindex = Container.newindex(o, false)
Container.is_readonly[o] = true
return o
end
function protected_t(o)
o = o or {}
o.__index = Container.index(o)
o.__newindex = Container.newindex(o, true)
Container.is_protected[o] = true
return o
end
function is_readonly_table(o)
return Container.is_readonly[o]
end
function is_protected_table(o)
return Container.is_protected[o]
end
end
function print_after(msg, delay)
delay = delay or 0.00
TimerStart(CreateTimer(), delay, false, function() print(msg) DestroyTimer(GetExpiredTimer()) end)
end
function is_function(func)
if type(func) == 'table' then
return getmetatable(func).__call and (is_function(getmetatable(func).__call))
end
return type(func) == 'function'
end
end
function CC2Four(cc)
local str = ""
local j = 1
while j <= 4 do
local i = math.floor(math.fmod(cc, 256))
str = string.char(i) .. str
cc = math.floor(cc/256)
j = j + 1
end
return str
end
VersionCheck = setmetatable({}, protected_t())
do
local m_vers = getmetatable(VersionCheck)
m_vers.patch = (BlzStartUnitAbilityCooldown and "1.32") or "1.31"
end
--[[
--------------
NTable
--------------
NTable:create(size, constructor, destructor)
NTable:new(size, constructor, destructor)
NTable(size, constructor, destructor)
- Returns a table with a preloaded list of tables defined by size.
- constructor is a function with variable arguments.
- destructor is a function with one argument, the destroyed table instance.
NTable:flush(table)
- Explicitly recycles the table back into the list of tables, if and only if
the table belongs to the NTable object.
NTable:destroy()
- Flushes all associated tables to the NTable object, then nullifies
the NTable object.
]]
do
local tab = setmetatable({PRELOAD_SIZE=20, MAX_ALLOC_RATIO=1.25, DEBUG=false}, readonly_t())
local mtab = getmetatable(tab)
local clear = {flag=false}
local func_tables = {construct=weaktable({},'k'), destroy=weaktable({},'k'), shadow=weaktable({},'k')}
local m_table = weaktable({},'k')
local meta = {set = setmetatable, get = getmetatable}
local cache_clear = setmetatable({}, {__gc = function(t)
clear.flag = (not IsPlayerSlotState(GetLocalPlayer(), PLAYER_SLOT_STATE_PLAYING))
end})
mtab.__metatable = tab
mtab.__index = mtab
mtab.__newindex = function(t, k, v) if mtab[k] then return; end; rawset(t, k, v) end
_G['NTable'] = tab
-- Override _print to be defined only at compiletime
local _print = print
if tab.DEBUG then
local function print(msg)
_print(msg)
end
else
local function print(msg)
end
end
local function recycle(self, t)
if func_tables.destroy[self] then
-- This will call a destructor function when present.
func_tables.destroy[self](t)
end
self.list[#self.list + 1] = t
self.list.index[t] = #self.list
end
local n_shadow = {__gc = function(t)
local self = func_tables.shadow[t]
if m_table[t] and m_table[t].__gc then
m_table[t].__gc(t)
end
m_table[t] = nil
if (not clear.flag) and self.list.total.index[t] then
recycle(self, t)
end
end, __mode='k'}
function getmetatable(t)
if func_tables.shadow[t] then
-- This is an NTable requesting its' metatable
if m_table[t] and m_table[t].__metatable then
return m_table[t].__metatable
end
return m_table[t]
end
return meta.get(t)
end
function setmetatable(t, mt)
if func_tables.shadow[t] then
-- This is a sub-table of a Child table
if m_table[t] and m_table[t].__metatable then
return t
end
if mt ~= n_shadow then
m_table[t] = mt
end
return t
end
return meta.set(t, mt)
end
--[[
-- o.list stores NTable instances, so it must be a strong table.
-- since o.list is a strong table, o.list.index for NTable instances
-- must be a weak table. (Stores indices as values).
-- o.list.total behaves similarly to o.list, except o.list.total
-- stores all current NTable instances, whether pending or active.
-- As such, it must be a weak table.
]]
local function new_table(o)
o = o or {}
o.list = {index=weaktable({}, 'k'), total=weaktable({index=weaktable({}, 'k')}, 'v')}
o._SIZE = 0
o._MAX_SIZE = 0
setmetatable(o, mtab)
return o
end
function mtab:create(size, constructor, destructor)
local is_const_func = is_function(constructor)
local is_dest_func = is_function(destructor)
local o = new_table()
size = math.max(size or tab.PRELOAD_SIZE, 1)
o._SIZE = size
o._MAX_SIZE = math.floor(size*tab.MAX_ALLOC_RATIO)
if is_const_func then func_tables.construct[o] = constructor end
if is_dest_func then func_tables.destroy[o] = destructor end
for i = 1,size do
o.list[i] = {}
o.list.index[o.list[i] ] = i
o.list.total[i] = o.list[i]
o.list.total.index[o.list[i] ] = i
func_tables.shadow[o.list[i] ] = o
meta.set(o.list[i], n_shadow)
end
return o
end
function mtab:new(size, constructor, destructor)
return self:create(size, constructor, destructor)
end
function mtab:__call(...)
if self == tab then
return self:create(...)
end
local o, total; total = self.total_list
if #self.list > 0 then
o = self.list[#self.list]
self.list[#self.list] = nil
self.list.index[o] = nil
else
o = {}
func_tables.shadow[o] = self
meta.set(o, n_shadow)
if #total < self._MAX_SIZE then
total[#total + 1] = o
total.index[o] = #total
end
end
if func_tables.construct[self] then func_tables.construct[self](o, ...) end
return o
end
function mtab:flush(t)
-- Check if the table to be flushed belongs to the flushing table (self).
if func_tables.shadow[t] == self then
-- Forcibly collects the table.
n_shadow.__gc(t)
end
end
function mtab:constructor(func)
if is_function(func) then func_tables.construct[self] = constructor end
end
function mtab:destructor(func)
if is_function(func) then func_tables.destroy[self] = destructor end
end
function mtab:destroy()
if self == tab then return end
for i=1,#self.list.total do
self:flush(self.list.total[i])
end
func_tables.construct[self] = nil
func_tables.destroy[self] = nil
self._MAX_SIZE = nil
self._SIZE = nil
self.list.total.index = nil
self.list.total = nil
self.list.index = nil
self.list = nil
end
function n_shadow.__index(t, k)
if m_table[t] then
if m_table[t].__index then
if type(m_table[t].__index) == 'function' then
return m_table[t].__index(t, k)
end
return m_table[t].__index[k]
end
end
return nil
end
function n_shadow.__newindex(t, k, v)
if m_table[t] then
if m_table[t].__newindex then
m_table[t].__newindex(t, k, v)
end
end
rawset(t, k, v)
end
function n_shadow.__call(t, ...)
if m_table[t] then
if m_table[t].__call then
return m_table[t].__call(t, ...)
end
end
return nil
end
function n_shadow.__tostring(t)
if m_table[t] then
if m_table[t].__tostring then
return m_table[t].__tostring(t)
end
end
return tostring(t)
end
function n_shadow.__unm(t)
if m_table[t] then
if m_table[t].__unm then
return m_table[t].__unm(t)
end
end
return nil
end
function n_shadow.__len(t)
if m_table[t] then
if m_table[t].__len then
return m_table[t].__len(t)
end
end
meta.set(t, nil)
local result = #t
meta.set(t, n_shadow)
return result
end
function n_shadow.__add(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__add then
return m_table[lhs].__add(lhs, rhs)
end
end
return nil
end
function n_shadow.__sub(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__sub then
return m_table[lhs].__sub(lhs, rhs)
end
end
return nil
end
function n_shadow.__mul(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__mul then
return m_table[lhs].__mul(lhs, rhs)
end
end
return nil
end
function n_shadow.__div(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__div then
return m_table[lhs].__div(lhs, rhs)
end
end
return nil
end
function n_shadow.__idiv(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__idiv then
return m_table[lhs].__idiv(lhs, rhs)
end
end
return nil
end
function n_shadow.__mod(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__mod then
return m_table[lhs].__mod(lhs, rhs)
end
end
return nil
end
function n_shadow.__pow(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__pow then
return m_table[lhs].__pow(lhs, rhs)
end
end
return nil
end
function n_shadow.__concat(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__concat then
return m_table[lhs].__concat(lhs, rhs)
end
end
return nil
end
function n_shadow.__eq(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__eq then
return m_table[lhs].__eq(lhs, rhs)
end
end
return false
end
function n_shadow.__lt(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__lt then
return m_table[lhs].__lt(lhs, rhs)
end
end
return false
end
function n_shadow.__le(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__le then
return m_table[lhs].__le(lhs, rhs)
elseif m_table[lhs].__lt then
return not m_table[lhs].__lt(rhs, lhs)
end
end
return false
end
function n_shadow.__band(lhs,rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__band then
return m_table[lhs].__band(lhs, rhs)
end
end
return 0
end
function n_shadow.__bor(lhs,rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__bor then
return m_table[lhs].__bor(lhs, rhs)
end
end
return 1
end
function n_shadow.__bxor(lhs,rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__bxor then
return m_table[lhs].__bxor(lhs, rhs)
end
end
return 0
end
function n_shadow.__bnot(lhs,rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__bnot then
return m_table[lhs].__bnot(lhs, rhs)
end
end
return 0
end
function n_shadow.__shl(lhs,rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__shl then
return m_table[lhs].__shl(lhs, rhs)
end
end
return 1
end
function n_shadow.__shr(lhs,rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__shr then
return m_table[lhs].__shr(lhs, rhs)
end
end
return 2
end
end
--[[
Initializer Behavior:
Initializer will initialize all registered functions
or tables in itself at different stages.
Stages:
SYSTEM
TRIGGER
USER
If a function or table is registered more than once,
the function will be executed based on the order
by which it was registered in.
The Initializer functions expect functions with no
arguments.
function list:
- Initializer.register(func, opt string prio)
Registers a given function or table
to an initialization stage indicated by
priority.
prio is assumed to be a string, and
can only be valid for the following:
"SYSTEM"
"TRIGGER"
"USER"
Leaving prio empty usually results into
it defaulting to "USER"
- Initializer.registerBJ(string prio, func)
Internally calls Initializer.register
This is more intuitive to use, but
slower.
]]
do
local init = setmetatable({}, protected_t())
local m_init = getmetatable(init)
local list = {
SYSTEM = {},
TRIGGER = {},
USER = {},
}
Initializer = init
m_init.__metatable = init
m_init.list = list
m_init._DONE = {
SYSTEM = false,
TRIGGER = false,
USER = false
}
m_init.index = 0
m_init.result = 1
function m_init.register(func, prio)
if not is_function(func) then return end
if type(prio) ~= "string" or (not list[prio]) then
prio = "USER"
end
list[prio][#list[prio] + 1] = func
end
function m_init.registerBJ(prio, func) m_init.register(func, prio) end
function m_init:__call(prio, func) m_init.register(func, prio) end
function m_init.initialized(prio)
prio = ((not list[prio] or (prio == nil)) and "USER") or prio
return m_init._DONE[prio]
end
-- Initialize le functions
local _SetMapMusic = SetMapMusic
function SetMapMusic(musicname, random, index)
_SetMapMusic(musicname, random, index)
SetMapMusic = _SetMapMusic
local size = #list.SYSTEM
for i = 1, size do
m_init.result = (pcall(list.SYSTEM[i]))
size = #list.SYSTEM
end
m_init._DONE.SYSTEM = true
local hasCustomInit = RunInitializationTriggers ~= nil
local tempVar = ""
local tempCustomTriggers
if hasCustomInit then
tempCustomTriggers = RunInitializationTriggers
tempVar = "RunInitializationTriggers"
else
tempCustomTriggers = InitGlobals
tempVar = "InitGlobals"
end
_G[tempVar] = function()
size = #list.TRIGGER
for i = 1, size do
m_init.result = (pcall(list.TRIGGER[i]))
size = #list.TRIGGER
end
m_init._DONE.TRIGGER = true
tempCustomTriggers()
_G[tempVar] = tempCustomTriggers
size = #list.USER
for i = 1, size do
m_init.result = (pcall(list.USER[i]))
size = #list.USER
end
m_init._DONE.USER = true
end
end
end
WorldRect = setmetatable({}, protected_t())
do
local m_rect = getmetatable(WorldRect)
m_rect.__metatable = WorldRect
Initializer("SYSTEM", function()
m_rect.reg = CreateRegion()
m_rect.rect = GetWorldBounds()
m_rect.rectMaxX = GetRectMaxX(m_rect.rect)
m_rect.rectMaxY = GetRectMaxY(m_rect.rect)
m_rect.rectMinX = GetRectMinX(m_rect.rect)
m_rect.rectMinY = GetRectMinY(m_rect.rect)
m_rect.rectX = GetRectCenterX(m_rect.rect)
m_rect.rectY = GetRectCenterY(m_rect.rect)
RegionAddRect(m_rect.reg, m_rect.rect)
end)
end
EventListener = setmetatable({}, protected_t())
do
local listener = EventListener
local m_listener = getmetatable(listener)
local DEBUG_MODE = false
local object_lists = {}
local params = {it={}, jt={}, kt={}, lt={}, mt={}, ft={}} -- An internal table dedicated to local
-- variables.
m_listener.__obj_list = object_lists
if DEBUG_MODE then
local function print(msg, env, prf, sf)
env = env or ""
prf = prf or ""
sf = sf or ""
msg = "EventListener" .. prf .. env .. sf .. " >> " .. msg
DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 3600, msg)
end
end
if not is_function then
local function is_function(func)
if type(func) == 'table' then
return is_function(getmetatable(func).__call)
else
return type(func) == 'function'
end
end
end
function m_listener:new(o)
o = o or {}
object_lists[o] = {}
object_lists[o].func = {} -- Stack of functions.
object_lists[o].pointer = {} -- Table containing functions as keys, which point to indices
-- in the stack of functions.
object_lists[o].reps = {} -- The amount of repetitions for each associated function.
-- ("associated" means that the entry is accessed the same
-- way as the access of functions in the stack (via index).)
object_lists[o].call = {} -- This refers to the number of times a function has been called
-- front-end recursively. This allows the system to stop the
-- execution of a problematic trigger if wanted.
object_lists[o].maxcall = 0 -- By default, all functions in the stack can be called an
-- infinite amount of times. This can be modified to ensure
-- that the game will gracefully terminate the function
-- while in a certain recursion depth.
object_lists[o].ex_count = 0 -- This refers to the number of times the EventListener
-- object has executed its' stack of functions via
-- EventListener:conditionalExec.
object_lists[o].dereg = {} -- This will exist for functions which have been deregistered
-- while in an EventListener:conditionalExec iteration.
-- If such a value exists for said function, the function
-- will be removed from the EventListener object, after
-- execution has finished. This can be countered via
-- EventListener:register.
object_lists[o].dest_count = 0
setmetatable(o, m_listener)
return o
end
function m_listener:create(o)
return self:new(o)
end
function m_listener:__call(o)
return self:new(o)
end
function m_listener:register(func, rep)
rep = rep or 1
if not is_function(func) then return false; end
if self == listener then return false; end
if not object_lists[self].pointer[func] then
-- The function is not in the stack of functions for this EventListener
params.index = #object_lists[self].func + 1
params.pointer = params.index
object_lists[self].func[params.index] = func
object_lists[self].reps[params.index] = 0
object_lists[self].call[params.index] = 0
object_lists[self].pointer[func] = params.index
else
params.pointer = object_lists[self].pointer[func]
end
if object_lists[self].dereg[params.pointer] then
object_lists[self].dereg[params.pointer] = object_lists[self].dereg[params.pointer] - rep
if object_lists[self].dereg[params.pointer] <= 0 then
object_lists[self].reps[params.pointer] = -object_lists[self].dereg[params.pointer]
object_lists[self].dereg[params.pointer] = nil
end
else
object_lists[self].reps[params.pointer] = object_lists[self].reps[params.pointer] + rep
end
return true
end
function m_listener:deregister(func, rep)
rep = rep or 1
if not is_function(func) then return false; end
if self == listener then return false; end
if not object_lists[self].pointer[func] then
return false
end
params.pointer = object_lists[self].pointer[func]
object_lists[self].reps[params.pointer] = object_lists[self].reps[params.pointer] - rep
if object_lists[self].reps[params.pointer] <= 0 then
-- Proceed with removal of the function from the stack.
if object_lists[self].ex_count > 0 then
if not object_lists[self].dereg[func] then
object_lists[self].dereg[func] = -object_lists[self].reps[params.pointer]
else
object_lists[self].dereg[func] = object_lists[self].dereg[func] + rep
end
object_lists[self].reps[params.pointer] = 0
return false
end
params.i = params.pointer
params.j = #object_lists[self].func
object_lists[self].pointer[func] = nil
object_lists[self].dereg[func] = nil
while params.i < params.j do
params.func = object_lists[self].func[params.i+1]
object_lists[self].reps[params.i] = object_lists[self].reps[params.i+1]
object_lists[self].func[params.i] = params.func
object_lists[self].pointer[params.func] = params.i
params.i = params.i + 1
end
object_lists[self].reps[params.j] = nil
object_lists[self].func[params.j] = nil
end
return true
end
function m_listener:destroy()
if self == listener then return end
if object_lists[self].ex_count > 0 then
object_lists[self].dest_count = object_lists[self].dest_count + 1
return
end
params.j = #object_lists[self].func
while params.j > 0 do
self:deregister(object_lists[self].func[params.j], object_lists[self].reps[params.j])
params.j = #object_lists[self].func
end
object_lists[self].func = nil
object_lists[self].pointer = nil
object_lists[self].reps = nil
object_lists[self].call = nil
object_lists[self].maxcall = nil
object_lists[self].ex_count = nil
object_lists[self].dest_count = nil
object_lists[self].dereg = nil
object_lists[self] = nil
end
local function checkForDestroy(self)
if object_lists[self].dest_count > 0 then
self:destroy()
return true
end
params.j = #object_lists[self].func
while params.j > 0 do
if object_lists[self].dereg[params.j] then
-- The function is to be removed from the stack of functions.
params.k = params.j
self:deregister(object_lists[self].func[params.j], object_lists[self].reps[params.j])
params.j = params.k
end
params.j = params.j - 1
end
return false
end
function m_listener:conditional_exec(cond, checkRecur, ...)
if checkRecur == nil then checkRecur = true end
local is_cond_func = is_function(cond)
object_lists[self].ex_count = object_lists[self].ex_count + 1
params.it[#params.it + 1] = 1
params.jt[#params.jt + 1] = #object_lists[self].func
while params.it[#params.it] <= params.jt[#params.jt] do
-- Check if the function can proceed to be executed.
if checkRecur then
params.kt[#params.it] = (object_lists[self].call[params.it[#params.it]] < object_lists[self].maxcall)
params.kt[#params.it] = (params.kt[#params.it] and (object_lists[self].maxcall > 0)) or (object_lists[self].maxcall <= 0)
else
params.kt[#params.it] = true
end
params.lt[#params.it] = 1
params.mt[#params.it] = object_lists[self].reps[params.it[#params.it]]
params.ft[#params.it] = object_lists[self].func[params.it[#params.it]]
if (checkRecur and params.kt[#params.it]) or not checkRecur then
-- The amount of executions exceed that of maximum calls.
-- The EventListener object does not have a restriction on
-- recursive loops, call it anyway.
if (is_cond_func and cond()) or (not is_cond_func and cond) then
if object_lists[self].dest_count <= 0 then
while params.lt[#params.it] <= params.mt[#params.it] do
object_lists[self].call[params.it[#params.it]] = object_lists[self].call[params.it[#params.it]] + 1
pcall(params.ft[#params.it], ...)
object_lists[self].call[params.it[#params.it]] = object_lists[self].call[params.it[#params.it]] - 1
params.lt[#params.it] = params.lt[#params.it] + 1
end
end
end
end
params.it[#params.it] = params.it[#params.it] + 1
params.jt[#params.jt] = #object_lists[self].func
end
params.ft[#params.it] = nil
params.mt[#params.it] = nil
params.lt[#params.it] = nil
params.kt[#params.it] = nil
params.jt[#params.jt] = nil
params.it[#params.it] = nil
object_lists[self].ex_count = object_lists[self].ex_count - 1
checkForDestroy(self)
end
function m_listener:conditionalExec(cond, checkRecur, ...)
self:conditional_exec(cond, checkRecur, ...)
end
function m_listener:execute(...)
self:conditional_exec(true, true, ...)
end
function m_listener:set_recursion_count(value)
value = ((type(value) ~= 'number') and 0) or math.floor(value)
object_lists[self].maxcall = value
end
function m_listener:set_recursion_depth(value)
self:set_recursion_count(value)
end
end
do
local prio = setmetatable({}, protected_t())
local m_prio = getmetatable(prio)
m_prio.__metatable = prio
PriorityEvent = prio
function m_prio:new(o)
o = o or {}
o.listeners = {}
o.listeners.priority = {}
o.listeners.min = 0
o.listeners.max = 1
o.listeners[0] = EventListener:create()
o.listeners[1] = EventListener:create()
o.listeners.priority[1] = 0
o.listeners.priority[2] = 1
o.listeners.size = 2
setmetatable(o, m_prio)
return o
end
function m_prio:create(o)
return self:new(o)
end
function m_prio:__call(o)
return self:new(o)
end
local function is_prio_in(self, prior)
local j = self.listeners.size
local n = 1
local mid = 0
-- A direct check for the outliers (worst-case scenario averted)
if prior >= self.listeners.max then
if prior == self.listeners.max then return true, j else return false, j + 1 end
elseif prior <= self.listeners.min then
if prior == self.listeners.min then return true, 1 else return false, 1 end
end
while true do
mid = math.floor((j + n)/2)
if prior == self.listeners.priority[mid] then
return true, mid
else
if mid == n or mid == j then break end
if prior > self.listeners.priority[mid] then
n = mid
elseif prior < self.listeners.priority[mid] then
j = mid
end
end
end
return false, mid + 1
end
local function insert(self, prior)
local flag, j = is_prio_in(self, prior)
local k = self.listeners.size
if flag then return flag, j end
-- Insertion sort
while k >= j do
self.listeners.priority[k + 1] = self.listeners.priority[k]
k = k - 1
end
self.listeners.priority[j] = prior
self.listeners.size = self.listeners.size + 1
if j == self.listeners.size then
self.listeners.max = prior
elseif j == 1 then
self.listeners.min = prior
end
return flag, j
end
function m_prio:register(prior, func)
if not is_function(func) then return end
prior = ((type(prior) ~= 'number') and self.listeners.min) or math.floor(prior)
if not select(1, insert(self, prior)) then
self.listeners[prior] = EventListener:create()
end
self.listeners[prior]:register(func)
end
function m_prio:deregister(prior, func)
if not is_function(func) then return end
prior = ((type(prior) ~= 'number') and self.listeners.min) or math.floor(prior)
if not select(1, is_prio_in(self, prior)) then return end
self.listeners[prior]:deregister(func)
end
function m_prio:conditional_fire(prior, cond)
prior = ((type(prior) ~= 'number') and self.listeners.min) or math.floor(prior)
if self.listeners[prior] then self.listeners[prior]:conditionalExec(cond) end
end
function m_prio:fire(prior)
self:conditional_fire(prior, true)
end
function m_prio:conditional_fire_to(upper, lower, cond, checkRecur, ...)
upper, lower = math.max(upper, lower), math.min(upper, lower)
local __, j = is_prio_in(self, upper)
local ___, k = is_prio_in(self, lower)
if j > self.listeners.size then j = self.listeners.size end
for i = j,k,-1 do
self.listeners[self.listeners.priority[i] ]:conditionalExec(cond, checkRecur, ...)
end
end
function m_prio:fire_to(upper, lower, ...)
self:conditional_fire_to(upper, lower, true, true, ...)
end
function m_prio:fire_all(...)
self:conditional_fire_to(self.listeners.max, self.listeners.min, true, true, ...)
end
function m_prio:conditional_fire_all(cond, checkRecur, ...)
self:conditional_fire_to(self.listeners.max, self.listeners.min, cond, checkRecur, ...)
end
function m_prio:set_prio_recursion(prior, value)
if not select(1, is_prio_in(self, prior)) then return false end
self.listeners[prior]:set_recursion_count(value)
return true
end
end
if VersionCheck.patch == "1.31" then
do
local SWEEP_INTERVAL = 10.00
Initializer.registerBJ("SYSTEM", function()
TimerStart(CreateTimer(), SWEEP_INTERVAL, true, function()
collectgarbage()
collectgarbage()
end)
end)
end
end
--[[
-- Originally made by Almia, this was ported over to LUA.
-- Link to the JASS library: https://www.hiveworkshop.com/threads/beziereasing.310514/
BezierEasing /* 1.0.0
*************************************************************************************
*
* Build Cubic Bezier-based Easing functions
*
* Instead of solving for the point on the cubic bezier curve, BezierEasing
* solves for output Y where X is the input.
*
* Useful for adjusting animation rate smoothness
*
*************************************************************************************
*
* struct BezierEasing extends array
*
* static method create takes real ax, real ay, real bx, real by returns thistype
* - points (ax, ay) and (bx, by) are cubic bezier control points on 2D plane.
* - cx = cubic(0, ax, bx, 1)
* - cy = cubic(0, ay, by, 1)
* method operator [] takes real t returns real
* - real "t" is the given time progression whose value in [0..1] range
*
*************************************************************************************/
]]
BezierEasing = setmetatable({}, protected_t())
do
local bezier = BezierEasing
local m_bez = getmetatable(bezier)
local _index = m_bez.__index
m_bez.__metatable = bezier
--[[
/*
* Adjust precision of epsilon's value
* higher precision = lower performance
*
* May cause infinite loop if the
* precision is too high.
*/
]]
local EPSILON = 0.00001
local function Abs(a)
return math.abs(a)
end
local function Max(a, b)
return math.max(a, b)
end
--[[
/*
* Float Equality Approximation
* Accuracy is influenced by EPSILON's value
*/
]]
local function Equals(a,b)
return Abs(a - b) <= EPSILON*Max(1., Max(Abs(a), Abs(b)))
end
local function Bezier3(a, b, c, d, t)
local x = 1 - t
return x*x*x*a + 3*x*x*t*b + 3*x*t*t*c + t*t*t*d
end
function m_bez.create(ax, ay, bx, by)
local o = o or setmetatable({x1 = ax or 0, x2 = bx or 0, y1 = ay or 0, y2 = by or 0}, m_bez)
return o
end
function m_bez.__index(t, k)
if type(k) == 'number' then
--[[
* Perform binary search for the equivalent points on curve
* by using the t factor of cubic beziers, where the input
* is equal to the bezier point's x, and the output is the
* point's y, respectively.
]]
local lo, hi = 0., 1.
--[[
* Since bezier points lies within
* the [0, 1] bracket, just return
* the bound values.
]]
if (Equals(k, 0.)) then
return 0.
elseif (Equals(k, 1.)) then
return 1.
end
--[[
* Binary Search
]]
while true do
local mid = (lo + hi)*0.5
local tx = Bezier3(0, t.x1, t.x2, 1, mid)
local ty = Bezier3(0, t.y1, t.y2, 1, mid)
if (Equals(k, tx)) then
return ty
elseif (k < tx) then
hi = mid
else
lo = mid
end
end
return 0.
else
return _index(t, k)
end
end
function m_bez:destroy()
self.x1, self.y1, self.x2, self.y2 = nil
setmetatable(self, nil)
end
BezierEase = {
inSine = m_bez.create(0.47, 0, 0.745, 0.715),
outSine = m_bez.create(0.39, 0.575, 0.565, 1),
inOutSine = m_bez.create(0.445, 0.05, 0.55, 0.95),
inQuad = m_bez.create(0.55, 0.085, 0.68, 0.53),
outQuad = m_bez.create(0.25, 0.46, 0.45, 0.94),
inOutQuad = m_bez.create(0.455, 0.03, 0.515, 0.955),
inCubic = m_bez.create(0.55, 0.055, 0.675, 0.19),
outCubic = m_bez.create(0.215, 0.61, 0.355, 1),
inOutCubic = m_bez.create(0.645, 0.045, 0.355, 1),
inQuart = m_bez.create(0.895, 0.03, 0.685, 0.22),
outQuart = m_bez.create(0.165, 0.84, 0.44, 1),
inOutQuart = m_bez.create(0.77, 0, 0.175, 1),
inQuint = m_bez.create(0.755, 0.05, 0.855, 0.06),
outQuint = m_bez.create(0.23, 1, 0.32, 1),
inOutQuint = m_bez.create(0.86, 0, 0.07, 1),
inExpo = m_bez.create(0.95, 0.05, 0.795, 0.035),
outExpo = m_bez.create(0.19, 1, 0.22, 1),
inOutExpo = m_bez.create(1, 0, 0, 1),
inCirc = m_bez.create(0.6, 0.04, 0.98, 0.335),
outCirc = m_bez.create(0.075, 0.82, 0.165, 1),
inOutCirc = m_bez.create(0.785, 0.135, 0.15, 0.86),
inBack = m_bez.create(0.6, -0.28, 0.735, 0.045),
outBack = m_bez.create(0.175, 0.885, 0.32, 1.275),
inOutBack = m_bez.create(0.68, -0.55, 0.265, 1.55),
easeInOut = m_bez.create(0.4, 0, 0.6, 1),
linear = m_bez.create(0, 0, 1, 1),
}
end
do
function cachetable(a, b, interval, func)
interval = interval or 1
local tb, j; tb = {next={}}
local i = 1
while a < b do
tb[i] = func(a)
tb.next[i] = i + 1
a = a + interval
i = i + 1
end
tb.next[#tb.next] = 1
tb.start = 1
return tb
end
math.cache = cachetable
function math.sign(x)
return (x > 0 and 1) or (x < 0 and -1) or 0
end
end
do
local wtable = protected_t()
-- Consider table {1, 1, 2, 1}
-- Synthesized Weight {1, 2, 3, 5}
function wtable:get_weight_index(val)
if val >= self[#self] then return #self end
if val < 1 then return 0 end
local low, high = 1, #self
while true do
local mid = math.floor((low + high)/2)
if val > self[mid] then
if low == mid then return mid end
low = mid
elseif val < self[mid] then
high = mid
elseif val == self[mid] then
return mid
end
end
end
function weightedtable(...)
local j = select('#', ...)
if j <= 0 then return nil; end
local tb = {...}
tb.sum = 0
tb.trail = 1
for i = 1, j do
tb.sum = tb.sum + tb[i]
tb.trail = tb.trail + tb[i]
tb[i] = tb.trail - tb[i]
end
tb.trail = nil
setmetatable(tb, wtable)
return tb
end
end
Vector2 = setmetatable({}, protected_t())
do
local vect = Vector2
local m_vect = getmetatable(vect)
local vect_alloc = NTable(256)
m_vect.__metatable = vect
vect_alloc:constructor(function(o, x, y) o.x, o.y = x or 0, y or 0; end)
vect_alloc:destructor(function(o) o.x, o.y = nil; end)
function isvector(o)
return getmetatable(o) == vect
end
function m_vect.create(x, y, o)
o = o or setmetatable(vect_alloc(x, y), m_vect)
return o
end
function m_vect.new(x, y)
return m_vect.create(x, y)
end
function m_vect:__call(x, y)
return m_vect.create(x, y)
end
function m_vect:destroy()
if self == vect then return end
vect_alloc:flush(self)
end
function m_vect.__add(lhs, rhs)
return vect(lhs.x + rhs.x, lhs.y + rhs.y)
end
function m_vect.__sub(lhs, rhs)
return vect(lhs.x - rhs.x, lhs.y - rhs.y)
end
function m_vect.__mul(lhs, rhs)
return vect(lhs.x*rhs.y, -lhs.y*rhs.x)
end
function m_vect.__unm(vec)
return vect(-vec.x, -vec.y)
end
function m_vect.__tostring(vec)
return "Vector: (" .. tostring(vec.x) .. ', ' .. tostring(vec.y) .. ')'
end
function m_vect.__len(vec)
return math.sqrt(vec.x*vec.x + vec.y*vec.y)
end
function m_vect:norm()
local norm = #self
return vect(self.x/norm, self.y/norm)
end
function m_vect:dot(vec2)
return self.x*vec2.x + self.y*vec2.y
end
m_vect.ORIGIN = vect(0, 0)
end
MyBar = setmetatable({}, protected_t())
do
local toc_present = false
Initializer.registerBJ("SYSTEM", function()
toc_present = BlzLoadTOCFile("war3mapimported\\mybar.toc")
if not toc_present then
print_after("MyBar.initialization >> war3mapimported\\mybar.toc not found.")
return
else
-- print_after("MyBar.initialization >> war3mapimported\\mybar.toc was loaded.")
end
end)
end
do
GetTriggerPlayerUnitEventId = setmetatable({}, {__call = function(t)
local str = GetTriggerEventId()
if not t[str] then
t[str] = ConvertPlayerUnitEvent(GetHandleId(str))
end
return t[str]
end})
GetTriggerUnitEventId = setmetatable({}, {__call = function(t)
local str = GetTriggerEventId()
if not t[str] then
t[str] = ConvertUnitEvent(GetHandleId(str))
end
return t[str]
end})
end
--[[
RegisterAnyPlayerUnitEvent(playerunitevent, func)
-- Nearly the same syntax as TriggerRegisterAnyUnitEventBJ, without
-- the trigger parameter.
]]
RegisterAnyPlayerUnitEvent = setmetatable({}, protected_t())
do
local m_reg = getmetatable(RegisterAnyPlayerUnitEvent)
m_reg.__metatable = RegisterAnyPlayerUnitEvent
do
local trig
local tb = {}
local function is_player_event(event)
return tostring(event):sub(1,15) == 'playerunitevent'
end
Initializer.registerBJ("SYSTEM", function()
trig = CreateTrigger()
TriggerAddAction(trig, function()
local event = GetTriggerPlayerUnitEventId()
tb[event]:execute()
end)
end)
function m_reg:__call(event, func)
if not is_player_event(event) then return end
if not trig then Initializer.register(function() m_reg:__call(event, func) end, "SYSTEM"); return; end
if not tb[event] then
tb[event] = EventListener:create()
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
local p = Player(i)
TriggerRegisterPlayerUnitEvent(trig, p, event, nil)
end
end
tb[event]:register(func)
end
end
end
do
local dex = setmetatable({}, protected_t())
local m_dex = getmetatable(dex)
local Info = {
preplaced_flag = true,
Preplaced = {},
Registered = {},
Corpse = {},
}
UnitDex = dex
m_dex._DETECT_LEAVE = FourCC("uDex")
m_dex.__listener = PriorityEvent()
m_dex.ENTER_EVENT = 1
m_dex.LEAVE_EVENT = 2
m_dex.eventUnit = 0
m_dex.eventType = 0
do
local listen = m_dex.__listener
local flag = false
local function checkparams(ev, func)
if not is_function(func) then return false, ev, func end
if type(ev) == 'string' then
ev = m_dex[ev]
if not ev then return false, ev, func end
elseif type(ev) == 'number' then
ev = math.floor(ev)
end
return not (ev > listen.listeners.max or ev < listen.listeners.min), ev, func
end
function m_dex.register(ev, func)
flag, ev, func = checkparams(ev, func)
if not flag then return end; listen:register(ev, func)
end
function m_dex.deregister(ev, func)
flag, ev, func = checkparams(ev, func)
if not flag then return end; listen:deregister(ev, func)
end
listen:register(2, DoNothing); listen:deregister(2, DoNothing)
end
Initializer.registerBJ("TRIGGER", function()
local t = CreateTrigger()
local id
local reg = function(whichunit)
local lasts = setmetatable({}, WeakTable.keyvalues)
lasts.tempunit = m_dex.eventUnit
lasts.tempev = m_dex.eventType
m_dex.eventUnit = whichunit
m_dex.eventType = m_dex.ENTER_EVENT
id = m_dex.eventUnit
if not Info.Registered[id] then
Info.Registered[id] = true
Info.Preplaced[id] = Info.preplaced_flag
Info.Corpse[id] = not UnitAlive(id)
UnitAddAbility(whichunit, m_dex._DETECT_LEAVE)
UnitMakeAbilityPermanent(whichunit, true, m_dex._DETECT_LEAVE)
BlzUnitDisableAbility(whichunit, m_dex._DETECT_LEAVE, true, true)
-- Throw event
m_dex.__listener:fire(m_dex.eventType)
end
m_dex.eventType = lasts.tempev
m_dex.eventUnit = lasts.tempunit
end
local dereg = function(whichunit)
local lasts = setmetatable({}, WeakTable.keyvalues)
lasts.tempunit = m_dex.eventUnit
lasts.tempev = m_dex.eventType
m_dex.eventUnit = whichunit
m_dex.eventType = m_dex.LEAVE_EVENT
id = m_dex.eventUnit
if Info.Registered[id] then
Info.Registered[id] = nil
Info.Preplaced[id] = nil
Info.Corpse[id] = nil
-- Throw event
m_dex.__listener:fire(m_dex.eventType)
end
m_dex.eventType = lasts.tempev
m_dex.eventUnit = lasts.tempunit
lasts = nil
end
TriggerRegisterEnterRegion(t, WorldRect.reg, nil)
TriggerAddCondition(t, Condition(function()
reg(GetTriggerUnit())
end))
t = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER)
TriggerAddCondition(t, Condition(function()
-- Detect undefend here
if GetIssuedOrderId() == 852056 then
if GetUnitAbilityLevel(GetTriggerUnit(), m_dex._DETECT_LEAVE) == 0 then
dereg(GetTriggerUnit())
end
end
end))
local g = CreateGroup()
GroupEnumUnitsInRect(g, WorldRect.rect, nil)
ForGroup(g, function()
reg(GetEnumUnit())
end)
DestroyGroup(g)
Info.preplaced_flag = false
end)
do
local function is_unit(whichunit)
return tostring(whichunit):sub(1,4) == 'unit'
end
function IsUnitPreplaced(whichunit)
if not is_unit(whichunit) then return false end
return Info.Preplaced[whichunit]
end
function WasUnitCorpseOnEnter(whichunit)
if not is_unit(whichunit) then return false end
return Info.Corpse[whichunit]
end
end
end
--[[
Inspired by Bribe's UnitEvent.
]]
UnitState = setmetatable({}, protected_t())
do
local m_state = getmetatable(UnitState)
local s_table = {living={}, unit_type={}, transport={}, transporter={}}
m_state.eventTransport = 0
m_state.eventKiller = 0
m_state.eventUnit = 0
m_state.eventType = 0
m_state.eventPrevUnitType = 0
m_state._DETECT_ABIL = FourCC("uDey")
m_state.DEATH_EVENT = 1
m_state.RESURRECT_EVENT = 2
m_state.TRANSFORM_EVENT = 3
m_state.LOAD_EVENT = 4
m_state.UNLOAD_EVENT = 5
m_state.__listener = PriorityEvent:create()
m_state.__listener:register(2, DoNothing)
m_state.__listener:register(3, DoNothing)
m_state.__listener:register(4, DoNothing)
m_state.__listener:register(5, DoNothing)
m_state.__listener:deregister(5, DoNothing)
m_state.__listener:deregister(4, DoNothing)
m_state.__listener:deregister(3, DoNothing)
m_state.__listener:deregister(2, DoNothing)
do
local listen = m_state.__listener
function m_state.register(prio, func)
prio = (type(prio or "DEATH_EVENT") == 'string') and UnitState[prio]
if prio > listen.listeners.max or prio < listen.listeners.min then
return
end
listen:register(prio, func)
end
end
Initializer.registerBJ("SYSTEM", function()
local ev_holder = {}
local prev_values = {}
local state_watch = {[m_state.DEATH_EVENT] = {}, [m_state.RESURRECT_EVENT] = {}, [m_state.TRANSFORM_EVENT] = {}}
local count = 0
local zero_timer = CreateTimer()
state_watch[m_state.UNLOAD_EVENT] = {}
state_watch[m_state.LOAD_EVENT] = {}
local function on_zero_callback()
local max_count = count
if count > 0 then
prev_values.killer = m_state.eventKiller
prev_values.unit = m_state.eventUnit
prev_values.event = m_state.eventType
prev_values.prevtype = m_state.eventPrevUnitType
prev_values.eventtrans = m_state.eventTransport
local i = 1
while i <= count do
m_state.eventKiller = ev_holder[i].killer
m_state.eventUnit = ev_holder[i].unit
m_state.eventType = ev_holder[i].event
m_state.eventPrevUnitType = ev_holder[i].prev_id
m_state.eventTransport = ev_holder[i].transport
state_watch[m_state.eventType][m_state.eventUnit] = nil
if m_state.eventType == m_state.TRANSFORM_EVENT then
local id = m_state.eventUnit
s_table.unit_type[id] = GetUnitTypeId(id)
UnitAddAbility(id, m_state._DETECT_ABIL)
end
if GetUnitTypeId(m_state.eventUnit) ~= 0 then
m_state.__listener:fire(m_state.eventType)
end
ev_holder[i] = nil
i = i + 1
end
m_state.eventTransport = prev_values.eventtrans
m_state.eventKiller = prev_values.killer
m_state.eventUnit = prev_values.unit
m_state.eventType = prev_values.event
m_state.eventPrevUnitType = prev_values.prevtype
end
count = 0
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function()
local unit = GetTriggerUnit()
for i = 1, count do
if ev_holder[i].event == m_state.DEATH_EVENT then
if ev_holder[i].unit == unit then
ev_holder[i].killer = GetKillingUnit()
break
end
end
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function()
-- Undefend
local orderId = GetIssuedOrderId()
local unit = GetTriggerUnit()
if orderId == OrderId("magicundefense") then
local id = unit
-- Algorithm: check if unit has fulfilled a certain event,
-- then start a timer throwing that event.
local flags = {
UnitAlive(unit),
GetUnitAbilityLevel(unit, m_state._DETECT_ABIL) ~= 0,
s_table.living[id] ~= nil,
}
-- If flags[3] -> false, the system tried to throw an event before
-- the unit officially entered the map.
if flags[3] then
if s_table.living[id] ~= flags[1] then
-- If unit is alive, that means unit was dead before
if flags[1] then
if not state_watch[m_state.RESURRECT_EVENT][unit] then
count = count + 1
ev_holder[count] = {event=m_state.RESURRECT_EVENT, unit=unit}
state_watch[m_state.RESURRECT_EVENT][unit] = true
end
else
if not state_watch[m_state.DEATH_EVENT][unit] and flags[2] then
count = count + 1
ev_holder[count] = {event=m_state.DEATH_EVENT, unit=unit}
state_watch[m_state.DEATH_EVENT][unit] = true
end
end
s_table.living[id] = flags[1]
end
if not flags[2] and flags[1] then
-- The unit lost the ability, or must have transformed.
if not state_watch[m_state.TRANSFORM_EVENT][unit] then
count = count + 1
ev_holder[count] = {event=m_state.TRANSFORM_EVENT, unit=unit, prev_id=s_table.unit_type[unit]}
state_watch[m_state.TRANSFORM_EVENT][unit] = true
end
end
if s_table.transporter[unit] and not IsUnitLoaded(unit) then
local transport = s_table.transporter[unit]
GroupRemoveUnit(s_table.transport[transport], unit)
s_table.transporter[unit] = nil
if not state_watch[m_state.UNLOAD_EVENT][unit] then
count = count + 1
ev_holder[count] = {event=m_state.UNLOAD_EVENT, unit=unit, transport=transport}
state_watch[m_state.UNLOAD_EVENT][unit] = true
end
end
end
elseif orderId == 851972 then
-- The unit has issued a stop order.
if s_table.transporter[unit] and not IsUnitLoaded(unit) then
local transport = s_table.transporter[unit]
GroupRemoveUnit(s_table.transport[transport], unit)
s_table.transporter[unit] = nil
if not state_watch[m_state.UNLOAD_EVENT][unit] then
count = count + 1
ev_holder[count] = {event=m_state.UNLOAD_EVENT, unit=unit, transport=transport}
state_watch[m_state.UNLOAD_EVENT][unit] = true
end
end
end
if count >= 1 then
TimerStart(zero_timer, 0.00, false, on_zero_callback)
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_LOADED, function()
local transport, loadee = GetTransportUnit(), GetLoadedUnit()
if not s_table.transport[transport] then
s_table.transport[transport] = CreateGroup()
end
GroupAddUnit(s_table.transport[transport], loadee)
s_table.transporter[loadee] = transport
if not UnitAlive(loadee) then
SetUnitX(loadee, WorldRect.rectMaxX)
SetUnitY(loadee, WorldRect.rectMaxY)
end
if not state_watch[m_state.LOAD_EVENT][loadee] then
count = count + 1
ev_holder[count] = {event=m_state.LOAD_EVENT, unit=loadee, transport=transport}
state_watch[m_state.LOAD_EVENT][loadee] = true
end
if count == 1 then
TimerStart(zero_timer, 0.00, false, on_zero_callback)
end
end)
local trig = CreateTrigger()
TriggerRegisterEnterRegion(trig, WorldRect.reg, nil)
TriggerAddCondition(trig, Filter(function()
local unit = GetTriggerUnit()
if s_table.transporter[unit] and not IsUnitLoaded(unit) then
local transport = s_table.transporter[unit]
GroupRemoveUnit(s_table.transport[transport], unit)
s_table.transporter[unit] = nil
if not state_watch[m_state.UNLOAD_EVENT][unit] then
count = count + 1
ev_holder[count] = {event=m_state.UNLOAD_EVENT, unit=unit, transport=transport}
state_watch[m_state.UNLOAD_EVENT][unit] = true
end
if count == 1 then
TimerStart(zero_timer, 0.00, false, on_zero_callback)
end
end
end))
end)
function GetTransportedGroup(transport)
return s_table.transport[transport]
end
UnitDex.register("ENTER_EVENT", function()
local id = UnitDex.eventUnit
s_table.living[id] = UnitAlive(UnitDex.eventUnit)
s_table.unit_type[id] = GetUnitTypeId(UnitDex.eventUnit)
UnitAddAbility(UnitDex.eventUnit, m_state._DETECT_ABIL)
end)
UnitDex.register("LEAVE_EVENT", function()
local id = UnitDex.eventUnit
s_table.unit_type[id] = nil
s_table.living[id] = nil
end)
end
-- Thanks to Bribe for the REPO thread about damage event interactions
DamageEvent = setmetatable({DEBUG_MODE=false}, protected_t())
do
local m_dmgEv = getmetatable(DamageEvent)
local values = {}
local dmgEvResp = {
pure_flag = false
}
m_dmgEv.__modifierEvents = PriorityEvent:create()
m_dmgEv.__dmgEvent = EventListener:create()
m_dmgEv.__afterDmgEvent = EventListener:create()
m_dmgEv.MAX_RECURSION_LEVEL = 15
-- Damage Modifier events
m_dmgEv.MODIFIER_EVENT_SYSTEM = 5
m_dmgEv.MODIFIER_EVENT_ALPHA = 4
m_dmgEv.MODIFIER_EVENT_BETA = 3
m_dmgEv.MODIFIER_EVENT_GAMMA = 2
m_dmgEv.MODIFIER_EVENT_DELTA = 1
m_dmgEv.__modifierEvents:register(5, DoNothing)
m_dmgEv.__modifierEvents:register(4, DoNothing)
m_dmgEv.__modifierEvents:register(3, DoNothing)
m_dmgEv.__modifierEvents:register(2, DoNothing)
m_dmgEv.__modifierEvents:register(1, DoNothing)
m_dmgEv.__modifierEvents:deregister(5, DoNothing)
m_dmgEv.__modifierEvents:deregister(4, DoNothing)
m_dmgEv.__modifierEvents:deregister(3, DoNothing)
m_dmgEv.__modifierEvents:deregister(2, DoNothing)
m_dmgEv.__modifierEvents:deregister(1, DoNothing)
m_dmgEv.__modifierEvents:set_prio_recursion(4, m_dmgEv.MAX_RECURSION_LEVEL)
m_dmgEv.__modifierEvents:set_prio_recursion(3, m_dmgEv.MAX_RECURSION_LEVEL)
m_dmgEv.__modifierEvents:set_prio_recursion(2, m_dmgEv.MAX_RECURSION_LEVEL)
m_dmgEv.__modifierEvents:set_prio_recursion(1, m_dmgEv.MAX_RECURSION_LEVEL)
m_dmgEv.__dmgEvent:set_recursion_count(m_dmgEv.MAX_RECURSION_LEVEL)
m_dmgEv.__afterDmgEvent:set_recursion_count(m_dmgEv.MAX_RECURSION_LEVEL)
local _print = print
local function print(...)
if DamageEvent.DEBUG_MODE then _print(...) end
end
values.interactions = {
damageType = {magic = 1, universal = 2, normal = 4},
attackType = {normal = 8, spells = 16, magic = 32},
}
values.afterDmg = function()
values.current = dmgEvResp[#dmgEvResp]
values.meta = getmetatable(values.current)
values.dmg_map = values.interactions.dmgTable[GetHandleId(values.meta.dmgtype)]
values.atk_map = values.interactions.atkTable[GetHandleId(values.meta.atktype)]
DisableTrigger(GetTriggeringTrigger())
DestroyTrigger(GetTriggeringTrigger())
m_dmgEv.__afterDmgEvent:conditionalExec(not values.meta.is_pure_dmg)
values.doflush(values.meta, values.current, false)
dmgEvResp[#dmgEvResp] = nil
end
values.damaging = function()
values.meta = {}
values.meta.__index = values.indexFunc
values.meta.__newindex = values.newIndexFunc
values.meta.dmg = {}
values.meta.atktype = {}
values.meta.dmgtype = {}
values.meta.wpntype = {}
values.meta.dmg.get = GetEventDamage
values.meta.atktype.get = BlzGetEventAttackType
values.meta.dmgtype.get = BlzGetEventDamageType
values.meta.wpntype.get = BlzGetEventWeaponType
values.meta.dmg.set = BlzSetEventDamage
values.meta.atktype.set = BlzSetEventAttackType
values.meta.dmgtype.set = BlzSetEventDamageType
values.meta.wpntype.set = BlzSetEventWeaponType
values.current = setmetatable({}, values.meta)
values.meta.source = GetEventDamageSource()
values.meta.target = GetTriggerUnit()
values.meta.pure_dmg = GetEventDamage()
values.meta.base_dmg = GetEventDamage()
values.meta.base_atktype = BlzGetEventAttackType()
values.meta.base_dmgtype = BlzGetEventDamageType()
values.meta.base_wpntype = BlzGetEventWeaponType()
values.meta.is_pure_dmg = dmgEvResp.pure_flag
dmgEvResp[#dmgEvResp + 1] = values.current
values.meta.old_dmg = values.meta.pure_dmg
m_dmgEv.__modifierEvents:fire(m_dmgEv.MODIFIER_EVENT_SYSTEM)
m_dmgEv.__modifierEvents:conditional_fire_to(m_dmgEv.MODIFIER_EVENT_ALPHA, m_dmgEv.MODIFIER_EVENT_GAMMA, not values.meta.is_pure_dmg, true)
if BlzIsUnitInvulnerable(values.meta.target) or not UnitAlive(values.meta.target) then
values.doflush(values.meta, values.current, true)
dmgEvResp[#dmgEvResp] = nil
return
else
values.ethereal = IsUnitType(values.meta.target, UNIT_TYPE_ETHEREAL)
values.magic_immune = IsUnitType(values.meta.target, UNIT_TYPE_MAGIC_IMMUNE)
values.dmg_map = values.interactions.dmgTable[GetHandleId(values.current.dmgtype)]
values.atk_map = values.interactions.atkTable[GetHandleId(values.current.atktype)]
if values.ethereal then
if values.atk_map == values.interactions.attackType.normal or
(values.atk_map == values.interactions.attackType.spells and
values.dmg_map == values.interactions.damageType.normal) then
values.doflush(values.meta, values.current, true)
dmgEvResp[#dmgEvResp] = nil
return
end
end
if values.magic_immune then
if values.atk_map == values.interactions.attackType.magic or
values.dmg_map == values.interactions.damageType.magic then
values.doflush(values.meta, values.current, true)
dmgEvResp[#dmgEvResp] = nil
return
end
end
end
values.meta.old_dmg = values.current.dmg
end
values.damaged = function()
values.current = dmgEvResp[#dmgEvResp]
values.meta = getmetatable(values.current)
values.meta.base_dmg = values.current.dmg
-- Do not allow the assignment of attack type, damage type and weapon type
values.meta.atktype.set = nil
values.meta.dmgtype.set = nil
values.meta.wpntype.set = nil
values.dmg_map = values.interactions.dmgTable[GetHandleId(values.current.dmgtype)]
values.atk_map = values.interactions.atkTable[GetHandleId(values.current.atktype)]
if values.meta.is_pure_dmg then
values.current.dmg = values.meta.pure_dmg
end
m_dmgEv.__modifierEvents:conditional_fire(m_dmgEv.MODIFIER_EVENT_DELTA, not values.meta.is_pure_dmg)
values.meta.dmg.set = nil
m_dmgEv.__dmgEvent:conditional_exec(not values.meta.is_pure_dmg)
if BlzIsUnitInvulnerable(values.meta.target) then
values.doflush(values.meta, values.current, true)
dmgEvResp[#dmgEvResp] = nil
return
end
values.meta.trigger = CreateTrigger()
TriggerAddCondition(values.meta.trigger, Condition(values.afterDmg))
values.curHp = GetWidgetLife(values.meta.target)
SetWidgetLife(values.meta.target, math.max(values.curHp - values.current.dmg, 0.406))
values.nextHp = GetWidgetLife(values.meta.target)
SetWidgetLife(values.meta.target, values.curHp)
values.meta.atktype.get = nil
values.meta.dmgtype.get = nil
values.meta.wpntype.get = nil
values.meta.dmg.get = nil
values.meta.dmg = GetEventDamage()
values.meta.dmgtype = BlzGetEventDamageType()
values.meta.wpntype = BlzGetEventWeaponType()
values.meta.atktype = BlzGetEventAttackType()
if values.curHp == values.nextHp then
TriggerEvaluate(values.meta.trigger)
else
TriggerRegisterUnitStateEvent(values.meta.trigger, values.meta.target, UNIT_STATE_LIFE, LESS_THAN, values.nextHp)
TriggerRegisterUnitStateEvent(values.meta.trigger, values.meta.target, UNIT_STATE_LIFE, GREATER_THAN, values.nextHp)
end
end
Initializer.registerBJ("SYSTEM", function()
values.indexFunc = function(t, k)
values.curMeta = getmetatable(t)
if type(values.curMeta[k]) == 'table' and values.curMeta[k].get then
return values.curMeta[k].get()
end
return values.curMeta[k]
end
values.newIndexFunc = function(t, k, ...)
values.curMeta = getmetatable(t)
if type(values.curMeta[k]) == 'table' and values.curMeta[k].set then
values.curMeta[k].set(...)
end
end
values.doflush = function(mt, t, incl)
if incl then
mt.wpntype = nil
mt.dmgtype = nil
mt.atktype = nil
mt.dmg = nil
end
end
values.interactions.atkTable = {
[1] = values.interactions.attackType.normal,
[2] = values.interactions.attackType.normal,
[3] = values.interactions.attackType.normal,
[5] = values.interactions.attackType.normal,
[6] = values.interactions.attackType.normal,
[0] = values.interactions.attackType.spells,
[4] = values.interactions.attackType.magic,
}
values.interactions.dmgTable = {
[0] = values.interactions.damageType.universal,
[26] = values.interactions.damageType.universal,
[4] = values.interactions.damageType.normal,
[5] = values.interactions.damageType.normal,
[11] = values.interactions.damageType.normal,
[12] = values.interactions.damageType.normal,
[16] = values.interactions.damageType.normal,
[21] = values.interactions.damageType.normal,
[23] = values.interactions.damageType.normal,
[8] = values.interactions.damageType.magic,
[9] = values.interactions.damageType.magic,
[10] = values.interactions.damageType.magic,
[13] = values.interactions.damageType.magic,
[14] = values.interactions.damageType.magic,
[15] = values.interactions.damageType.magic,
[17] = values.interactions.damageType.magic,
[18] = values.interactions.damageType.magic,
[19] = values.interactions.damageType.magic,
[20] = values.interactions.damageType.magic,
[22] = values.interactions.damageType.magic,
[24] = values.interactions.damageType.magic,
[25] = values.interactions.damageType.magic,
}
values.t = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(values.t, EVENT_PLAYER_UNIT_DAMAGING)
TriggerRegisterAnyUnitEventBJ(values.t, EVENT_PLAYER_UNIT_DAMAGED)
TriggerAddCondition(values.t, Condition(function()
if GetTriggerPlayerUnitEventId() == EVENT_PLAYER_UNIT_DAMAGING then
values.damaging()
else
values.damaged()
end
end))
end)
function m_dmgEv.register_modifier(func, prio)
if type(prio) == 'function' then prio, func = func, prio end
if type(prio) == 'string' then
prio = m_dmgEv[prio]
end
if type(prio) ~= 'number' then
prio = m_dmgEv.MODIFIER_EVENT_DELTA
else
-- Ensure that the boundaries are respected
prio = math.min(math.max(prio, m_dmgEv.MODIFIER_EVENT_DELTA), m_dmgEv.MODIFIER_EVENT_SYSTEM)
end
m_dmgEv.__modifierEvents:register(prio, func)
end
function m_dmgEv.register_damage(func)
m_dmgEv.__dmgEvent:register(func)
end
function m_dmgEv.register_after_damage(func)
m_dmgEv.__afterDmgEvent:register(func)
end
function IsSpellDamage()
return values.interactions.dmgTable[GetHandleId(values.current.dmgtype)] == values.interactions.damageType.magic
end
function IsPhysicalDamage()
return values.interactions.dmgTable[GetHandleId(values.current.dmgtype)] == values.interactions.damageType.normal
end
function IsUniversalDamage()
return values.interactions.dmgTable[GetHandleId(values.current.dmgtype)] == values.interactions.damageType.universal
end
function IsPureDamage()
return values.meta.is_pure_dmg
end
function UnitDamageTargetPure(source, target, amount)
local lastValue; lastValue, dmgEvResp.pure_flag = dmgEvResp.pure_flag, true
local result = UnitDamageTarget(source, target, amount, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, nil)
dmgEvResp.pure_flag = lastValue
return result
end
local __oldIndex = m_dmgEv.__index
m_dmgEv.__index = function(t, k)
if k == 'current' then
return dmgEvResp[#dmgEvResp]
elseif k == 'previous' then
if #dmgEvResp <= 1 then
return 0
end
return dmgEvResp[#dmgEvResp]
end
return __oldIndex(t, k)
end
end
SpellEvent = setmetatable({}, protected_t())
do
local m_spell = getmetatable(SpellEvent)
m_spell.__listeners = PriorityEvent:create()
m_spell.__evId = 0
m_spell.__spellId = {}
local mt_id = setmetatable({
[EVENT_PLAYER_UNIT_SPELL_CAST] = 1,
[EVENT_PLAYER_UNIT_SPELL_CHANNEL] = 2,
[EVENT_PLAYER_UNIT_SPELL_EFFECT] = 3,
[EVENT_PLAYER_UNIT_SPELL_ENDCAST] = 4,
[EVENT_PLAYER_UNIT_SPELL_FINISH] = 5,
[EVENT_PLAYER_HERO_SKILL] = 6,
-- Unit events
[EVENT_UNIT_SPELL_CAST] = 1,
[EVENT_UNIT_SPELL_CHANNEL] = 2,
[EVENT_UNIT_SPELL_EFFECT] = 3,
[EVENT_UNIT_SPELL_ENDCAST] = 4,
[EVENT_UNIT_SPELL_FINISH] = 5,
}, {__index = function(t, k) return 0 end})
local function event2Id(event)
return mt_id[event]
end
local function conv(event)
return ConvertPlayerUnitEvent(GetHandleId(event))
end
function m_spell.register(event, func)
if tostring(event):sub(1,5) == 'event' then event = conv(event); end
local i = event2Id(event)
if i ~= 0 then
m_spell.__listeners:register(i, func)
end
end
function m_spell.register_spell(event, abilId, func)
local i = event2Id(event)
if i == 0 then
return
end
if not m_spell.__spellId[abilId] then
m_spell.__spellId[abilId] = {}
end
m_spell.__spellId[abilId][i] = func
end
Initializer.registerBJ("SYSTEM", function()
local resp_func = function()
local id, abilId = event2Id(ConvertPlayerUnitEvent(GetHandleId(GetTriggerEventId()))), GetSpellAbilityId()
if id == 6 then abilId = GetLearnedSkill() end
m_spell.__listeners:fire(id)
if m_spell.__spellId[abilId] and m_spell.__spellId[abilId][id] then
m_spell.__spellId[abilId][id]()
end
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CAST, resp_func)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CHANNEL, resp_func)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, resp_func)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, resp_func)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_FINISH, resp_func)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_HERO_SKILL, resp_func)
end)
end
GenericUnitEvent = setmetatable({MAX_BUCKET_SIZE = 1, COLLECT_SIZE = 1}, protected_t())
do
local GUE = GenericUnitEvent
local m_GUE = getmetatable(GUE)
local DEBUG_MODE = false
local baseGroup
m_GUE.__metatable = GUE
Initializer.registerBJ("SYSTEM", function() baseGroup = CreateGroup(); end)
local function is_unit_event(whichevent)
return tostring(whichevent):sub(1,9) == 'unitevent'
end
if DEBUG_MODE then
--[[ ]]
local function print(msg)
DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 3600, msg)
end
--[[ ]]
end
do
local internal = {}
local tb = {}
function internal.resp()
local unitevent = ConvertUnitEvent(GetHandleId(GetTriggerEventId()))
tb[unitevent].listener:execute()
end
function internal.create(event, o)
o = o or {}
o.max_size = GUE.MAX_BUCKET_SIZE
o.col_size = GUE.COLLECT_SIZE
o.detectors = {CreateTrigger()}
o.clear_count = 0
o.detect_size = {0}
o.detect_lack = {#o.detect_size}
o.unit_map = setmetatable({}, WeakTable.keys)
o.unit_cont = {CreateGroup()}
o.assoc_event = event
TriggerAddCondition(o.detectors[1], Filter(internal.resp))
setmetatable(o, m_GUE)
return o
end
function internal.doclear(o)
local grp = CreateGroup()
for i = o.detect_lack[#o.detect_lack],2,-1 do
if i < 2 then break end
DestroyTrigger(o.detectors[i])
o.detectors[i] = CreateTrigger()
TriggerAddCondition(o.detectors[i], Filter(internal.resp))
ForGroup(o.unit_cont[i], function()
GroupAddUnit(grp, GetEnumUnit())
end)
GroupClear(o.unit_cont[i])
end
collectgarbage()
collectgarbage()
-- Clear out excess events.
o.clear_count = 0
ForGroup(grp, function()
local uu, id, i = GetEnumUnit(), GetHandleId(uu), o.detect_lack[#o.detect_lack]
if (o.detect_size[i] >= o.max_size) then
-- The system will examine buckets which lack
-- the desired amount of entries first.
while #o.detect_lack > 1 do
o.detect_lack[#o.detect_lack] = nil
i = o.detect_lack[#o.detect_lack]
if o_detect_size[i] < o.max_size then break end
end
if (i == #o.detect_size) then
o.unit_cont[#o.unit_cont + 1] = setmetatable({}, WeakTable.keys)
o.detectors[#o.detectors + 1] = CreateTrigger()
o.detect_size[#o.detect_size + 1] = 0
o.detect_lack[#o.detect_lack] = #o.detect_size
i = #o.detect_size
TriggerAddCondition(o.detectors[#o.detectors], Filter(internal.resp))
end
end
TriggerRegisterUnitEvent(o.detectors[i], uu, event)
o.detect_size[i] = o.detect_size[i] + 1
o.unit_map[id] = i
GroupAddUnit(o.unit_cont[i], uu)
end)
DestroyGroup(grp)
grp = nil
end
function internal.new(event)
if not is_unit_event(event) then return nil; end
local o = internal.create(event, o)
if baseGroup then
ForGroup(baseGroup, function()
local uu, id, i = GetEnumUnit(), GetHandleId(uu), o.detect_lack[#o.detect_lack]
-- When a new event is registered, detect_lack will proceed sequentially.
-- So, there is no need to consider another situation.
if (o.detect_size[i] >= o.max_size) then
o.unit_cont[#o.unit_cont + 1] = CreateGroup()
o.detectors[#o.detectors + 1] = CreateTrigger()
o.detect_size[#o.detect_size + 1] = 0
o.detect_lack[#o.detect_lack] = #o.detect_size
i = #o.detect_size
TriggerAddCondition(o.detectors[#o.detectors], Filter(internal.resp))
end
TriggerRegisterUnitEvent(o.detectors[i], uu, event)
o.detect_size[i] = o.detect_size[i] + 1
o.unit_map[id] = i
GroupAddUnit(o.unit_cont[i], uu)
end)
end
UnitDex.register("ENTER_EVENT", function()
local uu, id, i = UnitDex.eventUnit, GetHandleId(uu), o.detect_lack[#o.detect_lack]
if (o.detect_size[i] >= o.max_size) then
-- The system will examine buckets which lack
-- the desired amount of entries first.
while #o.detect_lack > 1 do
o.detect_lack[#o.detect_lack] = nil
i = o.detect_lack[#o.detect_lack]
if o_detect_size[i] < o.max_size then break end
end
if (i == #o.detect_size) then
o.unit_cont[#o.unit_cont + 1] = CreateGroup()
o.detectors[#o.detectors + 1] = CreateTrigger()
o.detect_size[#o.detect_size + 1] = 0
o.detect_lack[#o.detect_lack] = #o.detect_size
i = #o.detect_size
TriggerAddCondition(o.detectors[#o.detectors], Filter(internal.resp))
end
end
TriggerRegisterUnitEvent(o.detectors[i], uu, event)
o.detect_size[i] = o.detect_size[i] + 1
o.unit_map[id] = i
GroupAddUnit(o.unit_cont[i], uu)
end)
UnitDex.register("LEAVE_EVENT", function()
local uu, id = UnitDex.eventUnit, GetHandleId(uu)
local i = o.unit_map[id]
local j = 0
for k = 1,#o.detect_lack do
if (o.detect_lack[k] == i) then j = k; break; end
end
if j == 0 then o.detect_lack[#o.detect_lack + 1] = i; end; j = nil;
o.detect_size[i] = o.detect_size[i] - 1
o.clear_count = o.clear_count + 1
GroupRemoveUnit(o.unit_cont[i], uu)
-- Do a removal procedure here
o.unit_map[id] = nil
if o.clear_count >= o.col_size then
internal.doclear(o)
end
end)
return o
end
function RegisterUnitEvent(unitevent, func)
if not is_unit_event(unitevent) then return end
if not tb[unitevent] then
tb[unitevent] = {internal.new(unitevent), listener=EventListener:create()}
end
tb[unitevent].listener:register(func)
end
end
UnitDex.register("ENTER_EVENT", function() GroupAddUnit(baseGroup, UnitDex.eventUnit); end)
UnitDex.register("LEAVE_EVENT", function() GroupRemoveUnit(baseGroup, UnitDex.eventUnit); end)
end
do
local exp = protected_t({
MAX_RANGE = 1200.00, -- This is based on Gameplay Constants
HERO_EXP = {}
})
exp.eventUnit = 0
exp.xpAmount = 0
exp.xpGained = 0
local evt = EventListener:create()
local function filter_heroes(unit)
return IsHeroUnitId(GetUnitTypeId(unit))
end
local function update_exp(unit)
if not exp.HERO_EXP[unit] then return end
local prev_xp = exp.HERO_EXP[unit]
exp.HERO_EXP[unit] = GetHeroXP(unit)
if exp.HERO_EXP[unit] ~= prev_xp then
local prev_unit = exp.eventUnit
local prev_gain = exp.xpGained
local prev_amnt = exp.xpAmount
exp.eventUnit = unit
exp.xpAmount = exp.HERO_EXP[unit]
exp.xpGained = exp.HERO_EXP[unit] - prev_xp
evt:execute()
exp.eventUnit = prev_unit
exp.xpAmount = prev_gain
exp.xpGained = prev_amnt
end
end
local natives = {}
natives.SetHeroLevel = SetHeroLevel
natives.SetHeroXP = SetHeroXP
natives.AddHeroXP = AddHeroXP
natives.UnitStripHeroLevel = UnitStripHeroLevel
function SetHeroLevel(whichhero, level, showeyecandy)
natives.SetHeroLevel(whichhero, level, showeyecandy)
update_exp(whichhero)
end
function SetHeroXP(whichhero, newxpval, showeyecandy)
natives.SetHeroXP(whichhero, newxpval, showeyecandy)
update_exp(whichhero)
end
function AddHeroXP(whichhero, xptoadd, showeyecandy)
natives.AddHeroXP(whichhero, xptoadd, showeyecandy)
update_exp(whichhero)
end
function UnitStripHeroLevel(whichhero, howmanylevels)
natives.UnitStripHeroLevel(whichhero, howmanylevels)
update_exp(whichhero)
end
UnitDex.register("ENTER_EVENT", function()
if filter_heroes(UnitDex.eventUnit) then
exp.HERO_EXP[UnitDex.eventUnit] = GetHeroXP(UnitDex.eventUnit)
end
end)
UnitDex.register("LEAVE_EVENT", function()
if exp.HERO_EXP[UnitDex.eventUnit] then
exp.HERO_EXP[UnitDex.eventUnit] = nil
end
end)
Initializer("SYSTEM", function()
local grp = CreateGroup()
local enum = exp.MAX_RANGE + 150. -- Delta offset to cover niche cases.
local function update()
if exp.HERO_EXP[GetTriggerUnit()] then
update_exp(GetTriggerUnit())
end
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM, update)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM, update)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_USE_ITEM, update)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_HERO_LEVEL, update)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function()
local dead = GetTriggerUnit()
local cx, cy = GetUnitX(dead), GetUnitY(dead)
GroupEnumUnitsInRange(grp, cx, cy, enum, nil)
ForGroup(grp, function()
local uu = GetEnumUnit()
if exp.HERO_EXP[uu] then
update_exp(uu)
end
end)
end)
end)
function exp.register(func)
evt:register(func)
end
ExperienceEvent = setmetatable({}, exp)
end
HandleCheck = setmetatable({}, protected_t())
do
local m_check = getmetatable(HandleCheck)
m_check.__metatable = HandleCheck
local function type_factory(whichtype)
local f = function(param)
return tostring(param):sub(1, whichtype:len()) == whichtype
end
return f
end
m_check.is_unit = type_factory("unit")
m_check.is_dest = type_factory("destructable")
m_check.is_item = type_factory("item")
m_check.is_group = type_factory("group")
end
OrderMatrix = setmetatable({}, protected_t())
do
local order = OrderMatrix
local m_order = getmetatable(order)
local meta_target = {__mode='v'}
local meta_point = {x={}, y={}}
local meta_order = {}
local meta_order_id = {}
local meta_order_tp = {}
m_order.MAX_ORDERS = 8
m_order.NO_TARGET = setmetatable({}, {__tostring=function(t) return "No Target" end})
m_order.NO_POINT = Location(0, 0)
m_order.IMMEDIATE = setmetatable({}, {__tostring=function(t) return "Immediate" end})
m_order.POINT = setmetatable({}, {__tostring=function(t) return "Point" end})
m_order.TARGET = setmetatable({}, {__tostring=function(t) return "Target" end})
m_order.__metatable = order
meta_target.__index = function(t, k)
return m_order.NO_TARGET
end
meta_point.x.__index = function(t, k)
return 0
end
meta_point.y.__index = function(t, k)
return 0
end
meta_order.__index = function(t, k)
return "No order"
end
meta_order_id.__index = function(t, k)
return 0
end
meta_order_tp.__index = function(t, k)
return "No order type"
end
local function new_table(id)
m_order[id] = {}
m_order[id].point = {x = setmetatable({}, meta_point.x), y = setmetatable({}, meta_point.y)}
m_order[id].target = setmetatable({}, meta_target)
m_order[id].orderId = setmetatable({}, meta_order_id)
m_order[id].order = setmetatable({}, meta_order)
m_order[id].orderType = setmetatable({}, meta_order_tp)
end
local old_index = m_order.__index
function m_order.__index(t, k)
if HandleCheck.is_unit(k) then
local id = k
local result = old_index(t, id)
if result == nil then
new_table(id)
result = old_index(t, id)
end
return result
end
return old_index(t, k)
end
Initializer.registerBJ("SYSTEM", function()
local lt = {}
local func = function()
lt.eventType = GetTriggerPlayerUnitEventId()
lt.target = GetOrderTargetUnit() or GetOrderTargetDestructable() or GetOrderTargetItem() or m_order.NO_TARGET
lt.point = GetOrderPointLoc() or m_order.NO_POINT
if lt.point ~= m_order.NO_POINT then
RemoveLocation(lt.point)
lt.point_x, lt.point_y = GetOrderPointX(), GetOrderPointY()
end
if lt.eventType == EVENT_PLAYER_UNIT_ISSUED_ORDER then
lt.orderType = m_order.IMMEDIATE
elseif lt.eventType == EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER then
lt.orderType = m_order.POINT
else
lt.orderType = m_order.TARGET
end
lt.orderId = GetIssuedOrderId()
lt.result, lt.value = pcall(OrderId2String, lt.orderId)
if lt.result then
lt.order = ((lt.value ~= "") and lt.value) or "cannot convert order"
else
lt.order = "cannot convert order"
end
lt.id = GetTriggerUnit()
table.insert(m_order[lt.id].point.x, 1, lt.point_x)
table.insert(m_order[lt.id].point.y, 1, lt.point_y)
table.insert(m_order[lt.id].target, 1, lt.target)
table.insert(m_order[lt.id].orderId, 1, lt.orderId)
table.insert(m_order[lt.id].order, 1, lt.order)
table.insert(m_order[lt.id].orderType, 1, lt.orderType)
if #m_order[lt.id].point.x > m_order.MAX_ORDERS then
table.remove(m_order[lt.id].order)
table.remove(m_order[lt.id].target)
table.remove(m_order[lt.id].orderId)
table.remove(m_order[lt.id].orderType)
table.remove(m_order[lt.id].point.x)
table.remove(m_order[lt.id].point.y)
end
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, func)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, func)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, func)
end)
UnitDex.register("ENTER_EVENT", function()
local id = UnitDex.eventUnit
if not m_order[id] then
new_table(id)
end
end)
UnitDex.register("LEAVE_EVENT", function()
local id = UnitDex.eventUnit
m_order[id].point = nil
m_order[id].order = nil
m_order[id].target = nil
m_order[id].orderId = nil
m_order[id].orderType = nil
m_order[id] = nil
end)
end
TimeOfDay = setmetatable({}, protected_t())
do
local l_day = getmetatable(TimeOfDay)
l_day.listeners = {daytime=EventListener:create(), nighttime=EventListener:create()}
l_day.DAY_TIME = 6.00
l_day.NIGHT_TIME = 18.00
l_day.statii = {day="DAY_TIME", night="NIGHT_TIME"}
l_day.status = ""
Initializer.registerBJ("SYSTEM", function()
local t = CreateTrigger()
TriggerRegisterGameStateEvent(t, GAME_STATE_TIME_OF_DAY, LESS_THAN, l_day.DAY_TIME)
TriggerRegisterGameStateEvent(t, GAME_STATE_TIME_OF_DAY, LESS_THAN, l_day.NIGHT_TIME)
TriggerRegisterGameStateEvent(t, GAME_STATE_TIME_OF_DAY, GREATER_THAN_OR_EQUAL, l_day.DAY_TIME)
TriggerRegisterGameStateEvent(t, GAME_STATE_TIME_OF_DAY, GREATER_THAN_OR_EQUAL, l_day.NIGHT_TIME)
TriggerAddCondition(t, Condition(function()
local time = math.floor(GetFloatGameState(GAME_STATE_TIME_OF_DAY) + 0.5)
local prev = l_day.status
if time < l_day.DAY_TIME or time >= l_day.NIGHT_TIME then
l_day.status = l_day.statii.night
if prev ~= l_day.status then
l_day.listeners.nighttime:execute()
end
else
l_day.status = l_day.statii.day
if prev ~= l_day.status then
l_day.listeners.daytime:execute()
end
end
end))
end)
Initializer.register(function()
local time = math.floor(GetFloatGameState(GAME_STATE_TIME_OF_DAY) + 0.5)
if time < l_day.DAY_TIME or time >= l_day.NIGHT_TIME then
l_day.status = l_day.statii.night
else
l_day.status = l_day.statii.day
end
end)
function l_day.register(status, func)
if not is_function(func) then return end
if status == l_day.statii.day then
l_day.listeners.daytime:register(func)
elseif status == l_day.statii.night then
l_day.listeners.nighttime:register(func)
end
end
end
do
local sounds = protected_t()
GameSounds = setmetatable({}, sounds)
function sounds.get_error()
return CreateSoundFromLabel("InterfaceError", false, false, false, 10000, 10000)
end
function sounds.get_ping()
return CreateSoundFromLabel("AutoCastButtonClick", false, false, false, 10000, 10000)
end
function sounds.play(snd)
doAfter(0.00, function()
StartSound(snd)
KillSoundWhenDone(snd)
end)
end
function sounds.play_for_player(snd, player)
if GetLocalPlayer() == player then
SetSoundVolume(snd, 127)
else
SetSoundVolume(snd, 0)
end
sounds.play(snd)
end
function sounds.play_for_force(snd, force)
if IsPlayerInForce(GetLocalPlayer(), force) then
SetSoundVolume(snd, 127)
else
SetSoundVolume(snd, 0)
end
sounds.play(snd)
end
Initializer.register("SYSTEM", function()
bj_pingMinimapSound = CreateSoundFromLabel("AutoCastButtonClick", false, false, false, 10000, 10000)
end)
end
ObjectReader = setmetatable({}, protected_t())
do
local m_obj = getmetatable(ObjectReader)
m_obj.BASE_ABIL_ID = FourCC("ANfd")
m_obj.database = {}
-- ObjectReader.read returns a string value.
function m_obj.read(objId, field, format)
objId = ((type(objId) == 'number') and CC2Four(objId)) or objId
format = format or ""
if not m_obj.BASE_ABIL_TEXT then
m_obj.BASE_ABIL_TEXT = BlzGetAbilityExtendedTooltip(m_obj.BASE_ABIL_ID, 0)
end
if not m_obj.database[objId] then
m_obj.database[objId] = {}
end
if not m_obj.database[objId][field] then
local str = "<" .. objId .. "," .. field .. format .. ">"
BlzSetAbilityExtendedTooltip(m_obj.BASE_ABIL_ID, str, 0)
local result = BlzGetAbilityExtendedTooltip(m_obj.BASE_ABIL_ID, 0)
BlzSetAbilityExtendedTooltip(m_obj.BASE_ABIL_ID, m_obj.BASE_ABIL_TEXT, 0)
m_obj.database[objId][field] = result
return result
end
return m_obj.database[objId][field]
end
end
TimerUtils = setmetatable({}, protected_t())
do
local mt = getmetatable(TimerUtils)
local timer_data = {}
local timer_running = {}
mt.createTimer = CreateTimer
mt.destroyTimer = DestroyTimer
mt.timerStart = TimerStart
mt.pauseTimer = PauseTimer
mt.resumeTimer = ResumeTimer
local is_singleton = {isLooping={}, inCallback={}}
local function is_timer(whichtimer)
return tostring(whichtimer):sub(1,5) == 'timer'
end
function GetTimerData(whichtimer)
whichtimer = whichtimer or GetExpiredTimer()
if not is_timer(whichtimer) then return nil end
local id = GetHandleId(whichtimer)
return (not is_singleton[id] and table.unpack(timer_data[id])) or nil
end
function SetTimerData(whichtimer, ...)
if not is_timer(whichtimer) or is_singleton[GetHandleId(whichtimer)] then return end
local id = GetHandleId(whichtimer)
timer_data[id] = {...}
end
function ReleaseTimer(whichtimer)
if not is_timer(whichtimer) or is_singleton[GetHandleId(whichtimer)] then return nil end
local id, temp; id = GetHandleId(whichtimer); temp = timer_data[id];
timer_data[id] = {}
return table.unpack(temp)
end
function CreateTimer()
local result, id; result = mt.createTimer(); id = GetHandleId(result);
timer_data[id] = setmetatable({}, WeakTable.values)
timer_running[id] = false
return result
end
function DestroyTimer(whichtimer)
if not is_timer(whichtimer) or is_singleton[GetHandleId(whichtimer)] then return end
mt.pauseTimer(whichtimer)
local id, temp; id = GetHandleId(whichtimer); temp = timer_data[id];
timer_data[id] = nil
timer_running[id] = nil
mt.destroyTimer(whichtimer)
return table.unpack(temp)
end
function TimerStart(whichtimer, interval, looped, callback)
if not is_timer(whichtimer) or is_singleton[GetHandleId(whichtimer)] then return end
local id = GetHandleId(whichtimer)
timer_running[id] = true
mt.timerStart(whichtimer, interval, looped, function()
timer_running[id] = false
callback()
if type(timer_running[id]) ~= 'nil' then
timer_running[id] = looped
else
callback = nil
end
end)
end
function PauseTimer(whichtimer)
if not is_timer(whichtimer) then return end
local id = GetHandleId(whichtimer)
mt.pauseTimer(whichtimer)
timer_running[id] = false
end
function ResumeTimer(whichtimer)
if not is_timer(whichtimer) or is_singleton.inCallback[GetHandleId(whichtimer)] then return end
local id = GetHandleId(whichtimer)
if timer_running[id] == nil then return end
timer_running[id] = true
mt.resumeTimer(whichtimer)
end
function doAfter(delay, func, ...)
local downval = {...}
local result = mt.createTimer()
local id = GetHandleId(result)
is_singleton[id] = true
is_singleton.isLooping[id] = false
timer_running[id] = true
mt.timerStart(result, delay, false, function()
timer_running[id] = false
is_singleton.inCallback[id] = true
func(table.unpack(downval))
is_singleton.inCallback[id] = nil
is_singleton[id] = nil
timer_running[id] = nil
downval = nil
mt.pauseTimer(result)
mt.destroyTimer(result)
end)
return result
end
function doUntil(interval, func, cond, ...)
local downval = {...}
local result = mt.createTimer()
local id = GetHandleId(result)
local is_func = is_function(cond)
is_singleton[id] = true
is_singleton.isLooping[id] = true
timer_running[id] = true
mt.timerStart(result, interval, true, function()
if (is_func and cond()) or (not is_func and cond) then
is_singleton.inCallback[id] = true
func(table.unpack(downval))
is_singleton.inCallback[id] = false
else
timer_running[id] = false
end
if not timer_running[id] then
is_singleton.inCallback[id] = false
is_singleton[id] = nil
timer_running[id] = nil
downval = nil
is_func = nil
cond = nil
func = nil
mt.pauseTimer(result)
mt.destroyTimer(result)
end
end)
return result
end
function doRepeat(interval, func, ...)
return doUntil(interval, func, function() return true end, ...)
end
end
LinkedList = setmetatable({}, protected_t())
do
local list = LinkedList
local m_list = getmetatable(list)
local iterated = {}
local iters = {list=0, node=0}
local _index = m_list.__index
function m_list:new(o)
o = o or {}
o.head = setmetatable({}, WeakTable.keys)
o.nodes = setmetatable({}, WeakTable.keys)
o.next, o.prev = {}, {}
o.size = 0
o.next[o] = o
o.prev[o] = o
iterated[o] = 0
setmetatable(o, m_list)
return o
end
function m_list:create(o) return self:new(o) end
function m_list.__index(t, k)
if type(k) ~= 'number' then return _index(t, k); else k = math.floor(k); end
if k == 0 then return nil; end
if k > math.floor(t.size/2) then k = k - t.size; end
if k > 0 then
k = math.min(k, t.size)
local iter = t
for i=1, k do
iter = t.next[iter]
end
return iter.data
else
k = math.max(k, -t.size)
for i=-1,k,-1 do
iter = t.prev[iter]
end
return iter.data
end
end
function m_list:insert(...)
if self == list then return nil end
local n = select('#', ...)
local tab2 = setmetatable({}, WeakTable.keyvalues)
if n <= 0 then return self, 0; end
for i = 1,n do
local data = select(i, ...)
local tab = {["data"] = data}
self.next[tab] = self
self.prev[tab] = self.prev[self]
self.next[self.prev[tab]] = tab
self.prev[self.next[tab]] = tab
if not self.head[data] then self.head[data] = 1; else self.head[data] = self.head[data] + 1 end
self.nodes[tab] = true
self.size = self.size + 1
table.insert(tab2, tab)
end
return self, n, table.unpack(tab2)
end
function m_list:shift(...)
if self == list then return nil end
local n = select('#', ...)
local tab2 = setmetatable({}, WeakTable.keyvalues)
if n <= 0 then return self; end
for i = 1,n do
local data = select(i, ...)
local tab = {["data"] = data}
self.prev[tab] = self
self.next[tab] = self.next[self]
self.next[self.prev[tab]] = tab
self.prev[self.next[tab]] = tab
if not self.head[data] then self.head[data] = 1; else self.head[data] = self.head[data] + 1 end
self.nodes[tab] = true
self.size = self.size + 1
table.insert(tab2, 1, tab)
end
return self, n, table.unpack(tab2)
end
function m_list:remove(o)
if self == list then return nil end
if self.head[o] then
local iter = self.next[self]
while iter.data ~= o do
iter = self.next[iter]
end
iter.data = nil
self.nodes[iter] = nil
self.next[self.prev[iter]] = self.next[iter]
self.prev[self.next[iter]] = self.prev[iter]
self.next[iter], self.prev[iter] = nil
self.head[o] = self.head[o] - 1
self.size = self.size - 1
if self.head[o] <= 0 then self.head[o] = nil; end
return self, true
end
return self, false
end
function m_list:erase(node)
if self == list then return nil end
if self.nodes[node] then
self.next[self.prev[node]] = self.next[node]
self.prev[self.next[node]] = self.prev[node]
self.next[node], self.prev[node] = nil
self.head[node.data] = self.head[node.data] - 1
self.nodes[node] = nil
self.size = self.size - 1
if self.head[node.data] <= 0 then self.head[node.data] = nil; end
node.data = nil
return self, true
end
return self, false
end
function m_list:pop()
if self == list then return nil end
if self.size <= 0 then return false, self; end
local iter = self.prev[self]
self.nodes[iter] = nil
self.next[self.prev[iter]] = self.next[iter]
self.prev[self.next[iter]] = self.prev[iter]
self.next[iter], self.prev[iter] = nil
self.head[iter.data] = self.head[iter.data] - 1
self.size = self.size - 1
if self.head[iter.data] <= 0 then self.head[iter.data] = nil; end
iter.data = nil
return true, self
end
function m_list:unshift()
if self == list then return nil end
if self.size <= 0 then return false, self; end
local iter = self.next[self]
self.nodes[iter] = nil
self.next[self.prev[iter]] = self.next[iter]
self.prev[self.next[iter]] = self.prev[iter]
self.next[iter], self.prev[iter] = nil
self.head[iter.data] = self.head[iter.data] - 1
if self.head[iter.data] <= 0 then self.head[iter.data] = nil; end
iter.data = nil
return true, self
end
function m_list:clear()
if self == list then return nil end
while self:pop() do
end
end
function m_list:is_in(o)
if self == list then return false end
return type(self.head[o]) ~= 'nil'
end
function m_list:is_node_in(node)
if self == list then return false end
return self.nodes[node]
end
function m_list:first()
if self == list then return nil end
return self.next[self].data, self.next[self]
end
function m_list:last()
if self == list then return nil end
return self.prev[self].data, self.prev[self]
end
function m_list:destroy()
if self == list then return nil end
self:clear()
self.next[self] = nil
self.prev[self] = nil
self.head = nil
self.size = nil
self.next = nil
self.prev = nil
setmetatable(self, nil)
end
function m_list:__call(...)
local o = self:new()
o:insert(...)
return o
end
do
local function invalid_iterator(strmode)
return (strmode ~= "next") and (strmode ~= "prev")
end
function m_list:iterator(strmode)
strmode = strmode or "next"
if (self == list) or invalid_iterator(strmode) then return nil end
iterated[self] = iterated[self] + 1
if strmode == "next" then
local iter = self
local is_iterated = {}
return function()
while true do
if self.next[iter] == self then break end
if not is_iterated[self.next[iter]] then
is_iterated[self.next[iter]] = true
return self.next[iter].data, self.next[iter]
end
iter = self.next[iter]
is_iterated[iter] = nil
end
iterated[self] = iterated[self] - 1
end
else
local iter = self
local is_iterated = {}
return function()
while true do
if self.prev[iter] == self then break end
if not is_iterated[self.prev[iter]] then
is_iterated[self.prev[iter]] = true
return self.prev[iter].data, self.prev[iter]
end
iter = self.prev[iter]
is_iterated[iter] = nil
end
iterated[self] = iterated[self] - 1
end
end
end
end
function m_list:get_pos(node)
if self:is_node_in(node) then
local i = 0
for _, node2 in self:iterator() do
i = i + 1
if node == node2 then break end
end
return i
end
return 0
end
function m_list:get_pos_elem(elem)
if self:is_in(node) then
local i = 0
for elem2 in self:iterator() do
i = i + 1
if elem == elem2 then break end
end
return i
end
end
end
-- Not to be confused with TriggerHappy's PlayerUtils.
PlayerUtils = setmetatable({}, protected_t())
do
local m_utils = getmetatable(PlayerUtils)
local m_data = {
is_user = {},
is_computer = {},
is_playing = {},
player_list = LinkedList:create(),
all_player_list = LinkedList:create()
}
do
local function is_player(player)
return tostring(player):sub(1,6) == 'player'
end
function m_utils.is_playing(player)
if is_player(player) then player = GetPlayerId(player) end
return m_data.is_playing[player]
end
function m_utils.IsPlaying(player)
return m_utils.is_playing(player)
end
function m_utils.is_user(player)
if is_player(player) then player = GetPlayerId(player) end
return m_data.is_user[player]
end
function m_utils.IsUser(player)
return m_utils.is_user(player)
end
end
function m_utils.is_single_player()
return m_utils.singleplayer
end
Initializer.registerBJ("SYSTEM", function()
local t = CreateTrigger()
TriggerAddCondition(t, Condition(function()
m_data.is_playing[GetPlayerId(GetTriggerPlayer())] = nil
end))
for i = 0,bj_MAX_PLAYER_SLOTS - 1 do
local p = Player(i)
local control = GetPlayerController(p)
m_data.is_user[i] = (control == MAP_CONTROL_USER)
m_data.is_computer[i] = (control == MAP_CONTROL_COMPUTER)
m_data.is_playing[i] = GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING
if m_data.is_user[i] then m_data.player_list:insert(p); TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_LEAVE); end
if m_data.is_user[i] or (control == MAP_CONTROL_COMPUTER) then m_data.all_player_list:insert(p); end
end
m_utils.player = GetLocalPlayer()
m_utils.singleplayer = m_data.player_list.size == 1
end)
end
BuffUtils = setmetatable({}, protected_t())
do
local buff = BuffUtils
local m_buff = getmetatable(buff)
local list = LinkedList:create()
local last_val = {
target = {},
buff = {},
in_callback = false
}
local buff_tb = {list_p={}}
local funcList = {}
m_buff.__metatable = buff
m_buff.interval = 0.03125
m_buff.in_callback = false
m_buff.eventUnit = {}
m_buff.eventBuff = {}
Initializer.registerBJ("SYSTEM", function() m_buff.timer = CreateTimer() end)
function m_buff:new(o)
o = o or {}
o.buffId = 0
o.target = 0
o.delayed = 0
setmetatable(o, m_buff)
return o
end
function m_buff:destroy()
if self == buff then return end
self.buffId = nil
self.target = nil
self.delayed = nil
m_buff.__metatable = nil
setmetatable(o, nil)
m_buff.__metatable = buff
end
function m_buff:create(o)
return self:new(o)
end
function funcList.on_event_throw(func, ...)
last_val.target[#last_val.target + 1] = m_buff.eventUnit
last_val.buff[#last_val.target] = m_buff.eventBuff
func(...)
m_buff.eventUnit = last_val.target[#last_val.target]
m_buff.eventBuff = last_val.buff[#last_val.buff]
last_val.target[#last_val.target] = nil
last_val.buff[#last_val.buff] = nil
func = nil
end
function m_buff:clearEvents()
if rawget(self, 'onDispelEvent') then
funcList.on_event_throw(function()
m_buff.eventUnit = self.target
m_buff.eventBuff = self.buffId
self.onDispelEvent:execute()
end)
self.onDispelEvent:destroy()
self.onDispelEvent = nil
end
if rawget(self, 'observeList') then
for data, key in self.observeList:iterator() do
PauseTimer(data[1])
DestroyTimer(data[1])
self.observeList:erase(key)
data[1], data[2], data[3] = nil
end
self.observeList:destroy()
self.observeList = nil
end
end
function funcList.on_evaluate()
last_val.in_callback = true
-- Iterate through all instances, and check if they still have the buff
for instance, key in list:iterator() do
local id = instance.target
if GetUnitAbilityLevel(instance.target, instance.buffId) == 0 then
if instance.delayed then
instance.delayed = false
else
list:erase(key)
buff_tb[id]:remove(instance)
buff_tb.list_p[id][instance.buffId] = nil
instance:clearEvents()
instance:destroy()
end
end
end
last_val.in_callback = false
end
function funcList.on_observe()
local data = GetTimerData(GetExpiredTimer())
local self = data[3]
m_buff.eventUnit = self.target
m_buff.eventBuff = self.buffId
data[2]()
end
function funcList.on_evaluate_ex()
funcList.on_event_throw(funcList.on_evaluate)
end
function funcList.on_observe_ex()
funcList.on_event_throw(funcList.on_observe)
end
function m_buff:observe(interval, func)
if self == buff then
return
end
if not rawget(self, 'observeList') then
rawset(self, 'observeList', LinkedList:create())
end
local data = {CreateTimer(), func, self}
SetTimerData(data[1], data)
TimerStart(data[1], interval, true, funcList.on_observe_ex)
self.observeList:insert(data)
end
function m_buff:watch(buffId, target, delayed)
buffId = (type(buffId) == 'string' and FourCC(buffId)) or buffId
delayed = (type(delayed) == 'nil' and true) or delayed
if not HandleCheck.is_unit(target) then return end
local id = target
if not buff_tb[id] then
buff_tb[id] = LinkedList:create()
buff_tb.list_p[id] = {}
end
if not buff_tb.list_p[id][buffId] then
-- The object surely can't have been created
local o = buff:create()
o.buffId = buffId
o.target = target
o.delayed = delayed
buff_tb.list_p[id][buffId] = o
buff_tb[id]:insert(o); list:insert(o);
if list.size <= 1 then
TimerStart(m_buff.timer, m_buff.interval, true, funcList.on_evaluate_ex)
end
return o
end
return buff_tb.list_p[id][buffId]
end
function m_buff:on_dispel(func)
if self == buff then return end
if not rawget(self, 'onDispelEvent') then
rawset(self, 'onDispelEvent', EventListener:create())
end
self.onDispelEvent:register(func)
end
UnitDex.register("LEAVE_EVENT", function()
local id = UnitDex.eventUnit
if not buff_tb[id] then return end
for instance, key in buff_tb[id]:iterator() do
list:remove(instance)
buff_tb[id]:erase(key)
buff_tb.list_p[id][instance.buffId] = nil
instance:clearEvents()
instance:destroy()
end
buff_tb[id]:destroy()
buff_tb.list_p[id] = nil
buff_tb[id] = nil
end)
DamageEvent.register_modifier(DamageEvent.MODIFIER_EVENT_SYSTEM, function()
local dmg, dmgtype = DamageEvent.current.dmg, DamageEvent.current.dmgtype
-- Usually, magic damage types are usually dispels.
if (dmg == 0.00 and dmgtype == DAMAGE_TYPE_UNKNOWN) or (dmgtype == DAMAGE_TYPE_MAGIC) then
local id = DamageEvent.current.target
if not buff_tb[id] then return end
for instance, key in buff_tb[id]:iterator() do
if GetUnitAbilityLevel(instance.target, instance.buffId) == 0 then
if instance.delayed then
-- Buff probably came from a missile.
instance.delayed = false
else
list:remove(instance)
buff_tb[id]:erase(key)
buff_tb.list_p[id][instance.buffId] = nil
instance:clearEvents()
instance:destroy()
end
end
end
end
end)
UnitState.register("DEATH_EVENT", function()
local id = UnitState.eventUnit
if not buff_tb[id] then return end
for instance, key in buff_tb[id]:iterator() do
list:remove(instance)
buff_tb[id]:erase(key)
buff_tb.list_p[id][instance.buffId] = nil
instance:clearEvents()
instance:destroy()
end
end)
end
DummyUtils = setmetatable({}, protected_t())
do
--[[
DummyUtils
- Inspired by Nestharus' dummy allocation and deallocation algorithm.
an O(1) complexity algorithm.
]]
local m_utils = getmetatable(DummyUtils)
local operational = false
local list = {upper=LinkedList:create(), lower=LinkedList:create(), pointer={}, angle={}}
local dummy_flag = {}
local total_count = 0
m_utils.DUMMY_TYPE = FourCC('dumi')
-- PRELOAD_AMOUNT/LIST_COUNT will always round up to an integer.
m_utils.PRELOAD_AMOUNT = 60
m_utils.LIST_COUNT = 8
m_utils.FACING_OFFSET = 360/m_utils.LIST_COUNT
m_utils.PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
do
for id = 1,m_utils.LIST_COUNT do
list[id] = LinkedList:create()
list.angle[id] = id * m_utils.FACING_OFFSET
list.pointer[id] = select(3, list.upper:insert(id))
list.pointer[list[id]] = list.upper
end
end
local function SetUnitFacing(whichunit, facing)
if BlzSetUnitFacingEx then
BlzSetUnitFacingEx(whichunit, facing)
else
_G['SetUnitFacing'](whichunit, facing)
end
end
do
local rectX, rectY
Initializer.registerBJ("SYSTEM", function()
rectX, rectY = WorldRect.rectMinX, WorldRect.rectMinY
m_utils.PRELOAD_AMOUNT = math.ceil(math.max(m_utils.PRELOAD_AMOUNT, 1)/m_utils.LIST_COUNT)*m_utils.LIST_COUNT
for i = 1, m_utils.PRELOAD_AMOUNT do
local j = math.fmod(i, m_utils.LIST_COUNT); j = ((j==0) and 8) or j
local dummy = CreateUnit(m_utils.PLAYER, m_utils.DUMMY_TYPE, rectX, rectY, list.angle[j])
if not dummy then print_after("DummyUtils.initialization >> Dummy could not be created."); return end
PauseUnit(dummy, true)
ShowUnit(dummy, false)
BlzUnitDisableAbility(dummy, FourCC("Aatk"), true, false)
list[j]:insert(dummy)
dummy_flag[dummy] = "free"
total_count = total_count + 1
end
if not operational then operational = true; end
end)
function m_utils.request(player, x, y, z, facing)
x, y, z, facing = x or 0, y or 0, z or 0, facing or 0
if not operational then print("DummyUtils.request >> System is not operational."); return nil; end
if total_count > 0 then
local j = math.fmod(math.floor(facing/m_utils.FACING_OFFSET + 0.5), m_utils.LIST_COUNT)
j = ((j == 0) and m_utils.LIST_COUNT) or j
local dummy, pointer = list[j]:first()
list[j]:erase(pointer)
total_count = total_count - 1
-- Preparing the unit
SetUnitX(dummy, x); SetUnitY(dummy, y); SetUnitFlyHeight(dummy, z, 0.00);
ShowUnit(dummy, true); PauseUnit(dummy, false);
UnitRemoveAbility(dummy, FourCC("Aloc")); UnitAddAbility(dummy, FourCC("Aloc"));
SetUnitOwner(dummy, player, true); SetUnitFacing(dummy, facing);
BlzUnitDisableAbility(dummy, FourCC("Aatk"), false, false)
dummy_flag[dummy] = "used"
if list.pointer[list[j]] == list.upper then
-- Located in the upper list.
list.upper:erase(list.pointer[j])
list.pointer[j] = select(3, list.lower:insert(j))
list.pointer[list[j]] = list.lower
else
local k = list.upper:first()
local nextDummy, nextPointer = list[k]:first()
SetUnitFacing(nextDummy, list.angle[j])
list[k]:erase(nextPointer)
list[j]:insert(nextDummy)
list.upper:erase(list.pointer[k])
list.pointer[k] = select(3, list.lower:insert(k))
list.pointer[list[k]] = list.lower
end
if list.upper.size <= 0 then
local swap = list.upper
list.upper, list.lower = list.lower, swap
end
bj_lastCreatedUnit, dummy = dummy, nil
return bj_lastCreatedUnit
end
bj_lastCreatedUnit = CreateUnit(player, m_utils.DUMMY_TYPE, x, y, facing)
SetUnitFlyHeight(bj_lastCreatedUnit, z, 0.0)
dummy_flag[bj_lastCreatedUnit] = "used"
return bj_lastCreatedUnit
end
function m_utils.recycle(dummy)
if not operational then print("DummyUtils.recycle >> System is not operational."); return false; end
if (not dummy_flag[dummy]) or (dummy_flag[dummy] == "free") then return false; end
-- Populate the lower list first.
if list.lower.size <= 0 then
local swap = list.upper
list.upper, list.lower = list.lower, swap
end
local j, pointer = list.lower:first()
-- Prepare the unit for recycling
SetUnitX(dummy, rectX); SetUnitY(dummy, rectY);
SetUnitFacing(dummy, list.angle[j]); SetUnitOwner(dummy, m_utils.PLAYER, true);
ShowUnit(dummy, false); PauseUnit(dummy, true);
BlzUnitDisableAbility(dummy, FourCC("Aatk"), true, false)
list[j]:insert(dummy)
dummy_flag[dummy] = "free"
total_count = total_count + 1
list.lower:erase(pointer)
list.pointer[j] = select(3, list.upper:insert(j))
list.pointer[list[j]] = list.upper
return true
end
end
end
ResearchUtils = setmetatable({}, protected_t())
do
local utils = ResearchUtils
local m_utils = getmetatable(utils)
local is_listened = {}
local researchList = LinkedList:create()
m_utils.__metatable = utils
Initializer.registerBJ("SYSTEM", function()
is_listened.GROUP = CreateGroup()
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_RESEARCH_FINISH, function()
local researchId = GetResearched()
if is_listened[researchId] then
-- The researchId is being anticipated.
local play = GetOwningPlayer(GetResearchingUnit())
local play_id = GetPlayerId(play)
local prev_level = is_listened[researchId][play_id]
local next_level = GetPlayerTechCount(play, researchId, true)
is_listened[researchId][play_id] = next_level
GroupEnumUnitsOfPlayer(is_listened.GROUP, play, nil)
ForGroup(is_listened.GROUP, function()
local uu, uuType; uu = GetEnumUnit(); uuType = GetUnitTypeId(uu)
if is_listened[researchId].unitTypes:is_in(uuType) then
is_listened[researchId].callback:execute(uu, prev_level, next_level)
end
end)
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CHANGE_OWNER, function()
local u, unitId; u = GetTriggerUnit(); unitId = GetUnitTypeId(u);
local cur_owner, prev_owner = GetOwningPlayer(u), GetChangingUnitPrevOwner();
local cur_owner_id, prev_owner_id = GetPlayerId(cur_owner), GetPlayerId(prev_owner);
for researchId in researchList:iterator() do
if is_listened[researchId].unitTypes:is_in(unitId) then
local prev_level = is_listened[researchId][prev_owner_id]
local next_level = is_listened[researchId][cur_owner_id]
if prev_level ~= next_level then
is_listened[researchId].callback:execute(u, prev_level, next_level)
end
end
end
end)
UnitDex.register("ENTER_EVENT", function()
local u, unitId; u = UnitDex.eventUnit; unitId = GetUnitTypeId(u);
local cur_owner, cur_owner_id; cur_owner = GetOwningPlayer(u); cur_owner_id = GetPlayerId(cur_owner);
for researchId in researchList:iterator() do
if is_listened[researchId].unitTypes:is_in(unitId) then
local prev_level = 0
local next_level = is_listened[researchId][cur_owner_id]
if prev_level ~= next_level then
is_listened[researchId].callback:execute(u, prev_level, next_level)
end
end
end
end)
do
local techFunc = {}
techFunc.setPlayerTechRes = SetPlayerTechResearched
techFunc.addPlayerTechRes = AddPlayerTechResearched
techFunc.decPlayerTechRes = BlzDecPlayerTechResearched
function SetPlayerTechResearched(whichPlayer, techid, setToLevel)
if is_listened[techid] then
local player_id = GetPlayerId(whichPlayer)
local prev_level = is_listened[techid][player_id]
end
techFunc.setPlayerTechRes(whichPlayer, techid, setToLevel)
if is_listened[techid] then
local next_level = GetPlayerTechCount(whichPlayer, techid, true)
is_listened[techid][player_id] = next_level
if prev_level ~= next_level then
GroupEnumUnitsOfPlayer(is_listened.GROUP, whichPlayer, nil)
ForGroup(is_listened.GROUP, function()
local uu, uuType; uu = GetEnumUnit(); uuType = GetUnitTypeId(uu)
if is_listened[researchId].unitTypes:is_in(uuType) then
is_listened[researchId].callback:execute(uu, prev_level, next_level)
end
end)
end
end
end
function AddPlayerTechResearched(whichPlayer, techid, levels)
if is_listened[techid] then
local player_id = GetPlayerId(whichPlayer)
local prev_level = is_listened[techid][player_id]
end
techFunc.addPlayerTechRes(whichPlayer, techid, levels)
if is_listened[techid] then
local next_level = GetPlayerTechCount(whichPlayer, techid, true)
is_listened[techid][player_id] = next_level
if prev_level ~= next_level then
GroupEnumUnitsOfPlayer(is_listened.GROUP, whichPlayer, nil)
ForGroup(is_listened.GROUP, function()
local uu, uuType; uu = GetEnumUnit(); uuType = GetUnitTypeId(uu)
if is_listened[researchId].unitTypes:is_in(uuType) then
is_listened[researchId].callback:execute(uu, prev_level, next_level)
end
end)
end
end
end
function BlzDecPlayerTechResearched(whichPlayer, techid, levels)
if is_listened[techid] then
local player_id = GetPlayerId(whichPlayer)
local prev_level = is_listened[techid][player_id]
end
techFunc.decPlayerTechRes(whichPlayer, techid, levels)
if is_listened[techid] then
local next_level = GetPlayerTechCount(whichPlayer, techid, true)
is_listened[techid][player_id] = next_level
if prev_level ~= next_level then
GroupEnumUnitsOfPlayer(is_listened.GROUP, whichPlayer, nil)
ForGroup(is_listened.GROUP, function()
local uu, uuType; uu = GetEnumUnit(); uuType = GetUnitTypeId(uu)
if is_listened[researchId].unitTypes:is_in(uuType) then
is_listened[researchId].callback:execute(uu, prev_level, next_level)
end
end)
end
end
end
end
end)
-- researchId -> The research id to register, duh
-- callback -> The callback function to when the level changes
-- ... -> The unit-types that will be affected by this researchId.
-- -> The unit-types that will be affected can only be defined once.
function m_utils.register_research(researchId, callback, ...)
if not is_function(callback) then return end
--if type(researchId) == 'string' then researchId = FourCC(researchId); end
researchId = (type(researchId) == "string" and FourCC(researchId)) or researchId
if not is_listened[researchId] then
local container = setmetatable({}, WeakTable.values)
if select('#', ...) > 0 then
for i = 1,select('#', ...) do
local j = select(i, ...); j = (type(j) == 'string' and FourCC(j)) or j;
container[i] = j
end
end
is_listened[researchId] = {
callback = EventListener:create(),
unitTypes = LinkedList(table.unpack(container))
}
is_listened[researchId].pointer = select(3, researchList:insert(researchId))
for i = 0,bj_MAX_PLAYER_SLOTS - 1 do
local p = Player(i)
is_listened[researchId][i] = GetPlayerTechCount(p, researchId, true)
end
end
is_listened[researchId].callback:register(callback)
end
end
IsDestructableTree = setmetatable({}, protected_t())
--[[
*************************************************************************************
*
* Detect whether a destructable is a tree or not.
*
***************************************************************************
*
* Credits
*
* To PitzerMike
* -----------------------
*
* for IsDestructableTree
*
*************************************************************************************
*
* Functions
*
* function IsDestructableTree takes destructable d returns boolean
*
* function IsDestructableAlive takes destructable d returns boolean
*
* function IsDestructableDead takes destructable d returns boolean
*
* function IsTreeAlive takes destructable tree returns boolean
* - May only return true for trees.
*
* function KillTree takes destructable tree returns boolean
* - May only kill trees.
*
**************************************************************************************
*
* Translated to LUA.
*/
]]
do
local m_dest_tree = getmetatable(IsDestructableTree)
m_dest_tree.HARVESTER_UNIT_ID = FourCC('hpea') -- human peasant
m_dest_tree.HARVEST_ABILITY = FourCC('Ahrl') -- ghoul harvest
m_dest_tree.HARVEST_ORDER_ID = 0xD0032 -- harvest order ( 852018 )
m_dest_tree.NEUTRAL_PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
Initializer.registerBJ("SYSTEM", function()
m_dest_tree.harvester = CreateUnit(m_dest_tree.NEUTRAL_PLAYER, m_dest_tree.HARVESTER_UNIT_ID, 0, 0, 0)
UnitAddAbility(m_dest_tree.harvester, m_dest_tree.HARVEST_ABILITY)
UnitAddAbility(m_dest_tree.harvester, FourCC('Aloc'))
ShowUnit(m_dest_tree.harvester, false)
end)
function m_dest_tree:__call(d)
return (IssueTargetOrderById(m_dest_tree.harvester, m_dest_tree.HARVEST_ORDER_ID, d)) and (IssueImmediateOrderById(m_dest_tree.harvester, 851973))
end
function IsDestructableDead(d)
return (GetWidgetLife(d) <= 0.405)
end
function IsDestructableAlive(d)
return (GetWidgetLife(d) > .405)
end
function IsTreeAlive(tree)
return IsDestructableAlive(tree) and IsDestructableTree(tree)
end
function KillTree(tree)
if (IsTreeAlive(tree)) then
KillDestructable(tree)
return true
end
return false
end
end
do
local t = {DISPLAY_DURATION = 3.00}
Initializer.registerBJ("SYSTEM", function()
t.timer = {}
t.chatFrame = BlzGetOriginFrame(ORIGIN_FRAME_UNIT_MSG, 0)
t.chatDisplay = BlzCreateFrame("BACKDROP", t.chatFrame, 1, 0)
BlzFrameSetAllPoints(t.chatDisplay, t.chatFrame)
BlzFrameSetTextColor(frame, 0xffffcc00)
BlzFrameSetVisible(t.chatDisplay, true)
local i = 0
while i < bj_MAX_PLAYER_SLOTS do
t.timer[i] = CreateTimer()
i = i + 1
end
end)
function t.genSound()
return GameSounds.get_error()
end
function t.playSound(player)
local snd = t.genSound()
GameSounds.play_for_player(snd, player)
end
function GameError(player, msg)
local i = GetPlayerId(player)
t.playSound(player)
-- Create a handle using the text's origin frame
msg = "|cffffcc00" .. msg .. "|r"
if GetLocalPlayer() == player then
ClearTextMessages()
DisplayTimedTextToPlayer(player, 0, 0, t.DISPLAY_DURATION, msg)
BlzFrameSetText(t.chatDisplay, msg)
end
PauseTimer(t.timer[i])
TimerStart(t.timer[i], t.DISPLAY_DURATION, false, function(player)
if GetLocalPlayer() == player then
BlzFrameSetText(t.chatDisplay, "")
end
end, player)
end
end
Group = setmetatable({}, protected_t())
do
local m_group = getmetatable(Group)
local GROUP = CreateGroup()
m_group.__metatable = Group
function m_group.iterator(whichgroup)
if not HandleCheck.is_group(whichgroup) then return nil end
local i, j = 0, BlzGroupGetSize(whichgroup)
local f = function()
if i < j then
i = i + 1
-- Prevent holes from terminating the loop too early.
return BlzGroupUnitAt(whichgroup, i - 1), i - 1
end
end
return f
end
function m_group:__call()
return GROUP
end
end
UnitUtils = setmetatable({}, protected_t())
do
local m_utils = getmetatable(UnitUtils)
m_utils._CROW_FORM = FourCC("Amrf")
m_utils._REFRESH_ABIL = FourCC("uRef")
m_utils._INTERVAL = (EffectUtils and EffectUtils.INTERVAL) or 1/32.
m_utils._DEF_BEZ = BezierEasing.linear--BezierEasing.create(0, 0, 0, 0)
m_utils.__metatable = UnitUtils
m_utils.time = {total={}}
m_utils.height = {starth={}, endh={}}
m_utils.hold = {list=LinkedList:create(), pointer={}, bezier={}}
local _func = {
setHeight = SetUnitFlyHeight,
disableAbil = BlzUnitDisableAbility,
}
Initializer.registerBJ("SYSTEM", function()
local tempFunc = BlzUnitDisableAbility
m_utils._TIMER = CreateTimer()
BlzUnitDisableAbility = _func.disableAbil
m_utils.DUMMY = DummyUtils.request()
BlzUnitDisableAbility = tempFunc
m_utils.DUMMY_TIMER = CreateTimer()
UnitAddAbility(m_utils.DUMMY, m_utils._SILENCE)
UnitMakeAbilityPermanent(m_utils.DUMMY, true, m_utils._SILENCE)
TimerStart(m_utils.DUMMY_TIMER, 1.00, false, nil)
SetUnitPropWindow(m_utils.DUMMY, 0.)
end)
function _func.on_height_update()
for unit in m_utils.hold.list:iterator() do
if UnitAddAbility(targ, m_utils._CROW_FORM) then UnitRemoveAbility(targ, m_utils._CROW_FORM) end
m_utils.time[unit] = m_utils.time[unit] + m_utils._INTERVAL
local delta = m_utils.time[unit]/m_utils.time.total[unit]
local diff = m_utils.height.endh[unit] - m_utils.height.starth[unit]
if delta < 1 then
_func.setHeight(unit, m_utils.height.starth[unit] + diff*m_utils.hold.bezier[unit][delta], 0)
else
m_utils.hold.list:erase(m_utils.hold.pointer[unit])
_func.setHeight(unit, m_utils.height.endh[unit], 0)
m_utils.height.endh[unit], m_utils.height.starth[unit] = nil
m_utils.time[unit], m_utils.time.total[unit] = nil
m_utils.hold.bezier[unit], m_utils.hold.pointer[unit] = nil
end
end
if m_utils.hold.list == 0 then
PauseTimer(m_utils._TIMER)
end
end
function SetUnitFlyHeight(unit, height, dur, bezier)
bezier = bezier or m_utils._DEF_BEZ
dur = dur or 1
if UnitAddAbility(unit, m_utils._CROW_FORM) then
UnitRemoveAbility(unit, m_utils._CROW_FORM)
end
if dur <= 0 then
_func.setHeight(unit, height, 0)
return
end
m_utils.height.starth[unit] = GetUnitFlyHeight(unit)
m_utils.height.endh[unit] = height
if not m_utils.hold.pointer[unit] then
m_utils.hold.pointer[unit] = select(3, m_utils.hold.list:insert(unit))
if m_utils.hold.list.size == 1 then
TimerStart(m_utils._TIMER, m_utils._INTERVAL, true, _func.on_height_update)
end
end
m_utils.time[unit] = 0
m_utils.time.total[unit] = dur
m_utils.hold.bezier[unit] = bezier
end
function DisableUnitMovement(unit)
_func.disableAbil(unit, FourCC("Amov"), true, false)
end
function EnableUnitMovement(unit)
_func.disableAbil(unit, FourCC("Amov"), false, false)
end
function GetUnitMaxHP(unit)
local hp_preserve = GetWidgetLife(unit)
local hp_prev
SetWidgetLife(unit, BlzGetUnitMaxHP(unit))
while true do
hp_prev = GetWidgetLife(unit)
SetWidgetLife(unit, hp_prev*16)
if math.abs(GetWidgetLife(unit)-hp_prev) < 1 then break end
end
SetWidgetLife(unit, hp_preserve)
return math.floor(hp_prev + 0.5)
end
function GetUnitMaxMana(unit)
local mp_preserve = GetUnitState(unit, UNIT_STATE_MANA)
local mp_prev
SetUnitState(unit, UNIT_STATE_MANA, 100.)
while true do
mp_prev = GetUnitState(unit, UNIT_STATE_MANA)
SetUnitState(unit, UNIT_STATE_MANA, mp_prev*16)
if math.abs(GetUnitState(unit, UNIT_STATE_MANA)-mp_prev) < 1 then break end
end
SetUnitState(unit, UNIT_STATE_MANA, mp_preserve)
return math.floor(mp_prev + 0.5)
end
function RefreshUnit(unit)
UnitAddAbility(unit, m_utils._REFRESH_ABIL)
UnitRemoveAbility(unit, m_utils._REFRESH_ABIL)
end
end
do
local heal = protected_t()
heal.info = {
SYSTEM_HEAL = FourCC("Syhl"),
SYSTEM_BUFF = FourCC("BShl"),
SYSTEM_ORDER = "heal",
SYSTEM_DELTA = 1000.,
SYSTEM_AMOUNT = 100.,
}
Initializer("SYSTEM", function()
heal.DUMMY = DummyUtils.request()
UnitAddAbility(heal.DUMMY, heal.info.SYSTEM_HEAL)
BlzSetAbilityRealLevelField(BlzGetUnitAbility(heal.DUMMY, heal.info.SYSTEM_HEAL), ABILITY_RLF_HIT_POINTS_GAINED_HEA1, 0, heal.info.SYSTEM_AMOUNT)
BlzUnitDisableAbility(heal.DUMMY, FourCC("Amov"), true, false)
end)
local function do_heal(unit, prev_hp)
local regen_amnt = 0.
SetUnitX(heal.DUMMY, GetUnitX(unit)); SetUnitY(heal.DUMMY, GetUnitY(unit))
if IssueTargetOrder(heal.DUMMY, heal.info.SYSTEM_ORDER, unit) and IssueImmediateOrder(heal.DUMMY, "stop") then
print("Healing for everyone.")
if UnitRemoveAbility(unit, heal.info.SYSTEM_BUFF) then
print("GetUnitRegenEffectRate >> The buff was immediately placed and removed.")
else
print("GetUnitRegenEffectRate >> The buff was not immediately placed and removed.")
end
regen_amnt = (GetWidgetLife(unit) - prev_hp)/heal.info.SYSTEM_AMOUNT
end
return regen_amnt
end
function GetUnitRegenEffectRate(unit)
local prev_hp = GetWidgetLife(unit)
BlzSetUnitMaxHP(unit, BlzGetUnitMaxHP(unit) + heal.info.SYSTEM_DELTA)
local regen_amnt = do_heal(unit, prev_hp)
BlzSetUnitMaxHP(unit, BlzGetUnitMaxHP(unit) - heal.info.SYSTEM_DELTA)
SetWidgetLife(unit, prev_hp)
return regen_amnt
end
function HealUnit(unit, amount)
amount = math.abs(amount or 0)*GetUnitRegenEffectRate(unit)
print(amount)
UnitDamageTargetPure(unit, unit, -amount)
end
end
do
--[[
// Object generator
//! external ObjectMerger w3u hgry hPLF unsf "(PathingLib) FlyChecker" unam "" umdl ".mdl" ubdg 0 uabi "Aloc" uble 0 ulum 0 upoi 0 uico "" umxp 0 umxr 0 ussc 0 ushu "" ugol 0 uaen "" udea "" umvt "fly" usnd "" ufle 0 ufoo 0 uspe 1 uhom 1 urac "unknown" usid 0 usin 0 upgr "" uhot "" utip "" utub ""
//! external ObjectMerger w3u hfoo hPLW unsf "(PathingLib) WalkChecker" unam "" umdl ".mdl" ubdg 0 uabi "Aloc" uble 0 ulum 0 upoi 0 uico "" umxp 0 umxr 0 ussc 0 ushu "" ugol 0 uaen "" udea "" umvt "foot" ucol 0 usnd "" ufle 0 ufoo 0 uspe 1 uhom 1 urac "unknown" usid 0 usin 0 upgr "" uhot "" utip "" utub ""
//! external ObjectMerger w3u hhou hPLB unsf "(PathingLib) BuildChecker" unam "" umdl ".mdl" ubdg 1 uabi "Aloc" upat "" ulum 0 upoi 0 uubs "" uble 0 ushb "" uico "" ugol 0 ufma 0 umxp 0 ubsl "" umxr 0 ussc 0 ushu "" uaen "" udea "" umvt "" ucol 0 usnd "" ufle 0 ufoo 0 uspe 1 uhom 1 urac "unknown" usid 0 usin 0 upgr "" uhot "" utip "" utub ""
//! external ObjectMerger w3u hpea hPLP unsf "(PathingLib) PathChecker" unam "" umdl ".mdl" ubdg 0 uabi "Aloc" uble 0 ubui "hPLF,hPLW,hPLB" ulum 0 upoi 0 uico "" umxp 0 umxr 0 ussc 0 ushu "" ugol 0 uaen "" udea "" umvt "foot" ucol 0 usnd "" ufle 0 ufoo 0 uspe 1 uhom 1 urac "unknown" usid 0 usin 0 upgr "" uhot "" utip "" utub ""
// Configuration
]]
local PATH_CHECKER = FourCC('hPLP')
local FLY_CHECKER = FourCC('hPLF')
local WALK_CHECKER = FourCC('hPLW')
local BUILD_CHECKER = FourCC('hPLB')
local DUMMY_PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
--[[
Pathing Library v1.6
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
Description
¯¯¯¯¯¯¯¯¯¯¯
Allows you to detect all pathability types:
walkablility, flyability, and buildability.
Warning!
Please keep informed that this system is more sensitive than
any other walkability checker systems out there, as it also
detects pathing map generated by normal units as well.
API
¯¯¯
| function IsTerrainFlyable takes real x, real y returns boolean
| function IsTerrainWalkable takes real x, real y returns boolean
| function IsTerrainBuildable takes real x, real y returns boolean
How to import
¯¯¯¯¯¯¯¯¯¯¯¯¯
- Copy Fly, Walk, Build, and Path Checker at object editor (Unit).
- Make sure Path Checker is able to build Fly, Walk, and Build Checker
(at object editor>unit>Path Checker>"Techtree - Structures built")
- Configure this system correctly.
Link: hiveworkshop.com/forums/spells-569/pathing-type-v1-2-a-263230/
Transpiled to Lua.
]]
local PathChecker
function IsTerrainFlyable(x, y)
return IssueBuildOrderById(PathChecker, FLY_CHECKER, x, y)
end
function IsTerrainWalkable(x, y)
return IssueBuildOrderById(PathChecker, WALK_CHECKER, x, y)
end
function IsTerrainBuildable(x, y)
return IssueBuildOrderById(PathChecker, BUILD_CHECKER, x, y)
end
Initializer.registerBJ("SYSTEM", function()
PathChecker = CreateUnit(DUMMY_PLAYER, PATH_CHECKER, 0, 0, 0)
UnitRemoveAbility(PathChecker, FourCC("Amov"))
ShowUnit(PathChecker, false)
if GetLocalPlayer() == DUMMY_PLAYER then
FogEnable(false)
end
end)
end
do
local DUMMY_UNIT
local _CX, _CY = 0., 0. -- Initial coordinates
Initializer.registerBJ("SYSTEM", function()
DUMMY_UNIT = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), FourCC("hpea"), _CX, _CY, bj_UNIT_FACING)
SetUnitInvulnerable(DUMMY_UNIT, true)
ShowUnit(DUMMY_UNIT, false)
end)
function GroundCoordinates(tx, ty)
SetUnitPosition(DUMMY_UNIT, tx, ty)
tx, ty = GetUnitX(DUMMY_UNIT), GetUnitY(DUMMY_UNIT)
SetUnitX(DUMMY_UNIT, WorldRect.rectMinX)
SetUnitY(DUMMY_UNIT, WorldRect.rectMinY)
return tx, ty
end
end
--[[
* IsTerrainWalkable snippet for estimating the walkability status of a co-ordinate pair, credits
* to Anitarf and Vexorian.
*
* API:
* boolean IsTerrainWalkableEx(real x, real y) - returns the walkability of (x,y)
* - Altered by MyPad, conflict with existing IsTerrainWalkable function.
*/
]]
do
-- this value is how far from a point the item may end up for the point to be considered pathable
local MAX_RANGE = 10.
local MAX_ENUM = 128.
-- the following two variables are set to the position of the item after each pathing check
-- that way, if a point isn't pathable, these will be the coordinates of the nearest point that is
local tb = protected_t({hidden = {}})
IsTerrainWalkableEx = setmetatable({}, tb)
MAX_RANGE = MAX_RANGE*MAX_RANGE
Initializer("SYSTEM", function()
tb.check = CreateItem(FourCC('ciri'), 0., 0.)
tb.r = Rect(0.0, 0.0, MAX_ENUM, MAX_ENUM)
SetItemVisible(tb.check, false)
end)
function tb:__call(x, y)
-- first, hide any items in the area so they don't get in the way of our item
MoveRectTo(tb.r, x, y)
EnumItemsInRect(tb.r, nil, function()
if IsItemVisible(GetEnumItem()) then
tb.hidden[#tb.hidden + 1]=GetEnumItem()
SetItemVisible(tb.hidden[#tb.hidden], false)
end
end)
-- try to move the check item and get its coordinates
-- this unhides the item...
SetItemPosition(tb.check, x, y)
tb.X=GetItemX(tb.check)
tb.Y=GetItemY(tb.check)
--...so we must hide it again
SetItemVisible(tb.check,false)
-- before returning, unhide any items that got hidden at the start
repeat
SetItemVisible(tb.hidden[#tb.hidden], true)
tb.hidden[#tb.hidden] = nil
until #tb.hidden < 1
-- return pathability status
local dist = (x-tb.X)*(x-tb.X)+(y-tb.Y)*(y-tb.Y)
local flag = dist<MAX_RANGE
return (x-tb.X)*(x-tb.X)+(y-tb.Y)*(y-tb.Y)<MAX_RANGE
end
end
LightningUtils = setmetatable({}, protected_t())
do
local m_light = getmetatable(LightningUtils)
local pos = {cx = {}, cy = {}, cz = {}, tx = {}, ty = {}, tz = {}}
local color = {red= {}, green = {}, blue = {}, alpha = {}}
function m_light:add(codename, vis, cx, cy, tx, ty)
cx, cy, tx, ty = cx or 0, cy or 0, tx or 0, ty or 0
local o = setmetatable({lightning=AddLightning(codename, vis, cx, cy, tx, ty)}, m_light)
pos.cx[o], pos.cy[o], pos.cz[o] = cx, cy, 0
pos.tx[o], pos.ty[o], pos.tz[o] = tx, ty, 0
color.red[o], color.green[o], color.blue[o], color.alpha[o] = 255, 255, 255, 255
return o
end
function m_light:addEX(codename, vis, cx, cy, cz, tx, ty, tz)
cx, cy, cz, tx, ty, tz = cx or 0, cy or 0, cz or 0, tx or 0, ty or 0, tz or 0
local o = setmetatable({lightning=AddLightningEx(codename, vis, cx, cy, cz, tx, ty, tz)}, m_light)
pos.cx[o], pos.cy[o], pos.cz[o] = cx, cy, cz
pos.tx[o], pos.ty[o], pos.tz[o] = tx, ty, tz
color.red[o], color.green[o], color.blue[o], color.alpha[o] = 255, 255, 255, 255
return o
end
function m_light:destroy()
pos.cx[self], pos.cy[self], pos.cz[self] = nil
pos.tx[self], pos.ty[self], pos.tz[self] = nil
DestroyLightning(self.lightning)
color.red[self], color.green[self], color.blue[self], color.alpha[self] = nil
self.lightning = nil
setmetatable(self, nil)
end
function m_light:move(vis, cx, cy, tx, ty)
pos.cx[self], pos.cy[self], pos.tx[self], pos.ty[self] = cx or 0, cy or 0, tx or 0, ty or 0
MoveLightning(self.lightning, vis, pos.cx[self], pos.cy[self], pos.tx[self], pos.ty[self])
end
function m_light:moveEX(vis, cx, cy, cz, tx, ty, tz)
pos.cx[self], pos.cy[self], pos.cz[self], pos.tx[self], pos.ty[self], pos.tz[self] = cx or 0, cy or 0, cz or 0, tx or 0, ty or 0, tz or 0
MoveLightningEx(self.lightning, vis, pos.cx[self], pos.cy[self], pos.cz[self], pos.tx[self], pos.ty[self], pos.tz[self])
end
function m_light:set_color(a, b, g, r)
a = math.max(a or color.alpha[self]/255, 1)
g = math.max(g or color.green[self]/255, 1)
b = math.max(b or color.blue[self]/255, 1)
r = math.max(r or color.red[self]/255, 1)
color.red[self], color.green[self], color.blue[self], color.alpha[self] = r*255, g*255, b*255, a*255
SetLightningColor(self.lightning, r, g, b, a)
end
function m_light:get_alpha()
return color.alpha[self]
end
function m_light:get_green()
return color.green[self]
end
function m_light:get_blue()
return color.blue[self]
end
function m_light:get_red()
return color.red[self]
end
function m_light:set_alpha(value)
self:set_color(value)
end
function m_light:set_blue(value)
self:set_color(nil, value)
end
function m_light:set_green(value)
self:set_color(nil, nil, value)
end
function m_light:set_red(value)
self:set_color(nil, nil, nil, value)
end
function m_light:get_cx()
return pos.cx[self]
end
function m_light:get_cy()
return pos.cy[self]
end
function m_light:get_cz()
return pos.cz[self]
end
function m_light:get_tx()
return pos.tx[self]
end
function m_light:get_ty()
return pos.ty[self]
end
function m_light:get_tz()
return pos.tz[self]
end
end
do
local m_light = getmetatable(LightningUtils)
local utils = {list=LinkedList:create()}
local dx,dy,dz = {},{},{}
local _destroy = m_light.destroy
m_light.custom = {
INTERVAL = 1/32.
}
Initializer.registerBJ("SYSTEM", function() m_light.custom.TIMER = CreateTimer() end)
local states = {"move", "moveEx", "color"}
states.move = function(self)
if utils[self].move then
utils[self].move.time = utils[self].move.time + m_light.custom.INTERVAL
local delta = utils[self].move.time/utils[self].move.dur
if delta > 1 then
self:move(utils[self].move.vis, utils[self].move.cx, utils[self].move.cy,
utils[self].move.tx, utils[self].move.ty)
if utils[self].move.onEvent then
pcall(utils[self].move.onEvent, self)
end
utils[self].move = nil
utils[self].stack = utils[self].stack - 1
else
dx[1], dx[2] = utils[self].move.cx - self:get_cx(), utils[self].move.tx - self:get_tx()
dy[1], dy[2] = utils[self].move.cy - self:get_cy(), utils[self].move.ty - self:get_ty()
dx[3], dx[4] = self:get_cx() + dx[1]*delta, self:get_tx() + dx[2]*delta
dy[3], dy[4] = self:get_cy() + dy[1]*delta, self:get_ty() + dy[2]*delta
MoveLightning(self.lightning, utils[self].move.vis, dx[3], dy[3], dx[4], dy[4])
if utils[self].move.onUpdate then
utils[self].move.onUpdate(self, dx[3], dy[3], dx[4], dy[4])
end
end
end
end
states.moveEx = function(self)
if utils[self].moveEx then
utils[self].moveEx.time = utils[self].moveEx.time + m_light.custom.INTERVAL
local delta = utils[self].moveEx.time/utils[self].moveEx.dur
if delta > 1 then
self:moveEx(utils[self].moveEx.vis, utils[self].moveEx.cx, utils[self].moveEx.cy, utils[self].moveEx.cz,
utils[self].moveEx.tx, utils[self].moveEx.ty, utils[self].moveEx.tz)
if utils[self].moveEx.onEvent then
pcall(utils[self].moveEx.onEvent, self)
end
utils[self].moveEx = nil
utils[self].stack = utils[self].stack - 1
else
dx[1], dx[2] = utils[self].moveEx.cx - self:get_cx(), utils[self].moveEx.tx - self:get_tx()
dy[1], dy[2] = utils[self].moveEx.cy - self:get_cy(), utils[self].moveEx.ty - self:get_ty()
dz[1], dz[2] = utils[self].moveEx.cz - self:get_cz(), utils[self].moveEx.tz - self:get_tz()
dx[3], dx[4] = self:get_cx() + dx[1]*delta, self:get_tx() + dx[2]*delta
dy[3], dy[4] = self:get_cy() + dy[1]*delta, self:get_ty() + dy[2]*delta
dz[3], dz[4] = self:get_cz() + dz[1]*delta, self:get_tz() + dz[2]*delta
MoveLightning(self.lightning, utils[self].moveEx.vis, dx[3], dy[3], dz[3], dx[4], dy[4], dz[4])
if utils[self].moveEx.onUpdate then
utils[self].moveEx.onUpdate(self, dx[3], dy[3], dz[3], dx[4], dy[4], dz[4])
end
end
end
end
states.color = function(self)
if utils[self].color then
utils[self].color.time = utils[self].color.time + m_light.custom.INTERVAL
local delta = utils[self].color.time/utils[self].color.dur
if delta > 1 then
utils[self].color.a = math.floor(utils[self].color.a)
utils[self].color.r = math.floor(utils[self].color.r)
utils[self].color.g = math.floor(utils[self].color.g)
utils[self].color.b = math.floor(utils[self].color.b)
self:set_color(utils[self].color.r, utils[self].color.g, utils[self].color.b, utils[self].color.a)
if utils[self].color.onEvent then
pcall(utils[self].color.onEvent, self)
end
utils[self].color = nil
utils[self].stack = utils[self].stack - 1
else
dx[1], dx[2] = utils[self].color.r - self:get_red(), utils[self].color.g - self:get_green()
dy[1], dy[2] = utils[self].color.b - self:get_blue(), utils[self].color.a - self:get_alpha()
dx[3], dx[4] = (self:get_red() + dx[1]*delta)/255, (self:get_green() + dx[2]*delta)/255
dy[3], dy[4] = (self:get_blue() + dy[1]*delta)/255, (self:get_alpha() + dy[2]*delta)/255
SetLightningColor(self.lightning, dx[3], dx[4], dy[3], dy[4])
if utils[self].color.onUpdate then
utils[self].color.onUpdate(self, dx[3]*255, dx[4]*255, dy[3]*255, dy[4]*255)
end
end
end
end
local function OnUpdateInterval()
for self in utils.list:iterator() do
for i = 1,#states do
states[states[i]](self)
end
if utils[self].stack <= 0 then
utils.list:erase(utils[self].pointer)
utils[self] = nil
end
end
if utils.list.size == 0 then
PauseTimer(m_light.custom.TIMER)
end
end
local function verify_utils(self)
if not utils[self] then
utils[self] = {pointer=select(3, utils.list:insert(self)), stack = 0}
if utils.list.size == 1 then
TimerStart(m_light.custom.TIMER, m_light.custom.INTERVAL, true, OnUpdateInterval)
end
end
end
function m_light:move_to(dur, checkVis, ...)
-- Should only accept 4 parameters.
dur = math.max(dur or 1, 0)
local j = select('#', ...)
if j ~= 4 then return end
if dur == 0 then
self:move(checkVis, ...)
return
end
verify_utils(self)
if not utils[self].move then
utils[self].stack = utils[self].stack + 1
utils[self].move = {time = 0.}
utils[self].move.dur = dur
utils[self].move.vis = checkVis
end
utils[self].move.cx, utils[self].move.cy, utils[self].move.tx, utils[self].move.ty = ...
end
function m_light:move_to_ex(dur, checkVis, ...)
-- Should only accept 4 parameters.
dur = math.max(dur or 1, 0)
local j = select('#', ...)
if j ~= 6 then return end
if dur == 0 then
self:moveEx(checkVis, ...)
return
end
verify_utils(self)
if not utils[self].moveEx then
utils[self].stack = utils[self].stack + 1
utils[self].moveEx = {time = 0.}
utils[self].moveEx.dur = dur
utils[self].moveEx.vis = checkVis
end
utils[self].moveEx.cx, utils[self].moveEx.cy, utils[self].moveEx.cz, utils[self].moveEx.tx, utils[self].moveEx.ty, utils[self].moveEx.tz = ...
end
function m_light:apply_color(dur, ...)
dur = math.max(dur or 1, 0)
local j = select('#', ...)
if j > 4 or j <= 0 then return end
if dur == 0 then
self:set_color(...)
return
end
verify_utils(self)
if not utils[self].color then
utils[self].stack = utils[self].stack + 1
utils[self].color = {time = 0.}
utils[self].color.dur = dur
end
utils[self].color.a, utils[self].color.b, utils[self].color.g, utils[self].color.r = ...
if not utils[self].color.b then utils[self].color.b = self:get_blue() end
if not utils[self].color.g then utils[self].color.g = self:get_green() end
if not utils[self].color.r then utils[self].color.r = self:get_red() end
end
function m_light:add_callback(cmdtype, eventtype, func)
cmdtype = cmdtype or "move"
if not ((not is_function(func) and (func == nil)) or (is_function(func))) then return end
if utils[self][cmdtype] then
utils[self][cmdtype][eventtype] = func
end
end
function m_light:remove_callback(cmdtype, eventtype)
self:add_callback(cmdtype, eventtype, nil)
end
function m_light:on_event(func)
self:add_callback("move", "onEvent", func)
end
function m_light:on_event_ex(func)
self:add_callback("moveEx", "onEvent", func)
end
function m_light:on_update(func)
self:add_callback("move", "onUpdate", func)
end
function m_light:on_update_ex(func)
self:add_callback("moveEx", "onUpdate", func)
end
function m_light:destroy()
-- Work on this later
if utils[self] then
for i = 1,#states do
local k = states[states[i]]
if utils[self][k] then
if utils[self][k].onEvent then
utils[self][k].onEvent(self)
end
utils[self][k] = nil
end
end
utils[self] = nil
end
_destroy(self)
end
function m_light.__gc(t)
if self.lightning then
self:destroy()
end
end
end
EffectUtils = setmetatable({}, protected_t())
do
local m_effect = getmetatable(EffectUtils)
local properties = {detachable={}, destroyed={}, visible={}, alpha={}}
local attachment = {list = LinkedList:create()}
local height = {}
local scale = {}
local orient = {yaw={}, pitch={}, roll={}}
local height_broken = VersionCheck.patch == "1.32"
m_effect.INTERVAL = 1/64.
m_effect.__metatable = EffectUtils
Initializer.registerBJ("SYSTEM", function()
attachment.timer = CreateTimer()
if height_broken then
attachment._DUMMY = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), FourCC("uloc"), 0, 0, 0)
ShowUnit(attachment._DUMMY, false)
PauseUnit(attachment._DUMMY, true)
end
end)
-- Pointers to old functions
local _func = {
destroy = DestroyEffect,
addEffect = AddSpecialEffect,
addEffectTarg = AddSpecialEffectTarget,
setEffectAlpha = BlzSetSpecialEffectAlpha,
setEffectYaw = BlzSetSpecialEffectYaw,
setEffectPitch = BlzSetSpecialEffectPitch,
setEffectRoll = BlzSetSpecialEffectRoll,
setEffectOrient = BlzSetSpecialEffectOrientation,
setEffectScale = BlzSetSpecialEffectScale,
setEffectHeight = BlzSetSpecialEffectHeight,
}
-- Overridden functions.
function AddSpecialEffect(modelPath, x, y)
local fx = _func.addEffect(modelPath, x, y)
properties.detachable[fx] = true
properties.visible[fx] = 1
properties.alpha[fx] = 255
height[fx] = 0.
scale[fx] = 1.
orient.yaw[fx] = 0.
orient.pitch[fx] = 0.
orient.roll[fx] = 0.
return fx
end
function AddSpecialEffectTarget(modelName, targetWidget, attachPointName)
local fx = _func.addEffectTarg(modelName, targetWidget, attachPointName)
properties.detachable[fx] = false
properties.visible[fx] = 1
properties.alpha[fx] = 255
height[fx] = 0.
scale[fx] = 1.
orient.yaw[fx] = 0.
orient.pitch[fx] = 0.
orient.roll[fx] = 0.
return fx
end
function BlzSetSpecialEffectYaw(fx, value)
orient.yaw[fx] = ModuloReal(value or 0, 2*math.pi)
_func.setEffectYaw(fx, orient.yaw[fx])
end
function BlzSetSpecialEffectPitch(fx, value)
orient.pitch[fx] = ModuloReal(value or 0, 2*math.pi)
_func.setEffectPitch(fx, orient.pitch[fx])
end
function BlzSetSpecialEffectRoll(fx, value)
orient.roll[fx] = ModuloReal(value or 0, 2*math.pi)
_func.setEffectRoll(fx, orient.roll[fx])
end
function BlzSetSpecialEffectOrientation(fx, y, p, r)
orient.yaw[fx] = ModuloReal(y or 0, 2*math.pi)
orient.pitch[fx] = ModuloReal(p or 0, 2*math.pi)
orient.roll[fx] = ModuloReal(r or 0, 2*math.pi)
_func.setEffectOrient(fx, orient.yaw[fx], orient.pitch[fx], orient.roll[fx])
end
function BlzSetSpecialEffectScale(fx, scale)
scale = scale or 1
_func.setEffectScale(fx, scale)
end
function BlzSetSpecialEffectHeight(fx, h)
height[fx] = ((type(h) ~= 'number') and 0) or h
if height_broken then
SetUnitX(attachment._DUMMY, BlzGetLocalSpecialEffectX(fx))
SetUnitY(attachment._DUMMY, BlzGetLocalSpecialEffectY(fx))
h = h + BlzGetUnitZ(attachment._DUMMY)
end
_func.setEffectHeight(fx, h)
end
function BlzSetSpecialEffectAlpha(fx, value)
properties.alpha[fx] = value
_func.setEffectAlpha(fx, ((properties.visible[fx] > 0) and value) or 0)
end
-- Setters
function SetEffectX(fx, x)
BlzSetSpecialEffectX(fx, x)
end
function SetEffectY(fx, y)
BlzSetSpecialEffectY(fx, y)
end
function SetEffectZ(fx, z)
BlzSetSpecialEffectZ(fx, z)
end
function SetEffectHeight(fx, h)
BlzSetSpecialEffectHeight(fx, h)
end
function SetEffectScale(fx, scale)
BlzSetSpecialEffectScale(fx, scale)
end
function SetEffectYaw(fx, yaw)
BlzSetSpecialEffectYaw(fx, yaw)
end
function SetEffectPitch(fx, pitch)
BlzSetSpecialEffectPitch(fx, pitch)
end
function SetEffectRoll(fx, roll)
BlzSetSpecialEffectRoll(fx, roll)
end
function GetEffectX(fx)
return BlzGetLocalSpecialEffectX(fx)
end
function GetEffectY(fx)
return BlzGetLocalSpecialEffectY(fx)
end
function GetEffectZ(fx, z)
return BlzGetLocalSpecialEffectZ(fx)
end
function GetEffectHeight(fx)
return height[fx]
end
function GetEffectScale(fx)
return scale[fx]
end
function GetEffectYaw(fx)
return orient.yaw[fx]
end
function GetEffectPitch(fx)
return orient.pitch[fx]
end
function GetEffectRoll(fx)
return orient.roll[fx]
end
-- Utility functions
function ShowEffect(fx, flag)
local value = (flag and 1) or -1
properties.visible[fx] = properties.visible[fx] + value
if properties.visible[fx] > 0 then
_func.setEffectAlpha(fx, properties.alpha[fx])
else
_func.setEffectAlpha(fx, 0)
end
end
function IsEffectVisible(fx)
return properties.visible[fx] > 0
end
do
local tb_unit = {}
local tb_pointer = {}
function DetachEffect(fx)
if not properties.detachable[fx] or not properties[fx] then return end
attachment.list:erase(properties[fx].pointer)
tb_unit[properties[fx].target_unit]:erase(tb_pointer[fx])
properties[fx].pointer = nil
properties[fx].target_unit = nil
properties[fx].callback = nil
properties[fx] = nil
tb_pointer[fx] = nil
end
local function nillify(fx)
properties.detachable[fx] = nil
properties.visible[fx] = nil
properties.alpha[fx] = nil
height[fx] = nil
scale[fx] = nil
orient.yaw[fx] = nil
orient.pitch[fx] = nil
orient.roll[fx] = nil
end
function DestroyEffect(fx, dur)
if properties.destroyed[fx] then return end
if not dur then
if properties[fx] then
DetachEffect(fx)
end
nillify(fx)
_func.destroy(fx)
return
end
properties.destroyed[fx] = true
doAfter(dur, function()
if properties[fx] then
DetachEffect(fx)
end
nillify(fx)
properties.destroyed[fx] = nil
_func.destroy(fx)
end)
end
local function OnEffectUpdate()
for effect in attachment.list:iterator() do
local unit = properties[effect].target_unit
local cx, cy = GetUnitX(unit), GetUnitY(unit)
BlzSetSpecialEffectPosition(effect, cx, cy, 0)
BlzSetSpecialEffectHeight(effect, height[effect])
if properties[effect].callback then
-- If the effect has a callback function,
-- assume that the object will not be destroyed.
properties[effect].callback(effect, unit)
end
end
if attachment.list.size == 0 then
PauseTimer(attachment.list)
end
end
function AttachEffectToUnit(fx, whichunit, h)
if not properties.detachable[fx] then return end
if not whichunit then return end
height[fx] = ((type(h) ~= 'number') and 0) or h
if not properties[fx] then
-- Assume target_unit has no associated table.
properties[fx] = {target_unit = whichunit, pointer=select(3, attachment.list:insert(fx))}
if attachment.list.size == 1 then
TimerStart(attachment.timer, m_effect.INTERVAL, true, OnEffectUpdate)
end
else
tb_unit[properties[fx].target_unit]:erase(tb_pointer[fx])
properties[fx].target_unit = whichunit
end
if not tb_unit[whichunit] then
tb_unit[whichunit] = LinkedList:create()
end
tb_pointer[fx] = select(3, tb_unit[whichunit]:insert(fx))
end
function AttachEffectSetCallback(fx, callback)
if not properties.detachable[fx] or not properties[fx] then return end
properties[fx].callback = callback
end
UnitDex.register("LEAVE_EVENT", function()
if not tb_unit[UnitDex.eventUnit] then return end
for fx in tb_unit[UnitDex.eventUnit] do
-- DestroyEffect should handle element removal internally.
DestroyEffect(fx)
end
tb_unit[UnitDex.eventUnit]:destroy()
tb_unit[UnitDex.eventUnit] = nil
end)
end
function do_highlight(tx, ty)
DestroyEffect( AddSpecialEffect("Abilities\\Spells\\Other\\Charm\\CharmTarget.mdl", tx, ty), 0.5)
end
end
Missile = setmetatable({}, protected_t())
do
local miss = Missile
local m_miss = getmetatable(miss)
local speed = {turn_rate={}}
local facing = {}
m_miss.INTERVAL = EffectUtils.INTERVAL
m_miss.list = LinkedList:create()
Initializer.registerBJ("SYSTEM", function() m_miss.TIMER = CreateTimer() end)
function m_miss:create(modelPath, x, y)
local o = o or setmetatable({effect = AddSpecialEffect(modelPath, x or 0, y or 0)}, m_miss)
speed[o] = 0.
facing[o] = 0.
speed.turn_rate[o] = -1.
return o
end
function m_miss:new(modelPath, x, y)
return self:create(modelPath, x, y)
end
function m_miss:__call(modelPath, x, y)
return self:create(modelPath, x, y)
end
-- Setters
function m_miss:set_facing(angle, cmd)
cmd = cmd or ""
angle = angle or 0
if cmd == "raw" then
facing[self] = angle
else
facing[self] = angle*math.pi/180
end
end
function m_miss:set_speed(value, cmd)
cmd = cmd or ""
value = value or 0
if cmd == "raw" then
speed[self] = value
else
speed[self] = value*m_miss.INTERVAL
end
end
function m_miss:set_yaw(value)
SetEffectYaw(self.effect, value)
end
function m_miss:set_pitch(value)
SetEffectPitch(self.effect, value)
end
function m_miss:set_roll(value)
SetEffectRoll(self.effect, value)
end
function m_miss:set_x(x)
SetEffectX(self.effect, x)
end
function m_miss:set_y(y)
SetEffectY(self.effect, y)
end
function m_miss:set_z(z)
SetEffectZ(self.effect, z)
end
function m_miss:set_scale(scale)
SetEffectScale(self.effect, scale)
end
function m_miss:set_pos(x, y, z)
self:set_x(x); self:set_y(y); self:set_z(z)
end
function m_miss:set_height(h)
SetEffectHeight(self.effect, h)
end
function m_miss:set_orient(yaw, pitch, roll)
BlzSetSpecialEffectOrientation(self.effect, yaw, pitch, roll)
end
function m_miss:set_turn_rate(rate, cmd)
rate, cmd = rate or -1, cmd or ""
if cmd == "raw" then
speed.turn_rate[self] = rate
else
speed.turn_rate[self] = rate*math.pi/180.*m_miss.INTERVAL
end
end
-- Getters
function m_miss:get_facing(cmd)
cmd = cmd or ""
if cmd == "raw" then
return facing[self]
else
return facing[self]/math.pi*180
end
end
function m_miss:get_speed(cmd)
cmd = cmd or ""
if cmd == "raw" then
return speed[self]
end
return speed[self]/m_miss.INTERVAL
end
function m_miss:get_yaw()
return GetEffectYaw(self.effect)
end
function m_miss:get_pitch()
return GetEffectPitch(self.effect)
end
function m_miss:get_roll()
return GetEffectRoll(self.effect)
end
function m_miss:get_x()
return GetEffectX(self.effect)
end
function m_miss:get_y()
return GetEffectY(self.effect)
end
function m_miss:get_z()
return GetEffectZ(self.effect)
end
function m_miss:get_pos()
return self:get_x(), self:get_y(), self:get_z()
end
function m_miss:get_height()
return GetEffectHeight(self.effect)
end
function m_miss:get_scale()
return GetEffectScale(self.effect)
end
function m_miss:get_turn_rate(cmd)
cmd = cmd or ""
if cmd == "raw" then
return speed.turn_rate[self]
else
return speed.turn_rate[self]/(math.pi/180.*m_miss.INTERVAL)
end
end
function m_miss:get_orient()
return self:get_yaw(), self:get_pitch(), self:get_roll()
end
do
local inCallback = false
local _pointer = {}
m_miss.current = 0
local function sign(x)
return ((x > 0) and 1) or ((x < 0) and -1) or 0
end
local function is_valid_function(func)
-- One can nullify relevant parameters here.
return is_function(func) or (type(func) == 'nil')
end
function m_miss:is_active()
return _pointer[self]
end
local function OnMissileMovement()
inCallback = true
local lastCur = m_miss.current
for missile, pointer in m_miss.list:iterator() do
-- facing referes to the angle the missile is actually facing
m_miss.current = missile
local ty, tx, face = missile.onUpdate()
local cx, cy = missile:get_x(), missile:get_y()
local dist = (cx-tx)*(cx-tx) + (cy-ty)*(cy-ty)
local theta, phi
-- Now, theta is sure to be always positive.
theta = ModuloReal(math.atan(ty-cy, tx-cx), 2*math.pi)
local t_rate = speed.turn_rate[missile]
if face then
BlzSetSpecialEffectOrientation(missile.effect, face, missile:get_pitch(), missile:get_roll())
end
local delta = math.abs(theta-facing[missile])
if (t_rate <= 0) or ((t_rate > 0) and ((speed[missile]*speed[missile] > dist) or (t_rate >= math.abs(theta-facing[missile])))) then
-- If turn-rate is less than or equal to 0, turning is instantaneous.
-- else, if distance is very short, force instantaneous turning.
-- else, if the difference between the two angles is very small
-- instantly turn the missile.
phi = theta
else
local yaw = facing[missile]
local floored = math.floor(math.abs((yaw-theta)/math.pi))
phi = yaw + t_rate*sign(yaw-theta)*sign(2*floored - 1)
-- Equivalent expression would be the following
--[[
if yaw > theta then
-- It would be better to base the interpolation on the other end
if math.floor((yaw-theta)/math.pi) > 0 then
phi = yaw + speed.turn_rate[missile]
else
phi = yaw - speed.turn_rate[missile]
end
else
if math.floor((theta-yaw)/math.pi) > 0 then
phi = yaw - speed.turn_rate[missile]
else
phi = yaw + speed.turn_rate[missile]
end
end
]]
end
facing[missile] = math.fmod(phi, 2*math.pi)
if _pointer[missile] and (speed[missile] ~= 0) then
cx, cy = cx + speed[missile]*math.cos(phi), cy + speed[missile]*math.sin(phi)
SetEffectX(missile.effect, cx); SetEffectY(missile.effect, cy)
SetEffectHeight(missile.effect, GetEffectHeight(missile.effect))
end
end
m_miss.current = lastCur
if m_miss.list.size == 0 then
PauseTimer(m_miss.TIMER)
end
inCallback = false
end
function m_miss:on_land(func)
if not is_valid_function(func) then return end
rawset(self, 'onHit', func)
end
function m_miss:on_update(func)
if not is_valid_function(func) then return end
rawset(self, 'onUpdate', func)
end
function m_miss:launch(func)
if not self:is_active() then
_pointer[self] = select(3, m_miss.list:insert(self))
if m_miss.list.size == 1 and not inCallback then
TimerStart(m_miss.TIMER, m_miss.INTERVAL, true, OnMissileMovement)
end
end
if not rawget(self, 'onUpdate') then self:on_update(func) end
end
function m_miss:land()
if self:is_active() then
m_miss.list:erase(_pointer[self])
_pointer[self] = nil
if rawget(self, 'onHit') then
local lastCur = m_miss.current
m_miss.current = self
self.onHit()
m_miss.current = lastCur
end
end
end
end
function m_miss:destroy(delay)
-- This will fire an onHit event if it hasn't yet already.
-- Otherwise, this does nothing.
self:land()
DestroyEffect(self.effect, delay)
self.onUpdate = nil
self.onHit = nil
self.effect = nil
speed.turn_rate[self] = nil
speed[self] = nil
facing[self] = nil
setmetatable(self, nil)
end
end
do
local miss = Missile
local missileP = {}
local m_miss = getmetatable(miss)
local m_mPoint = protected_t()
m_miss.Point = setmetatable({}, m_mPoint)
m_mPoint.missileP = missileP
m_mPoint.current = 0
function m_mPoint:reassign(projectile)
local o = setmetatable({missile = projectile, tx = 0, ty = 0}, m_mPoint)
missileP[o.missile] = o
return o
end
function m_mPoint:create(modelPath, x, y)
return self:reassign(miss:create(modelPath, x, y))
end
function m_mPoint:new(modelPath, x, y)
return self:create(modelPath, x, y)
end
function m_mPoint:__call(modelPath, x, y)
return self:create(modelPath, x, y)
end
function m_mPoint:set_target_pos(tx, ty)
self.tx, self.ty = tx or 0, ty or 0
end
function m_mPoint:land()
local lastCur = m_mPoint.current
m_mPoint.current = self
self.missile:land()
m_mPoint.current = lastCur
end
local function OnMovementUpdate()
local self, lastCur = missileP[m_miss.current], m_mPoint.current
local cx, cy = self.missile:get_x(), self.missile:get_y()
local tx, ty = self.tx, self.ty
local dist, spd = (cx-tx)*(cx-tx) + (cy-ty)*(cy-ty), self.missile:get_speed('raw')
m_mPoint.current = self
if dist > spd*spd then
local theta = math.atan(ty - cy, tx - cx)
local yaw
if rawget(self, 'onUpdate') then
-- Assume that the callback function will have the courtesy of
-- returning the yaw value, given theta.
yaw = self.onUpdate(theta)
end
m_mPoint.current = lastCur
return ty, tx, yaw or theta
end
local theta = math.atan(ty - cy, tx - cx)
self.missile:set_pos(tx, ty, self.missile:get_height())
self:land()
m_mPoint.current = lastCur
return 0, 0, theta
end
function m_mPoint:launch(theta)
theta = theta or (math.atan(self.ty - self.missile:get_y(), self.tx - self.missile:get_x()))
-- Ensures that the missile will immediately point to the location
-- if theta isn't defined.
self.missile:set_yaw(theta)
self.missile:launch(OnMovementUpdate)
end
function m_mPoint:on_land(func)
self.missile:on_land(func)
end
function m_mPoint:on_update(func)
if not is_function(func) then return end
rawset(self, 'onUpdate', func)
end
function m_mPoint:remove()
local missile = self.missile
missileP[self.missile] = nil
self.missile, self.tx, self.ty = nil
if missile:is_active() then
missile:land()
end
missile:on_land(nil); missile:on_update(nil)
setmetatable(self, nil)
return missile
end
function m_mPoint:destroy()
self:remove():destroy()
end
end
do
local miss = Missile
local missileP = {rev_s={}, theta={}, unit={}, removed={}, radius={}}
local m_miss = getmetatable(miss)
local m_mOrbit = protected_t()
m_miss.Orbit = setmetatable({}, m_mOrbit)
m_mOrbit.missileP = missileP
m_mOrbit.current = 0
function m_mOrbit:reassign(projectile)
local o = {}
o.missile = projectile
missileP.radius[o.missile] = 0.
missileP.rev_s[o.missile] = 0.
missileP.theta[o.missile] = 0.
missileP.unit[o.missile] = 0.
missileP[o.missile] = o
setmetatable(o, m_mOrbit)
return o
end
function m_mOrbit:create(modelPath, x, y)
return self:reassign(miss:create(modelPath, x, y))
end
function m_mOrbit:new(modelPath, x, y)
return self:create(modelPath, x, y)
end
function m_mOrbit:__call(modelPath, x, y)
return self:create(modelPath, x, y)
end
do
local _GRADIENT = 2*math.pi*m_miss.INTERVAL
function m_mOrbit:set_revs(revs, cmd)
cmd = cmd or ""
revs = revs or 0.
if cmd == "raw" then
missileP.rev_s[self.missile] = revs
self.missile:set_turn_rate(-1)
else
missileP.rev_s[self.missile] = revs*_GRADIENT
self.missile:set_turn_rate(-1)
end
end
function m_mOrbit:get_revs(cmd)
cmd = cmd or ""
if cmd == "raw" then
return missileP.rev_s[self.missile]
else
return missileP.rev_s[self.missile]/_GRADIENT
end
end
end
function m_mOrbit:set_target(whichunit)
missileP.unit[self.missile] = whichunit
end
function m_mOrbit:get_target()
return missileP.unit[self.missile]
end
function m_mOrbit:set_radius(rad)
rad = rad or 0
missileP.radius[self.missile] = rad
end
function m_mOrbit:get_radius()
return missileP.radius[self.missile]
end
local function OnMovementUpdate()
local self, lastCur = missileP[m_miss.current], m_mOrbit.current
local tx, ty = GetUnitX(missileP.unit[self.missile]), GetUnitY(missileP.unit[self.missile])
local theta = math.fmod(missileP.theta[self.missile] + missileP.rev_s[self.missile], 2*math.pi)
local yaw
m_mOrbit.current = self
missileP.theta[self.missile] = theta
tx = tx + missileP.radius[self.missile]*math.cos(theta)
ty = ty + missileP.radius[self.missile]*math.sin(theta)
if rawget(self, "onUpdate") then
yaw = self.onUpdate(theta)
end
if self.missile and self.missile:is_active() then
-- This will override all set_speed calls, as well as set_turn_rate calls.
self.missile:set_speed(missileP.radius[self.missile]*missileP.rev_s[self.missile], 'raw')
self.missile:set_turn_rate(-1)
end
m_mOrbit.current = lastCur
return ty, tx, yaw or theta
end
function m_mOrbit:launch(theta)
if not missileP.unit[self.missile] then return end
if missileP.removed[self.missile] then return end
missileP.theta[self.missile] = theta or missileP.theta[self.missile]
self.missile:launch(OnMovementUpdate)
end
function m_mOrbit:on_land(func)
self.missile:on_land(func)
end
function m_mOrbit:on_update(func)
if missileP.removed[self.missile] then return end
if not is_function(func) then return end
rawset(self, 'onUpdate', func)
end
function m_mOrbit:set_theta(theta)
if missileP.removed[self.missile] then return end
missileP.theta[self.missile] = theta or missileP.theta[self.missile]
end
function m_mOrbit:get_theta(theta)
return missileP.theta[self.missile]
end
function m_mOrbit:theta_from_unit(cmd)
if not missileP.unit[self.missile] then return missileP.theta[self.missile] end
cmd = cmd or ""
local theta = math.atan(self.missile:get_y() - GetUnitY(missileP.unit[self.missile]),
self.missile:get_x() - GetUnitX(missileP.unit[self.missile]))
if cmd == "set" then
self:set_theta(theta)
end
return theta
end
function m_mOrbit:land()
local lastCur = m_mOrbit.current
m_mOrbit.current = self
self.missile:land()
m_mOrbit.current = lastCur
end
-- Remove technically destroys the object, whereas destroy
-- also destroys the missile as well.
function m_mOrbit:remove()
local missile = self.missile
missileP.removed[self.missile] = true
if missile:is_active() then
missile:land()
end
missile:on_land(nil); missile:on_update(nil)
missileP.removed[self.missile] = nil
missileP.rev_s[self.missile] = nil
missileP.theta[self.missile] = nil
missileP.unit[self.missile] = nil
missileP.radius[self.missile] = nil
missileP[self.missile] = nil
self.radius, self.missile = nil
setmetatable(self, nil)
return missile
end
function m_mOrbit:destroy()
missileP[self.missile] = nil
self.missile:destroy()
self.missile, self.tx, self.ty = nil
setmetatable(self, nil)
end
end
do
local miss = Missile
local m_miss = getmetatable(miss)
local types = {}
local type_instances = weaktable({}, 'kv')
do
-- Iterate through m_miss, and check for table types.
for k, v in pairs(m_miss) do
-- If the value is a table, assume it is explicitly defined
-- by external snippets.
-- Consider the case when the string is prefixed by __
if type(v) == 'table' and tostring(k):sub(1, 2) ~= '__' then
types[k] = getmetatable(v)
end
end
end
-- prevtype and nexttype are strings.
function m_miss.change_type(t, prevtype, nexttype)
-- If they are the same, do nothing.
if prevtype == nexttype then return t end
if not types[nexttype] then
-- Changing movement type of a missile from
-- an existing type to a non-existent type
-- should not be permitted (for safety).
return t
end
if types[prevtype] then
t = types[nexttype]:reassign(t:remove())
else
t = types[nexttype]:reassign(t)
end
return t
end
function m_miss.get_type(t)
if getmetatable(t) == m_miss then return "Missile" end
if not type_instances[t] then
for k, v in pairs(types) do
if getmetatable(t) == v then
type_instances[t] = k
break
end
end
end
return type_instances[t]
end
end
GameBarDisplay = setmetatable({}, protected_t())
do
local disp = GameBarDisplay
local m_disp = getmetatable(GameBarDisplay)
local bar_map = {}
local DEF_SCALE = 1.25
local t_progress = {}
local t_scale = {}
m_disp.barFile = "war3mapImported\\Progressbar.mdx"
function m_disp:create(scale, o)
scale = scale or DEF_SCALE
o = o or setmetatable({bar=AddSpecialEffect(m_disp.barFile, 0, 0)}, m_disp)
bar_map[o.bar] = o
t_progress[o] = 0
BlzSetSpecialEffectTimeScale(o.bar, 0)
SetEffectScale(o.bar, scale)
BlzSetSpecialEffectColorByPlayer(o.bar, Player(21))
return o
end
function m_disp:new(scale, o)
return self:create(scale, o)
end
function m_disp:__call(scale)
return self:create(scale)
end
function m_disp:show(flag)
ShowEffect(self.bar, flag)
end
function m_disp:visible()
return IsEffectVisible(self.bar)
end
function m_disp:set_alpha(value)
BlzSetSpecialEffectAlpha(self.bar, value)
end
function m_disp:show_for_player(flag, player)
if GetLocalPlayer() == player then
if flag then
self:set_alpha(255)
else
self:set_alpha(0)
end
end
end
function m_disp:destroy()
bar_map[self.bar] = nil
BlzSetSpecialEffectTimeScale(o.bar, 1)
DestroyEffect(self.bar)
t_progress[self] = nil
self.bar = nil
end
function m_disp:set_progress(value)
-- Work on this later.
value = math.max(0, math.min(value, 1))
t_progress[self] = value
BlzSetSpecialEffectTime(self.bar, value)
end
function m_disp:set_color(red, green, blue)
if type(blue) == 'nil' and type(green) == 'nil' then
red, green, blue = math.fmod(math.floor(red/65536), 256), math.fmod(math.floor(red/256), 256), math.fmod(red, 256);
end
BlzSetSpecialEffectColor(self.bar, red, green, blue)
end
function m_disp:set_pos(x, y, z)
x, y, z = x or 0, y or 0, z or 0;
SetEffectX(self.bar, x); SetEffectY(self.bar, y);
SetEffectHeight(self.bar, z)
end
function m_disp:get_pos(arg)
arg = arg or 'xyz'
values = {}
if arg:find('x') then
values[#values + 1] = BlzGetLocalSpecialEffectX(self.bar)
end
if arg:find('y') then
values[#values + 1] = BlzGetLocalSpecialEffectY(self.bar)
end
if arg:find('z') then
values[#values + 1] = GetEffectHeight(self.bar)
end
return table.unpack(values)
end
function m_disp:set_scale(scale)
SetEffectScale(self.bar, scale)
end
function m_disp:get_scale()
return GetEffectScale(self.bar)
end
function m_disp:get_progress()
return t_progress[self]
end
function m_disp.get_bar(fx)
return bar_map[fx]
end
end
do
local disp = GameBarDisplay
local m_disp = getmetatable(GameBarDisplay)
local _create = m_disp.create
local _destroy = m_disp.destroy
local LINEAR = BezierEasing.create(0, 0, 1, 1)
local watcher
local WATCH_BAR_OFFSET = 32
local attachlist = {}
m_disp.utils = {progress = {}, progressList = LinkedList:create()}
m_disp.__metatable = disp
Initializer.registerBJ("SYSTEM", function() watcher = {timer=CreateTimer(), interval=EffectUtils.INTERVAL}; end)
function m_disp:create(x, y, z, scale)
local o = _create(self, scale)
o:set_pos(x, y, z)
return o
end
function m_disp:new(x, y, z, scale)
return self:create(x, y, z, scale)
end
function m_disp:__call(x, y, z, scale)
return self:create(x, y, z, scale)
end
function m_disp:on_update(callback)
if not is_function(callback) then return end
rawset(self, 'onUpdate', callback)
end
function m_disp:detach()
if rawget(self, 'pointer') then
attachlist[self.unit]:erase(self.pointer)
self.pointer = nil
self.unit = nil
end
DetachEffect(self.bar)
end
function m_disp:progress_to(progLevel, dur, refresh, bezier)
bezier = bezier or LINEAR
dur = dur or 1
if dur <= 0 then
self:set_progress(progLevel)
if m_disp.utils.progress[self] then
m_disp.utils.progressList:erase(m_disp.utils.progress[self].pointer)
if m_disp.utils.progress[self].onProgressEnd then
m_disp.utils.progress[self].onProgressEnd(self, progLevel)
end
m_disp.utils.progress[self] = nil
end
return
end
if not m_disp.utils.progress[self] then
m_disp.utils.progress[self] = {startProg = self:get_progress(), endProg = progLevel}
m_disp.utils.progress[self].time = 0.
m_disp.utils.progress[self].pointer = select(3, m_disp.utils.progressList:insert(self))
if m_disp.utils.progressList.size == 1 then
TimerStart(watcher.timer, watcher.interval, true, function()
for self in m_disp.utils.progressList:iterator() do
m_disp.utils.progress[self].time = m_disp.utils.progress[self].time + watcher.interval
local delta = m_disp.utils.progress[self].time/m_disp.utils.progress[self].dur
if delta >= 1. then
self:set_progress(m_disp.utils.progress[self].endProg)
m_disp.utils.progressList:erase(m_disp.utils.progress[self].pointer)
if m_disp.utils.progress[self].onProgressEnd then
m_disp.utils.progress[self].onProgressEnd(self, self:get_progress())
end
m_disp.utils.progress[self] = nil
else
local diff = m_disp.utils.progress[self].endProg - m_disp.utils.progress[self].startProg
self:set_progress(m_disp.utils.progress[self].startProg + diff*m_disp.utils.progress[self].bez[delta])
if m_disp.utils.progress[self].onProgressUpdate then
m_disp.utils.progress[self].onProgressUpdate(self, self:get_progress())
end
end
end
if m_disp.utils.progressList.size == 0 then
PauseTimer(watcher.timer)
end
end)
end
else
m_disp.utils.progress[self].endProg = progLevel
m_disp.utils.progress[self].time = (refresh and 0) or m_disp.utils.progress[self].time
end
m_disp.utils.progress[self].dur = dur
m_disp.utils.progress[self].bez = bezier
end
function m_disp:on_progress_callback(eventcmd, func)
if not is_function(func) or not m_disp.utils.progress[self] then return end
eventcmd = eventcmd or "onProgressEnd"
m_disp.utils.progress[self][eventcmd] = func
end
function m_disp:on_progress_update(func) self:on_progress_callback("onProgressUpdate", func) end
function m_disp:on_progress_end(func) self:on_progress_callback("onProgressEnd", func) end
function m_disp:destroy()
if rawget(self, 'onUpdate') then self.onUpdate = nil; end
if m_disp.utils.progress[self] then
m_disp.utils.progressList:erase(m_disp.utils.progress[self].pointer)
m_disp.utils.progress[self] = nil
end
self:detach()
_destroy(self)
end
function m_disp:attach_to_unit(unit, height)
if unit == nil or (rawget(self, 'unit') == unit) then return end
if not attachlist[unit] then
attachlist[unit] = LinkedList:create()
end
self:detach()
rawset(self, 'unit', unit)
rawset(self, 'pointer', select(3, attachlist[unit]:insert(self)))
AttachEffectToUnit(self.bar, unit, height)
AttachEffectSetCallback(self.bar, function(effect, unit)
pos = attachlist[unit]:get_pos(self.pointer) - 1
SetEffectHeight(self.bar, height + pos*WATCH_BAR_OFFSET)
if rawget(self, 'onUpdate') then
self.onUpdate(self, unit)
end
end)
end
UnitDex.register("LEAVE_EVENT", function()
if attachlist[UnitDex.eventUnit] then
for self in attachlist[UnitDex.eventUnit]:iterator() do
self:destroy()
end
attachlist[UnitDex.eventUnit]:destroy()
attachlist[UnitDex.eventUnit] = nil
end
end)
end
BonusInterface = setmetatable({}, protected_t())
do
local m_bonus = getmetatable(BonusInterface)
local sys_list = {}
m_bonus.ADD = {}
m_bonus.MULT = {}
m_bonus.TOTAL = {}
function m_bonus:new(setter, o)
o = o or readonly_t()
o.total = {}
o.defValues = {[m_bonus.ADD] = 0, [m_bonus.MULT] = 1}
o.native_set = setter or DoNothing
do
o.create = function(self, oo)
oo = oo or setmetatable({object=0, amount=0, type=0, id=0, pointer=0}, o)
return oo
end
o.new = function(self, oo)
return self:create(oo)
end
o.assignObj = function(self, obj, typer)
typer = typer or m_bonus.ADD
if type(obj) == 'nil' then return nil end
local oo = o:create()
oo.object = obj
oo.id = obj
oo.amount = o.defValues[typer]
oo.type = typer
if not o.total[obj] then
o.total[obj] = {
[m_bonus.ADD] = {total=0, list=LinkedList:create()},
[m_bonus.MULT] = {total=1, list=LinkedList:create(), zeros={count=0}},
base = 1,
total = 1}
end
local _, __; _, __, oo.pointer = o.total[obj][oo.type].list:insert(oo)
return oo
end
o.getTotal = function(self)
if o.total[self.id][m_bonus.MULT].zeros.count > 0 then
return o.total[self.id][m_bonus.ADD].total
end
return o.total[self.id].total
end
o.getProduct = function(self)
if o.total[self.id][m_bonus.MULT].zeros.count > 0 then
return 0
end
return o.total[self.id][m_bonus.MULT].total
end
o.getSum = function(self)
return o.total[self.id][m_bonus.ADD].total
end
o.applyModifier = function(obj, amount)
if o["onApplyModifier"] then amount = o["onApplyModifier"](amount) end
setter(obj, amount)
end
o.setAmount = function(self, amount)
if self == o then return end
amount = ((type(amount) == 'number') and amount) or 0
if (self.type == m_bonus.MULT) then
local zeros = o.total[self.id][self.type].zeros
if amount == 0 then
if not zeros[self.pointer] then
zeros[self.pointer] = true
zeros.count = zeros.count + 1
end
else
if zeros[self.pointer] then
zeros[self.pointer] = nil
zeros.count = zeros.count - 1
end
local total = o.total[self.id][self.type]
total.total = total.total/self.amount*amount
o.total[self.id].total = o.total[self.id].base*o.total[self.id][m_bonus.MULT].total + o.total[self.id][m_bonus.ADD].total
self.amount = amount
end
elseif (self.type == m_bonus.ADD) then
local total = o.total[self.id][self.type]
total.total = total.total - self.amount + amount
o.total[self.id].total = o.total[self.id].base*o.total[self.id][m_bonus.MULT].total + o.total[self.id][m_bonus.ADD].total
self.amount = amount
end
o.applyModifier(self.object, self:getTotal())
end
o.addProduct = function(obj, amount)
local oo = o:assignObj(obj, m_bonus.MULT)
amount = amount or 1
oo:setAmount(amount)
return oo
end
o.addSum = function(obj, amount)
local oo = o:assignObj(obj, m_bonus.ADD)
amount = amount or 0
oo:setAmount(amount)
return oo
end
o.reset = function(self)
self:setAmount(o.defValues[self.type])
end
o.destroy = function(self)
self:reset()
o.total[self.id][self.type].list:erase(self.pointer)
self.object = nil
self.id = nil
self.amount = nil
self.type = nil
self.pointer = nil
setmetatable(self, nil)
end
o.setBase = function(obj, amount)
if not o.total[obj] then
o.total[obj] = {
[m_bonus.ADD] = {total=0, list=LinkedList:create()},
[m_bonus.MULT] = {total=1, list=LinkedList:create(), zeros={count=0}},
base = 1,
total = 1}
end
o.total[obj].base = amount
end
UnitDex.register("LEAVE_EVENT", function()
local id = UnitDex.eventUnit
if o.total[id] then
for self, key in o.total[id][m_bonus.ADD].list:iterator() do
self:destroy()
end
for self, key in o.total[id][m_bonus.MULT].list:iterator() do
self:destroy()
end
o.total[id][m_bonus.ADD].list:destroy()
o.total[id][m_bonus.MULT].list:destroy()
o.total[id][m_bonus.ADD] = nil
o.total[id][m_bonus.MULT] = nil
o.total[id].base = nil
o.total[id].total = nil
o.total[id] = nil
end
end)
end
sys_list[o] = true
return o
end
function m_bonus:create(setter, o)
return self:new(setter, o)
end
function m_bonus.is_bonus_class(o)
return sys_list[o]
end
end
BonusBaseAtkSpeed = setmetatable({}, protected_t())
do
local bonus = BonusBaseAtkSpeed
local m_bonus = getmetatable(bonus)
local getcdwn = BlzGetUnitAttackCooldown
m_bonus[0] = BonusInterface:create(function(unit, amount) BlzSetUnitAttackCooldown(unit, amount, 0) end)
m_bonus[1] = BonusInterface:create(function(unit, amount) BlzSetUnitAttackCooldown(unit, amount, 1) end)
m_bonus.BASE_ATTACK_TIME = 2.00
m_bonus.BASE_ATTACK_SCALE = 100
m_bonus.id = 0
UnitDex.register("ENTER_EVENT", function()
local cooldown = {
[0] = m_bonus.BASE_ATTACK_TIME*m_bonus.BASE_ATTACK_SCALE/getcdwn(UnitDex.eventUnit, 0),
[1] = m_bonus.BASE_ATTACK_TIME*m_bonus.BASE_ATTACK_SCALE/getcdwn(UnitDex.eventUnit, 1)
}
m_bonus[0].setBase(UnitDex.eventUnit, cooldown[0])
m_bonus[1].setBase(UnitDex.eventUnit, cooldown[1])
end)
function m_bonus.onApplyModifier(amount)
if amount == 0 then return amount end
return m_bonus.BASE_ATTACK_TIME/(amount/m_bonus.BASE_ATTACK_SCALE)
end
m_bonus[0].onApplyModifier = m_bonus.onApplyModifier
m_bonus[1].onApplyModifier = m_bonus.onApplyModifier
function m_bonus:addAtkSpeed(whichunit, index, value)
local modifier = bonus[index].addSum(whichunit, value)
return modifier
end
function m_bonus:multAtkSpeed(whichunit, index, value)
local modifier = bonus[index].addProduct(whichunit, value)
return modifier
end
m_bonus[0].setAtkSpeed = function(self, value)
self:setAmount(value)
end
m_bonus[1].setAtkSpeed = function(self, value)
self:setAmount(value)
end
end
--[[
When using CustomMelee.add_race_setup, setupfunc expects 5 parameters:
(whichPlayer, startLoc, doHeroes, doCamera, doPreload)
]]
CustomMelee = setmetatable({}, protected_t())
do
local l_melee = getmetatable(CustomMelee)
local race_cond = {}
local player_fact = {}
local hero_list = {index={}, race={}}
local dialogs = {players={}, playerPos={}}
local town_hall_ids = {index={}}
local race_name = {
[RACE_HUMAN] = "Human",
[RACE_ORC] = "Orc",
[RACE_UNDEAD] = "Undead",
[RACE_NIGHTELF] = "Night Elf",
[RACE_DEMON] = "Demon",
[RACE_OTHER] = "Other",
}
l_melee.race_cond = race_cond
l_melee.race_name = race_name
l_melee.player_fact = player_fact
l_melee.hero_list = hero_list
l_melee.dialogs = dialogs
l_melee.town_hall_ids = town_hall_ids
local function printAfter(msg)
Initializer.register(function()
doAfter(0.00, function() print(msg) end)
end)
end
local function get_random_hero(whichplayer, heroLoc, race, index)
local v = VersionGet()
local roll
if (v == VERSION_REIGN_OF_CHAOS) then
roll = GetRandomInt(1,IMinBJ(3, #hero_list.race[race][index]))
else
roll = GetRandomInt(1,#hero_list.race[race][index])
end
-- Translate the roll into a unitid.
local pick = hero_list.race[race][index][roll]
local hero = CreateUnitAtLoc(whichplayer, pick, heroLoc, bj_UNIT_FACING)
if bj_meleeGrantHeroItems then
if (bj_meleeTwinkedHeroes[owner] < bj_MELEE_MAX_TWINKED_HEROES) then
UnitAddItemById(hero, FourCC('stwp'))
bj_meleeTwinkedHeroes[owner] = bj_meleeTwinkedHeroes[owner] + 1
end
end
return hero
end
function l_melee.add_heroID(heroId, race, index)
-- Default value for index
index = index or 1
if type(heroId) == "string" then heroId = FourCC(heroId) end
if not hero_list.race[race] then
hero_list.race[race] = {}
end
if not hero_list.race[race][index] then
hero_list.race[race][index] = {}
end
if not hero_list.index[heroId] then
hero_list[#hero_list + 1] = heroId
hero_list.index[heroId] = #hero_list
local i = #hero_list.race[race][index] + 1
hero_list.race[race][index][i] = heroId
end
end
function l_melee.add_race_faction(race, factionname)
if type(factionname) ~= 'string' then return 0 end
if not race_cond[race] then
race_cond[race] = {index={}, setupfunc={}, melee_ai={}}
end
if not race_cond[race].index[factionname] then
race_cond[race][#race_cond[race] + 1] = factionname
race_cond[race].index[factionname] = #race_cond[race]
end
return race_cond[race].index[factionname]
end
function l_melee.race_faction_setupID(race, index, setupfunc)
if not is_function(setupfunc) then return end
if not race_cond[race][index] then
return 0
end
race_cond[race].setupfunc[index] = setupfunc
return index
end
function l_melee.race_faction_setup(race, factionname, setupfunc)
return l_melee.race_faction_setupID(race, race_cond[race].index[factionname] or 0, setupfunc)
end
function l_melee.set_race_factionAI(race, factionname, melee_ai)
if type(melee_ai) ~= 'string' then return end
local index = race_cond[race].index[factionname] or 0
if index ~= 0 then
race_cond[race].melee_ai[index] = melee_ai
end
end
function l_melee.finalize_setup_func(race, factionname, func)
if not is_function(func) then return DoNothing end
if not race_cond[race] then
printAfter("CustomMelee.finalizeSetupFunction >> Race Conditions Failed.")
return DoNothing
end
local index = race_cond[race].index[factionname] or 0
if index == 0 then
printAfter("CustomMelee.finalizeSetupFunction >> Faction " .. factionname .. " does not exist for race: " .. race_name[race])
return DoNothing
end
local f = function(whichplayer, startloc, doheroes, docamera, dopreload)
local randomFlag = IsMapFlagSet(MAP_RANDOM_HERO)
local peonX, peonY, heroLoc = func(whichplayer, startloc, doheroes, docamera, dopreload)
if doheroes then
if randomFlag then
get_random_hero(whichplayer, heroLoc, race, index)
else
SetPlayerState(whichplayer, PLAYER_STATE_RESOURCE_HERO_TOKENS, bj_MELEE_STARTING_HERO_TOKENS)
end
end
if (docamera) then
-- Center the camera on the initial Peasants.
SetCameraPositionForPlayer(whichplayer, peonX, peonY)
SetCameraQuickPositionForPlayer(whichplayer, peonX, peonY)
end
RemoveLocation(heroLoc)
end
return f
end
function l_melee.add_townhallID(...)
local ids, j = {}, select('#', ...)
if j > 0 then
for i = 1,j do
ids[i] = select(i, ...)
ids[i] = ((type(ids[i]) == 'string') and FourCC(ids[i])) or ids[i]
end
end
for i = 1,j do
if not town_hall_ids.index[ids[i]] then
town_hall_ids[#town_hall_ids + 1] = ids[i]
town_hall_ids.index[ids[i]] = #town_hall_ids
end
end
end
function l_melee.remove_townhallID(...)
local ids, j = {}, select('#', ...)
-- Assume that the parameters passed are raw codes.
if j > 0 then
for i = 1,j do
ids[i] = select(i, ...)
ids[i] = ((type(ids[i]) == 'string') and FourCC(ids[i])) or ids[i]
end
end
-- Begin removal (Do not sort here).
local size, removed = #town_hall_ids, 0
for i = 1,j do
if town_hall_ids.index[ids[i]] then
local k = town_hall_ids.index[ids[i]]
town_hall_ids[k] = nil
town_hall_ids.index[ids[i]] = nil
removed = removed + 1
end
end
-- Sort the array (This should ideally be O(n), since the order of the registered ids is not
-- based on comparisons).
do
local j = 1
for i = 1,size do
if not town_hall_ids[i] then
while not town_hall_ids[i + j] do
if i + j > size then break end
j = j + 1
end
town_hall_ids[i] = town_hall_ids[i + j]
town_hall_ids.index[town_hall_ids[i]] = i
town_hall_ids[i + j] = nil
end
end
end
end
end
do
local funcs = {}
local l_melee = getmetatable(CustomMelee)
local race_cond = l_melee.race_cond
local player_fact = l_melee.player_fact
local hero_list = l_melee.hero_list
local dialogs = l_melee.dialogs
local race_name = l_melee.race_name
local town_hall_ids = l_melee.town_hall_ids
dialogs.max_players = 0
-- Special Dialog functions
function funcs.preInit()
dialogs.donePlayers = CreateForce()
dialogs.selectionDone = false
if l_melee.ui_initialization then
l_melee.ui_initialization()
end
end
function funcs.onPlayerRemove(p, i)
while i < #dialogs.players do
local p2 = dialogs.players[i + 1][1]
dialogs.players[i] = dialogs.players[i + 1]
dialogs.playerPos[p2] = i
i = i + 1
end
dialogs.playerPos[p] = nil
dialogs.players[i] = nil
if #dialogs.players <= 0 then
if dialogs.max_players > 1 then
PauseGame(false)
end
if l_melee.display_elements then
l_melee.display_elements(false)
end
DestroyForce(dialogs.donePlayers)
dialogs.donePlayers = nil
if l_melee.remove_elements then
l_melee.remove_elements()
end
else
ForceAddPlayer(dialogs.donePlayers, p)
if GetLocalPlayer() == p then
if l_melee.display_elements then
l_melee.display_elements(true)
end
end
end
end
function funcs.queuePlayerForDialog(p, race, indexStartLoc, doheroes, docamera, dopreload)
dialogs.players[#dialogs.players + 1] = {p, race, indexStartLoc, __mode="kv"}
dialogs.playerPos[p] = #dialogs.players
if not dialogs[race] then
dialogs[race] = {choice={}, listener=CreateTrigger(), choiceMap={}}
dialogs[race].main = DialogCreate()
doAfter(0.00, function()
TriggerRegisterDialogEvent(dialogs[race].listener, dialogs[race].main)
TriggerAddCondition(dialogs[race].listener, Condition(function()
local p = GetTriggerPlayer()
local button = GetClickedButton()
local i = dialogs.playerPos[p]
local race = dialogs.players[i][2]
local indexStartLoc = dialogs.players[i][3]
local faction = dialogs[race].choiceMap[button]
DialogDisplay(p, dialogs[race].main, false)
player_fact[GetPlayerId(p)] = faction
race_cond[race].setupfunc[faction](p, indexStartLoc, true, true, true)
RemoveLocation(indexStartLoc)
funcs.onPlayerRemove(p, i)
end))
DialogClear(dialogs[race].main)
DialogSetMessage(dialogs[race].main, "Select " .. race_name[race] .. " Faction")
for index=1,#race_cond[race] do
local button = DialogAddButton(dialogs[race].main, race_cond[race][index], 0)
dialogs[race].choice[index] = button
dialogs[race].choiceMap[button] = index
end
end)
end
doAfter(0.00, function()
DialogDisplay(p, dialogs[race].main, true)
end)
end
function funcs.queueRemovePlayer(p)
if not dialogs.playerPos[p] then
return
end
local i = dialogs.playerPos[p]
funcs.onPlayerRemove(p, i)
end
-- Melee-only mimic functions.
function funcs.starting_vis()
SetFloatGameState(GAME_STATE_TIME_OF_DAY, bj_MELEE_STARTING_TOD)
end
function funcs.starting_hero_limit()
local p
for player_id = 0,bj_MAX_PLAYERS-1 do
p = Player(player_id)
-- SetPlayerMaxHeroesAllowed(bj_MELEE_HERO_LIMIT, p)
SetPlayerTechMaxAllowed(p, FourCC('HERO'), bj_MELEE_HERO_LIMIT)
-- ReducePlayerTechMaxAllowed(p, <heroType>, bj_MELEE_HERO_TYPE_LIMIT)
for index = 1,#hero_list do
SetPlayerTechMaxAllowed(p, hero_list[index], bj_MELEE_HERO_TYPE_LIMIT)
end
end
end
function funcs.grant_hero_items()
local t = CreateTrigger()
for player_id = 0, bj_MAX_PLAYER_SLOTS-1 do
bj_meleeTwinkedHeroes[player_id] = 0
if player_id < bj_MAX_PLAYERS then
TriggerRegisterPlayerUnitEvent(t, Player(player_id), EVENT_PLAYER_UNIT_TRAIN_FINISH, nil)
end
end
TriggerAddCondition(t, Filter(function()
local u = GetTriggerUnit()
if IsUnitType(u, UNIT_TYPE_HERO) then
local owner = GetPlayerId(GetOwningPlayer(u))
-- If we haven't twinked N heroes for this player yet, twink away.
if (bj_meleeTwinkedHeroes[owner] < bj_MELEE_MAX_TWINKED_HEROES) then
UnitAddItemById(u, FourCC('stwp'))
bj_meleeTwinkedHeroes[owner] = bj_meleeTwinkedHeroes[owner] + 1
end
end
end))
end
function funcs.starting_resources()
local startingGold
local startingLumber
local v = VersionGet()
if (v == VERSION_REIGN_OF_CHAOS) then
startingGold, startingLumber = bj_MELEE_STARTING_GOLD_V0, bj_MELEE_STARTING_LUMBER_V0
else
startingGold, startingLumber = bj_MELEE_STARTING_GOLD_V1, bj_MELEE_STARTING_LUMBER_V1
end
local p
for player_id = 0,bj_MAX_PLAYERS-1 do
p = Player(player_id)
if (GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING) then
SetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD, startingGold)
SetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER, startingLumber)
end
end
end
function funcs.clear_units()
local p
local loc
local locX
local locY
local g = CreateGroup()
for player_id = 0,bj_MAX_PLAYERS-1 do
p = Player(player_id)
if (GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING) then
loc = GetPlayerStartLocation(p)
locX, locY = GetStartLocationX(loc), GetStartLocationY(loc)
GroupEnumUnitsInRange(g, locX, locY, bj_MELEE_CLEAR_UNITS_RADIUS, nil)
local enum_unit = FirstOfGroup(g)
while enum_unit do
GroupRemoveUnit(g, enum_unit)
if GetOwningPlayer(enum_unit) == Player(PLAYER_NEUTRAL_AGGRESSIVE) then
RemoveUnit(enum_unit)
elseif (GetOwningPlayer(enum_unit) == Player(PLAYER_NEUTRAL_AGGRESSIVE)) and
not IsUnitType(enum_unit, UNIT_TYPE_STRUCTURE) then
RemoveUnit(enum_unit)
end
enum_unit = FirstOfGroup(g)
end
end
end
DestroyGroup(g)
end
function funcs.starting_units()
local p
for player_id = 0, bj_MAX_PLAYERS-1 do
p = Player(player_id)
if (GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING) then
local indexStartLoc, indexRace = GetStartLocationLoc(GetPlayerStartLocation(p)), GetPlayerRace(p)
-- Create initial race-specific starting units
if #race_cond[indexRace] == 1 then
-- Check if the race has only one layout (or faction)
player_fact[player_id] = 1
race_cond[indexRace].setupfunc[1](p, indexStartLoc, true, true, true)
RemoveLocation(indexStartLoc)
if GetPlayerController(p) == MAP_CONTROL_USER then
dialogs.max_players = dialogs.max_players + 1
if GetLocalPlayer() == p then
if l_melee.display_elements then
l_melee.display_elements(true)
end
end
end
else
-- Computer units select normal race units by default.
if GetPlayerController(p) == MAP_CONTROL_COMPUTER then
player_fact[player_id] = 1
race_cond[indexRace].setupfunc[1](p, indexStartLoc, true, true, true)
RemoveLocation(indexStartLoc)
elseif GetPlayerController(p) == MAP_CONTROL_USER then
-- Setup a Dialog System here.
-- If greater than 5, revamp using UI natives.
funcs.queuePlayerForDialog(p, indexRace, indexStartLoc, true, true, true)
dialogs.max_players = dialogs.max_players + 1
end
end
end
end
if #dialogs.players > 0 then
if dialogs.max_players > 1 then
doAfter(0.00, function()
PauseGame(true)
end)
end
else
if l_melee.display_elements then
l_melee.display_elements(false)
end
doAfter(0.00, function()
if l_melee.remove_elements then
l_melee.remove_elements()
end
end)
end
end
function funcs.starting_ai()
for player_id = 0,bj_MAX_PLAYERS-1 do
local indexPlayer = Player(player_id)
if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
local indexRace = GetPlayerRace(indexPlayer)
if (GetPlayerController(indexPlayer) == MAP_CONTROL_COMPUTER) then
-- Run a race-specific melee AI script.
StartMeleeAI(indexPlayer, race_cond[indexRace].melee_ai[1])
if indexRace == RACE_UNDEAD then
RecycleGuardPosition(bj_ghoul[player_id])
end
ShareEverythingWithTeamAI(indexPlayer)
end
end
end
end
function funcs.victory_defeat()
local trig
local triggerList = {CreateTrigger(), CreateTrigger(), CreateTrigger(), CreateTrigger(), CreateTrigger(), CreateTrigger()}
local indexPlayer
-- Create a timer window for the "finish soon" timeout period, it has no timer
-- because it is driven by real time (outside of the game state to avoid desyncs)
bj_finishSoonTimerDialog = CreateTimerDialog(nil)
-- Set a trigger to fire when we receive a "finish soon" game event
trig = CreateTrigger()
TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_SOON)
TriggerAddAction(trig, MeleeTriggerTournamentFinishSoon)
-- Set a trigger to fire when we receive a "finish now" game event
trig = CreateTrigger()
TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_NOW)
TriggerAddAction(trig, MeleeTriggerTournamentFinishNow)
do
local ENUM_GROUP = CreateGroup()
local function CheckForVictors()
local opponentlessPlayers = CreateForce()
local gameOver = false
-- Check to see if any players have opponents remaining.
for playerIndex = 0, bj_MAX_PLAYERS - 1 do
if (not bj_meleeDefeated[playerIndex]) then
-- Determine whether or not this player has any remaining opponents.
for opponentIndex = 0, bj_MAX_PLAYERS - 1 do
-- If anyone has an opponent, noone can be victorious yet.
if MeleePlayerIsOpponent(playerIndex, opponentIndex) then
DestroyForce(opponentlessPlayers)
return CreateForce()
end
end
--[[
// Keep track of each opponentless player so that we can give
// them a victory later.
]]
ForceAddPlayer(opponentlessPlayers, Player(playerIndex))
gameOver = true
end
end
-- Set the game over global flag
bj_meleeGameOver = gameOver
return opponentlessPlayers
end
local function GetAllyKeyStructureCount(whichPlayer)
local keyStructs = 0
-- Count the number of buildings controlled by all not-yet-defeated co-allies.
for playerIndex = 0,bj_MAX_PLAYERS - 1 do
local indexPlayer = Player(playerIndex)
if (PlayersAreCoAllied(whichPlayer, indexPlayer)) then
GroupEnumUnitsOfPlayer(ENUM_GROUP, indexPlayer, nil)
ForGroup(ENUM_GROUP, function()
local uu = GetEnumUnit()
if type(town_hall_ids.index[GetUnitTypeId(uu)]) == 'nil' or (not UnitAlive(uu)) then
GroupRemoveUnit(ENUM_GROUP, uu)
end
end)
keyStructs = keyStructs + BlzGroupGetSize(ENUM_GROUP)
end
end
return keyStructs
end
local function PlayerIsCrippled(player)
-- Dead teams are not considered to be crippled.
return (MeleeGetAllyStructureCount(player) > 0) and (GetAllyKeyStructureCount(player) <= 0) and (not bj_meleeVictoried[GetPlayerId(player)])
end
local function CheckForCrippledPlayers()
local crippledPlayers = CreateForce()
-- The "finish soon" exposure of all players overrides any "crippled" exposure
if bj_finishSoonAllExposed then
return
end
-- Check each player to see if he or she has been crippled or uncrippled.
for playerIndex = 0,bj_MAX_PLAYERS - 1 do
local indexPlayer = Player(playerIndex)
local isNowCrippled = PlayerIsCrippled(indexPlayer)
if (not bj_playerIsCrippled[playerIndex] and isNowCrippled) then
-- Player became crippled; start their cripple timer.
bj_playerIsCrippled[playerIndex] = true
TimerStart(bj_crippledTimer[playerIndex], bj_MELEE_CRIPPLE_TIMEOUT, false, MeleeCrippledPlayerTimeout)
if (GetLocalPlayer() == indexPlayer) then
-- Use only local code (no net traffic) within this block to avoid desyncs.
-- Show the timer window.
TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], true)
-- Display a warning message.
DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, MeleeGetCrippledWarningMessage(indexPlayer))
end
elseif (bj_playerIsCrippled[playerIndex] and not isNowCrippled) then
-- Player became uncrippled; stop their cripple timer.
bj_playerIsCrippled[playerIndex] = false
PauseTimer(bj_crippledTimer[playerIndex])
if (GetLocalPlayer() == indexPlayer) then
-- Use only local code (no net traffic) within this block to avoid desyncs.
-- Hide the timer window for this player.
TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], false)
-- Display a confirmation message if the player's team is still alive.
if (MeleeGetAllyStructureCount(indexPlayer) > 0) then
if (bj_playerIsExposed[playerIndex]) then
DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNREVEALED"))
else
DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNCRIPPLED"))
end
end
end
-- If the player granted shared vision, deny that vision now.
MeleeExposePlayer(indexPlayer, false)
end
end
end
local function CheckForLosersAndVictors()
local defeatedPlayers = CreateForce()
local victoriousPlayers
-- If the game is already over, do nothing
if (bj_meleeGameOver) then
return
end
--[[
If the game was disconnected then it is over, in this case we
don't want to report results for anyone as they will most likely
conflict with the actual game results
]]
if (GetIntegerGameState(GAME_STATE_DISCONNECTED) ~= 0) then
bj_meleeGameOver = true
return
end
-- Check each player to see if he or she has been defeated yet.
for playerIndex = 0,bj_MAX_PLAYERS - 1 do
local indexPlayer = Player(playerIndex)
if (not bj_meleeDefeated[playerIndex] and not bj_meleeVictoried[playerIndex]) then
-- DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "Player"+I2S(playerIndex)+" has "+I2S(MeleeGetAllyStructureCount(indexPlayer))+" ally buildings.")
if (MeleeGetAllyStructureCount(indexPlayer) <= 0) then
-- Keep track of each defeated player so that we can give
-- them a defeat later.
ForceAddPlayer(defeatedPlayers, Player(playerIndex))
-- Set their defeated flag now so MeleeCheckForVictors
-- can detect victors.
bj_meleeDefeated[playerIndex] = true
end
end
end
-- Now that the defeated flags are set, check if there are any victors
victoriousPlayers = CheckForVictors()
-- Defeat all defeated players
ForForce(defeatedPlayers, MeleeDoDefeatEnum)
-- Give victory to all victorious players
ForForce(victoriousPlayers, MeleeDoVictoryEnum)
DestroyForce(defeatedPlayers)
DestroyForce(victoriousPlayers)
-- If the game is over we should remove all observers
if (bj_meleeGameOver) then
MeleeRemoveObservers()
end
end
local function CheckLostUnit(whichunit)
local lostUnitOwner = GetOwningPlayer(lostUnit)
if (GetPlayerStructureCount(lostUnitOwner, true) <= 0) then
CheckForLosersAndVictors()
end
--[[
// Check if the lost unit has crippled or uncrippled the player.
// (A team with 0 units is dead, and thus considered uncrippled.)
]]
CheckForCrippledPlayers()
end
local function CheckAddedUnit(whichunit)
local addedUnitOwner = GetOwningPlayer(addedUnit)
-- If the player was crippled, this unit may have uncrippled him/her.
if (bj_playerIsCrippled[GetPlayerId(addedUnitOwner)]) then
CheckForCrippledPlayers()
end
end
TriggerAddAction(triggerList[1], function()
CheckLostUnit(GetCancelledStructure())
end)
TriggerAddAction(triggerList[2], function()
if (IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE)) then
CheckLostUnit(GetTriggerUnit())
end
end)
TriggerAddAction(triggerList[3], function()
CheckAddedUnit(GetConstructingStructure())
end)
TriggerAddAction(triggerList[4], function()
local thePlayer = GetTriggerPlayer()
CachePlayerHeroData(thePlayer)
if (MeleeGetAllyCount(thePlayer) > 0) then
-- If at least one ally is still alive and kicking, share units with
-- them and proceed with death.
ShareEverythingWithTeam(thePlayer)
if (not bj_meleeDefeated[GetPlayerId(thePlayer)]) then
MeleeDoDefeat(thePlayer)
end
else
-- If no living allies remain, swap all units and buildings over to
-- neutral_passive and proceed with death.
MakeUnitsPassiveForTeam(thePlayer)
if (not bj_meleeDefeated[GetPlayerId(thePlayer)]) then
MeleeDoDefeat(thePlayer)
end
end
CheckForLosersAndVictors()
end)
local TriggerActionPlayerLeft = function(thePlayer)
-- Just show game over for observers when they leave
if (IsPlayerObserver(thePlayer)) then
RemovePlayerPreserveUnitsBJ(thePlayer, PLAYER_GAME_RESULT_NEUTRAL, false)
return
end
-- Inspect the player for leaving while in queue.
funcs.queueRemovePlayer(thePlayer)
CachePlayerHeroData(thePlayer)
-- This is the same as defeat except the player generates the message
-- "player left the game" as opposed to "player was defeated".
if (MeleeGetAllyCount(thePlayer) > 0) then
-- If at least one ally is still alive and kicking, share units with
-- them and proceed with death.
ShareEverythingWithTeam(thePlayer)
else
-- If no living allies remain, swap all units and buildings over to
-- neutral_passive and proceed with death.
MakeUnitsPassiveForTeam(thePlayer)
end
MeleeDoLeave(thePlayer)
if not dialogs.donePlayers then
CheckForLosersAndVictors()
else
doAfter(0.01, CheckForLosersAndVictors)
end
end
TriggerAddAction(triggerList[5], function()
TriggerActionPlayerLeft(GetTriggerPlayer())
end)
TriggerAddAction(triggerList[6], function()
if dialogs.donePlayers then
doAfter(0.01, function()
CheckForLosersAndVictors()
CheckForCrippledPlayers()
end)
return
end
CheckForLosersAndVictors()
CheckForCrippledPlayers()
end)
TriggerAddAction(triggerList[6], function()
TriggerActionPlayerLeft(GetTriggerPlayer())
end)
end
-- Set up each player's mortality code.
for index=0,bj_MAX_PLAYERS-1 do
local indexPlayer = Player(index)
-- Make sure this player slot is playing.
if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
bj_meleeDefeated[index] = false
bj_meleeVictoried[index] = false
-- Create a timer and timer window in case the player is crippled.
bj_playerIsCrippled[index] = false
bj_playerIsExposed[index] = false
bj_crippledTimer[index] = CreateTimer()
bj_crippledTimerWindows[index] = CreateTimerDialog(bj_crippledTimer[index])
TimerDialogSetTitle(bj_crippledTimerWindows[index], MeleeGetCrippledTimerMessage(indexPlayer))
-- Set a trigger to fire whenever a building is cancelled for this player.
TriggerRegisterPlayerUnitEvent(triggerList[1], indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, null)
-- Set a trigger to fire whenever a unit dies for this player.
TriggerRegisterPlayerUnitEvent(triggerList[2], indexPlayer, EVENT_PLAYER_UNIT_DEATH, null)
-- Set a trigger to fire whenever a unit begins construction for this player
TriggerRegisterPlayerUnitEvent(triggerList[3], indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_START, null)
-- Set a trigger to fire whenever this player defeats-out
TriggerRegisterPlayerEvent(triggerList[4], indexPlayer, EVENT_PLAYER_DEFEAT)
-- Set a trigger to fire whenever this player leaves
TriggerRegisterPlayerEvent(triggerList[5], indexPlayer, EVENT_PLAYER_LEAVE)
-- Set a trigger to fire whenever this player changes his/her alliances.
TriggerRegisterPlayerAllianceChange(triggerList[6], indexPlayer, ALLIANCE_PASSIVE)
TriggerRegisterPlayerStateEvent(triggerList[6], indexPlayer, PLAYER_STATE_ALLIED_VICTORY, EQUAL, 1)
else
bj_meleeDefeated[index] = true
bj_meleeVictoried[index] = false
-- Handle leave events for observers
if (IsPlayerObserver(indexPlayer)) then
-- Set a trigger to fire whenever this player leaves
TriggerRegisterPlayerEvent(triggerList[6], indexPlayer, EVENT_PLAYER_LEAVE)
end
end
end
UnitDex.register("ENTER_EVENT", function()
if IsUnitType(UnitDex.eventUnit, UNIT_TYPE_STRUCTURE) then
CheckAddedUnit(UnitDex.eventUnit)
end
end)
UnitDex.register("LEAVE_EVENT", function()
if IsUnitType(UnitDex.eventUnit, UNIT_TYPE_STRUCTURE) then
CheckLostUnit(UnitDex.eventUnit)
end
end)
-- Test for victory / defeat at startup, in case the user has already won / lost.
-- Allow for a short time to pass first, so that the map can finish loading.
doAfter(2.0, false, MeleeTriggerActionAllianceChange)
end
function l_melee.initialization()
local result = {}
funcs.preInit()
funcs.starting_vis()
funcs.starting_hero_limit()
funcs.grant_hero_items()
funcs.starting_resources()
funcs.clear_units()
funcs.starting_units()
funcs.starting_ai()
funcs.victory_defeat()
end
end
do
local l_melee = getmetatable(CustomMelee)
local ui_display = {TOC_PATH = "war3mapimported\\CustomText.toc",
MODEL = "ReplaceableTextures\\CommandButtons\\BTNPeon.blp"}
function l_melee.ui_initialization()
local loaded_toc = BlzLoadTOCFile(ui_display.TOC_PATH)
ui_display.main = BlzCreateFrame("QuestButtonBaseTemplate", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), 1, 1)
ui_display.main_text = BlzCreateFrameByType("TEXT", "", ui_display.main, "CustomText" , 0)
ui_display.main_model = BlzCreateFrameByType("BACKDROP", "UI Model", ui_display.main, "", 0)
BlzFrameSetParent(ui_display.main, nil)
BlzFrameSetSize(ui_display.main, 0.20, 0.1875)
BlzFrameClearAllPoints(ui_display.main)
BlzFrameSetAbsPoint(ui_display.main, FRAMEPOINT_CENTER, 0.4, 0.3)
BlzFrameSetSize(ui_display.main_model, 0.14, 0.14)
BlzFrameSetTexture(ui_display.main_model, ui_display.MODEL, 0, true)
BlzFrameSetScale(ui_display.main_text, 1.35)
BlzFrameSetText(ui_display.main_text, "Please wait for others...")
BlzFrameSetPoint(ui_display.main_text, FRAMEPOINT_TOP, ui_display.main, FRAMEPOINT_TOP, 0, -0.00875)
BlzFrameSetPoint(ui_display.main_model, FRAMEPOINT_TOP, ui_display.main_text, FRAMEPOINT_BOTTOM, 0, -0.0075)
BlzFrameSetVisible(ui_display.main, false)
BlzFrameSetVisible(ui_display.main_text, false)
BlzFrameSetVisible(ui_display.main_model, false)
end
function l_melee.display_elements(flag)
BlzFrameSetVisible(ui_display.main, flag)
BlzFrameSetVisible(ui_display.main_text, flag)
BlzFrameSetVisible(ui_display.main_model, flag)
end
function l_melee.remove_elements()
BlzDestroyFrame(ui_display.main_model)
BlzDestroyFrame(ui_display.main_text)
BlzDestroyFrame(ui_display.main)
end
end
CustomMeleeSetup = {
unitSpacing = 64.00,
minTreeDist = 3.50,
minWispDist = 1.75,
}
CustomMeleeSetup.minTreeDist = CustomMeleeSetup.minTreeDist * bj_CELLWIDTH
CustomMeleeSetup.minWispDist = CustomMeleeSetup.minWispDist * bj_CELLWIDTH
do
CustomMelee.add_townhallID('htow', 'hkee', 'hcas',
'ogre', 'ostr', 'ofrt',
'unpl', 'unp1', 'unp2',
'etol', 'etoa', 'etoe')
-- Starting Base Layouts.
local funcs = CustomMeleeSetup
funcs.human_id = CustomMelee.add_race_faction(RACE_HUMAN, "Alliance")
funcs.orc_id = CustomMelee.add_race_faction(RACE_ORC, "Horde")
funcs.undead_id = CustomMelee.add_race_faction(RACE_UNDEAD, "Scourge")
funcs.elf_id = CustomMelee.add_race_faction(RACE_NIGHTELF, "Sentinel")
funcs.demon_id = CustomMelee.add_race_faction(RACE_DEMON, "normal")
funcs.other_id = CustomMelee.add_race_faction(RACE_OTHER, "normal")
funcs.def = function(whichplayer, startloc, hallId, peonId)
if type(hallId) == 'string' then hallId = FourCC(hallId) end
if type(peonId) == 'string' then peonId = FourCC(peonId) end
local nearestMine = MeleeFindNearestMine(startloc, bj_MELEE_MINE_SEARCH_RADIUS)
local peonX
local peonY
local heroLoc
local townHall
if (nearestMine ~= nil) then
-- Spawn Town Hall at the start location.
townHall = CreateUnitAtLoc(whichplayer, hallId, startloc, bj_UNIT_FACING)
-- Spawn Peasants near the mine.
local mineLoc = GetUnitLoc(nearestMine)
local nearMineLoc = MeleeGetProjectedLoc(mineLoc, startloc, 320, 0)
peonX, peonY = GetLocationX(nearMineLoc), GetLocationY(nearMineLoc)
CreateUnit(whichplayer, peonId, peonX + 0.00 * funcs.unitSpacing, peonY + 1.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, peonId, peonX + 1.00 * funcs.unitSpacing, peonY + 0.15 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, peonId, peonX - 1.00 * funcs.unitSpacing, peonY + 0.15 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, peonId, peonX + 0.60 * funcs.unitSpacing, peonY - 1.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, peonId, peonX - 0.60 * funcs.unitSpacing, peonY - 1.00 * funcs.unitSpacing, bj_UNIT_FACING)
-- Set random hero spawn point to be off to the side of the start location.
heroLoc = MeleeGetProjectedLoc(mineLoc, startloc, 384, 45)
RemoveLocation(mineLoc)
RemoveLocation(nearMineLoc)
else
-- Spawn Town Hall at the start location.
townHall = CreateUnitAtLoc(whichplayer, hallId, startloc, bj_UNIT_FACING)
-- Spawn Peasants directly south of the town hall.
peonX = GetLocationX(startloc)
peonY = GetLocationY(startloc) - 224.00
CreateUnit(whichplayer, peonId, peonX + 2.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, peonId, peonX + 1.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, peonId, peonX + 0.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, peonId, peonX - 1.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, peonId, peonX - 2.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
-- Set random hero spawn point to be just south of the start location.
heroLoc = Location(peonX, peonY - 2.00 * funcs.unitSpacing)
end
return peonX, peonY, heroLoc, townHall, nearestMine
end
funcs.def_human = function(whichplayer, startloc, doheroes, docamera, dopreload)
if (dopreload) then
Preloader("scripts\\HumanMelee.pld")
end
local peonX, peonY, heroLoc, townHall = funcs.def(whichplayer, startloc, 'htow', 'hpea')
if (townHall ~= nil) then
UnitAddAbility(townHall, FourCC('Amic'))
UnitMakeAbilityPermanent(townHall, true, FourCC('Amic'))
end
return peonX, peonY, heroLoc
end
funcs.def_orc = function(whichplayer, startloc, doheroes, docamera, dopreload)
if (dopreload) then
Preloader("scripts\\OrcMelee.pld")
end
local peonX, peonY, heroLoc = funcs.def(whichplayer, startloc, 'ogre', 'opeo')
return peonX, peonY, heroLoc
end
funcs.def_nightelf = function(whichplayer, startloc, doheroes, docamera, dopreload)
if (dopreload) then
Preloader( "scripts\\NightElfMelee.pld" )
end
local townhall, heroLoc, peonX, peonY
local nearestMine = MeleeFindNearestMine(startloc, bj_MELEE_MINE_SEARCH_RADIUS)
if nearestMine then
-- Spawn Tree of Life near the mine and have it entangle the mine.
-- Project the Tree's coordinates from the gold mine, and then snap
-- the X and Y values to within minTreeDist of the Gold Mine.
local mineLoc = GetUnitLoc(nearestMine)
local nearMineLoc = MeleeGetProjectedLoc(mineLoc, startloc, 650, 0)
nearMineLoc = MeleeGetLocWithinRect(nearMineLoc, GetRectFromCircleBJ(mineLoc, funcs.minTreeDist))
townhall = CreateUnitAtLoc(whichplayer, FourCC('etol'), nearMineLoc, bj_UNIT_FACING)
IssueTargetOrder(townhall, "entangleinstant", nearestMine)
-- Spawn Wisps at the start location.
local wispLoc = MeleeGetProjectedLoc(mineLoc, startloc, 280, 0)
wispLoc = MeleeGetLocWithinRect(wispLoc, GetRectFromCircleBJ(mineLoc, funcs.minWispDist))
peonX = GetLocationX(wispLoc)
peonY = GetLocationY(wispLoc)
CreateUnit(whichplayer, FourCC('ewsp'), peonX + 0.00 * funcs.unitSpacing, peonY + 1.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('ewsp'), peonX + 1.00 * funcs.unitSpacing, peonY + 0.15 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('ewsp'), peonX - 1.00 * funcs.unitSpacing, peonY + 0.15 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('ewsp'), peonX + 0.58 * funcs.unitSpacing, peonY - 1.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('ewsp'), peonX - 0.58 * funcs.unitSpacing, peonY - 1.00 * funcs.unitSpacing, bj_UNIT_FACING)
-- Set random hero spawn point to be off to the side of the start location.
heroLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine), startloc, 384, 45)
else
-- Spawn Tree of Life at the start location.
CreateUnitAtLoc(whichplayer, FourCC('etol'), startloc, bj_UNIT_FACING)
-- Spawn Wisps directly south of the town hall.
peonX = GetLocationX(startloc)
peonY = GetLocationY(startloc) - 224.00
CreateUnit(whichplayer, FourCC('ewsp'), peonX - 2.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('ewsp'), peonX - 1.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('ewsp'), peonX + 0.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('ewsp'), peonX + 1.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('ewsp'), peonX + 2.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
-- Set random hero spawn point to be just south of the start location.
heroLoc = Location(peonX, peonY - 2.00 * funcs.unitSpacing)
end
return peonX, peonY, heroLoc
end
funcs.def_undead = function(whichplayer, startloc, doheroes, docamera, dopreload)
if (dopreload) then
Preloader("scripts\\UndeadMelee.pld")
end
local peonX, peonY, heroLoc
local nearestMine = MeleeFindNearestMine(startloc, bj_MELEE_MINE_SEARCH_RADIUS)
if (nearestMine ~= nil) then
-- Spawn Necropolis at the start location.
CreateUnitAtLoc(whichplayer, FourCC('unpl'), startloc, bj_UNIT_FACING)
-- Replace the nearest gold mine with a blighted version.
nearestMine = BlightGoldMineForPlayerBJ(nearestMine, whichplayer)
local mineLoc = GetUnitLoc(nearestMine)
-- Spawn Ghoul near the Necropolis.
nearTownLoc = MeleeGetProjectedLoc(startloc, mineLoc, 288, 0)
ghoulX = GetLocationX(nearTownLoc)
ghoulY = GetLocationY(nearTownLoc)
bj_ghoul[GetPlayerId(whichplayer)] = CreateUnit(whichplayer, FourCC('ugho'), ghoulX + 0.00 * funcs.unitSpacing, ghoulY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
-- Spawn Acolytes near the mine.
nearMineLoc = MeleeGetProjectedLoc(mineLoc, startloc, 320, 0)
peonX = GetLocationX(nearMineLoc)
peonY = GetLocationY(nearMineLoc)
CreateUnit(whichplayer, FourCC('uaco'), peonX + 0.00 * funcs.unitSpacing, peonY + 0.50 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('uaco'), peonX + 0.65 * funcs.unitSpacing, peonY - 0.50 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('uaco'), peonX - 0.65 * funcs.unitSpacing, peonY - 0.50 * funcs.unitSpacing, bj_UNIT_FACING)
-- Create a patch of blight around the gold mine.
SetBlightLoc(whichplayer,nearMineLoc, 768, true)
-- Set random hero spawn point to be off to the side of the start location.
heroLoc = MeleeGetProjectedLoc(mineLoc, startloc, 384, 45)
RemoveLocation(mineLoc)
RemoveLocation(nearTownLoc)
else
-- Spawn Necropolis at the start location.
CreateUnitAtLoc(whichplayer, FourCC('unpl'), startloc, bj_UNIT_FACING)
-- Spawn Acolytes and Ghoul directly south of the Necropolis.
peonX = GetLocationX(startloc)
peonY = GetLocationY(startloc) - 224.00
CreateUnit(whichplayer, FourCC('uaco'), peonX - 1.50 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('uaco'), peonX - 0.50 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('uaco'), peonX + 0.50 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, FourCC('ugho'), peonX + 1.50 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
-- Create a patch of blight around the start location.
SetBlightLoc(whichplayer,startloc, 768, true)
-- Set random hero spawn point to be just south of the start location.
heroLoc = Location(peonX, peonY - 2.00 * funcs.unitSpacing)
end
return peonX, peonY, heroLoc
end
funcs.def_human = CustomMelee.finalize_setup_func(RACE_HUMAN, "Alliance", funcs.def_human)
funcs.def_orc = CustomMelee.finalize_setup_func(RACE_ORC, "Horde", funcs.def_orc)
funcs.def_undead = CustomMelee.finalize_setup_func(RACE_UNDEAD, "Scourge", funcs.def_undead)
funcs.def_nightelf = CustomMelee.finalize_setup_func(RACE_NIGHTELF, "Sentinel", funcs.def_nightelf)
funcs.def_demon = MeleeStartingUnitsUnknownRace
funcs.def_other = MeleeStartingUnitsUnknownRace
CustomMelee.race_faction_setup(RACE_HUMAN, "Alliance", funcs.def_human)
CustomMelee.race_faction_setup(RACE_ORC, "Horde", funcs.def_orc)
CustomMelee.race_faction_setup(RACE_UNDEAD, "Scourge", funcs.def_undead)
CustomMelee.race_faction_setup(RACE_NIGHTELF, "Sentinel", funcs.def_nightelf)
CustomMelee.race_faction_setup(RACE_DEMON, "normal", funcs.def_demon)
CustomMelee.race_faction_setup(RACE_OTHER, "normal", funcs.def_other)
CustomMelee.set_race_factionAI(RACE_HUMAN, "Alliance", "human.ai")
CustomMelee.set_race_factionAI(RACE_ORC, "Horde", "orc.ai")
CustomMelee.set_race_factionAI(RACE_UNDEAD, "Scourge", "undead.ai")
CustomMelee.set_race_factionAI(RACE_NIGHTELF, "Sentinel", "elf.ai")
-- Registering Hero Ids.
-- Human
CustomMelee.add_heroID('Hamg', RACE_HUMAN, funcs.human_id)
CustomMelee.add_heroID('Hmkg', RACE_HUMAN, funcs.human_id)
CustomMelee.add_heroID('Hpal', RACE_HUMAN, funcs.human_id)
CustomMelee.add_heroID('Hblm', RACE_HUMAN, funcs.human_id)
-- Orc
CustomMelee.add_heroID('Obla', RACE_ORC, funcs.orc_id)
CustomMelee.add_heroID('Ofar', RACE_ORC, funcs.orc_id)
CustomMelee.add_heroID('Otch', RACE_ORC, funcs.orc_id)
CustomMelee.add_heroID('Oshd', RACE_ORC, funcs.orc_id)
-- Night Elf
CustomMelee.add_heroID('Edem', RACE_NIGHTELF, funcs.nightelf_id)
CustomMelee.add_heroID('Ekee', RACE_NIGHTELF, funcs.nightelf_id)
CustomMelee.add_heroID('Emoo', RACE_NIGHTELF, funcs.nightelf_id)
CustomMelee.add_heroID('Ewar', RACE_NIGHTELF, funcs.nightelf_id)
-- Undead
CustomMelee.add_heroID('Udea', RACE_UNDEAD, funcs.undead_id)
CustomMelee.add_heroID('Udre', RACE_UNDEAD, funcs.undead_id)
CustomMelee.add_heroID('Ulic', RACE_UNDEAD, funcs.undead_id)
CustomMelee.add_heroID('Ucrl', RACE_UNDEAD, funcs.undead_id)
-- Neutrals
CustomMelee.add_heroID('Npbm', RACE_OTHER, funcs.other_id)
CustomMelee.add_heroID('Nbrn', RACE_OTHER, funcs.other_id)
CustomMelee.add_heroID('Nngs', RACE_OTHER, funcs.other_id)
CustomMelee.add_heroID('Nplh', RACE_OTHER, funcs.other_id)
CustomMelee.add_heroID('Nbst', RACE_OTHER, funcs.other_id)
CustomMelee.add_heroID('Nalc', RACE_OTHER, funcs.other_id)
CustomMelee.add_heroID('Ntin', RACE_OTHER, funcs.other_id)
CustomMelee.add_heroID('Nfir', RACE_OTHER, funcs.other_id)
end
do
local Class = setmetatable({}, protected_t())
local m_class = getmetatable(Class)
local _DEF_INT = 1/32.
ClassTimer = Class
function m_class:create(interval, callback)
interval = interval or _DEF_INT
callback = (not is_function(callback) and nil) or callback
local o = {
_LIST = LinkedList:create(),
_TIMER = 0,
_POINTERS = {},
_FUNC = callback,
_INTERVAL = interval,
_STARTED = false,
}
setmetatable(o, m_class)
Initializer.register(function() o._TIMER = CreateTimer() end, "SYSTEM")
return o
end
function m_class:on_callback(func)
if not is_function(func) then return end
rawset(self, '_FUNC', func)
end
function m_class:include(elem)
if not self._POINTERS[elem] then
self._POINTERS[elem] = select(3, self._LIST:insert(elem))
if not self._STARTED then
self._STARTED = true
TimerStart(self._TIMER, self._INTERVAL, true, function()
for elem in self._LIST:iterator() do
if self._FUNC then
self._FUNC(elem)
end
end
if self._LIST.size == 0 then
self._STARTED = false
PauseTimer(self._TIMER)
end
end)
end
end
end
function m_class:remove(elem)
if not self._POINTERS[elem] then return end
self._LIST:erase(self._POINTERS[elem])
self._POINTERS[elem] = nil
end
function m_class:is_in(elem)
return self._LIST:is_in(elem)
end
function m_class:pointer(elem)
return self._POINTERS[elem]
end
end
PendantOfOuroboros = {}
do
local tb = protected_t()
PendantOfOuroboros.RestoreMana = setmetatable({}, tb)
tb.custom = {
ITEM_ID = FourCC("I003"),
MANA_RESTORED_RATIO = 0.35,
}
Initializer("USER", function()
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_USE_ITEM, function()
local item = GetManipulatedItem()
if GetItemTypeId(item) == tb.custom.ITEM_ID then
local unit = GetManipulatingUnit()
SetUnitState(unit, UNIT_STATE_MANA,
GetUnitState(unit, UNIT_STATE_MANA) + GetUnitMaxMana(unit)*tb.custom.MANA_RESTORED_RATIO)
end
end)
end)
end
do
local tb = protected_t()
PendantOfOuroboros.AbsorbDamage = setmetatable({}, tb)
tb.custom = {
ITEM_ID = PendantOfOuroboros.RestoreMana.custom.ITEM_ID,
DAMAGE_ABSORBED = 0.1,
MANA_PER_DAMAGE = 1., -- Please do not set this to 0.
}
DamageEvent.register_modifier("MODIFIER_EVENT_DELTA", function()
local targ = DamageEvent.current.target
if GetInventoryIndexOfItemTypeBJ(targ, tb.custom.ITEM_ID) > 0 then
local dmg = math.min(DamageEvent.current.dmg*tb.custom.DAMAGE_ABSORBED,
GetUnitState(targ, UNIT_STATE_MANA)/tb.custom.MANA_PER_DAMAGE)
local cost = dmg*tb.custom.MANA_PER_DAMAGE
DamageEvent.current.dmg = DamageEvent.current.dmg - dmg
SetUnitState(targ, UNIT_STATE_MANA, GetUnitState(targ, UNIT_STATE_MANA) - cost)
end
end)
end
DebtSystem = setmetatable({}, protected_t())
do
local debt = DebtSystem
local m_debt = getmetatable(debt)
local amount = {gold={}, lumber={}, detector={}}
m_debt.debts = {}
m_debt._PATH = {"war3mapImported\\CustomDialog.toc", "war3mapImported\\BoxedText.toc"}
m_debt.__metatable = debt
function m_debt:destroy()
if rawget(self, 'onForgive') then
self.onForgive(self)
self.onForgive = nil
end
self.gold, self.lumber = nil
self.id = nil
m_debt.__metatable = nil
setmetatable(self, nil)
m_debt.__metatable = debt
end
local function update_text(gold, lumber)
BlzFrameSetText(BlzGetFrameByName("BoxedTextValue", 0),
"Gold: " .. tostring(gold) .. "|n" ..
"Lumber: " .. tostring(lumber))
end
local function pay(player)
-- Payment should be FIFO
local id = GetPlayerId(player)
for debts, pointer in m_debt.debts[id]:iterator() do
local gold, lumber = GetPlayerState(player, PLAYER_STATE_RESOURCE_GOLD), GetPlayerState(player, PLAYER_STATE_RESOURCE_LUMBER)
SetPlayerState(player, PLAYER_STATE_RESOURCE_GOLD, gold - debts.gold)
SetPlayerState(player, PLAYER_STATE_RESOURCE_LUMBER, lumber - debts.lumber)
amount.gold[id] = amount.gold[id] - math.min(debts.gold, gold)
amount.lumber[id] = amount.lumber[id] - math.min(debts.lumber, lumber)
debts.gold = math.max(debts.gold - gold, 0)
debts.lumber = math.max(debts.lumber - lumber, 0)
if debts.gold > 0 or debts.lumber > 0 then break end
m_debt.debts[id]:erase(pointer)
debts:destroy()
end
return amount.gold[id] > 0 or amount.lumber[id] > 0
end
function m_debt:update_cost(gold, lumber)
gold, lumber = gold or self.gold, lumber or self.lumber
amount.gold[self.id] = amount.gold[self.id] - self.gold + gold
amount.lumber[self.id] = amount.lumber[self.id] - self.lumber + gold
self.gold = gold
self.lumber = lumber
-- Setting gold and lumber to 0 will make the system treat the debt
-- as forgiven, thus preparing it for destruction.
if self.gold <= 0 and self.lumber <= 0 then
m_debt.debts[id]:remove(self)
self:destroy()
end
end
function m_debt:forgive()
self:update_cost(0, 0)
end
local function process_debt(player)
local i = GetPlayerId(player)
-- Immediately pay debt.
if pay(player) then
if not IsTriggerEnabled(amount.detector[i]) then
EnableTrigger(amount.detector[i])
end
if PlayerUtils.player == player then
if not BlzFrameIsVisible(m_debt.container) then
BlzFrameSetVisible(m_debt.container, true)
end
update_text(amount.gold[i], amount.lumber[i])
end
end
end
function m_debt:add_debt_item(player, gold, lumber)
local id = GetPlayerId(player)
local o = setmetatable({gold=gold or 0, lumber = lumber or 0, id=id}, m_debt)
amount.gold[id] = amount.gold[id] + o.gold
amount.lumber[id] = amount.lumber[id] + o.lumber
m_debt.debts[id]:insert(o)
-- Add this debt.
doAfter(0.00, function() process_debt(player) end)
return o
end
function m_debt:on_forgive(callback)
if not is_function(callback) then return end
rawset(self, 'onForgive', callback)
end
function m_debt.load_ui()
BlzLoadTOCFile(m_debt._PATH[1])
BlzLoadTOCFile(m_debt._PATH[2])
m_debt.container = BlzCreateFrame("CustomDialogWithText", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), 0, 0)
m_debt.container_shadow = BlzCreateFrameByType("FRAME", "FaceFrame", m_debt.container, "", 0)
m_debt.tooltip = BlzCreateFrame("BoxedText", m_debt.container, 0, 0)
BlzFrameSetAllPoints(m_debt.container_shadow, m_debt.container)
BlzFrameSetTooltip(m_debt.container_shadow, m_debt.tooltip)
BlzFrameSetSize(m_debt.container, 0.10, 0.03)
BlzFrameSetSize(m_debt.tooltip, 0.175, 0.06)
BlzFrameSetAbsPoint(m_debt.container, FRAMEPOINT_RIGHT, 0.80, 0.18)
BlzFrameSetPoint(m_debt.tooltip, FRAMEPOINT_BOTTOM, m_debt.container, FRAMEPOINT_BOTTOM, 0, 0.03)
--BlzFrameSetAbsPoint(m_debt.tooltip, FRAMEPOINT_RIGHT, 0.00, 0.23)
BlzFrameSetText(BlzGetFrameByName("CustomDialogText", 0), "Debt:")
BlzFrameSetText(BlzGetFrameByName("BoxedTextTitle", 0), "Resources:")
BlzFrameSetText(BlzGetFrameByName("BoxedTextValue", 0), "Gold: 0 |nLumber: 0")
BlzFrameSetVisible(m_debt.container, false)
end
function m_debt.load_player_info()
local callback_func = function()
local player = GetTriggerPlayer()
local id = GetPlayerId(player)
DisableTrigger(amount.detector[id])
if pay(player) then
EnableTrigger(amount.detector[id])
if PlayerUtils.player == player then
update_text(amount.gold[id], amount.lumber[id])
end
else
-- Update UI Element.
if PlayerUtils.player == player then
BlzFrameSetVisible(m_debt.container, false)
end
end
end
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
local p = Player(i)
amount.detector[i] = CreateTrigger()
amount.gold[i] = 0
amount.lumber[i] = 0
m_debt.debts[i] = LinkedList:create()
TriggerRegisterPlayerStateEvent(amount.detector[i], p, PLAYER_STATE_RESOURCE_GOLD, NOT_EQUAL, 0)
TriggerRegisterPlayerStateEvent(amount.detector[i], p, PLAYER_STATE_RESOURCE_LUMBER, NOT_EQUAL, 0)
TriggerAddCondition(amount.detector[i], Filter(callback_func))
end
end
Initializer.registerBJ("SYSTEM", function()
m_debt.load_ui()
m_debt.load_player_info()
end)
end
do
local faction = {
name = "Death and Taxes",
mineId = FourCC("u003"),
townHallId = FourCC("h000"),
contractId = FourCC("u000"),
engineerId = FourCC("u001"),
shadeId = FourCC("u002"),
}
local funcs = CustomMeleeSetup
faction.setup_id = CustomMelee.add_race_faction(RACE_UNDEAD, faction.name)
CustomMelee.add_townhallID('h000', 'h004', 'h007')
CustomMelee.add_heroID('U009', RACE_UNDEAD, faction.setup_id)
CustomMelee.add_heroID('U00D', RACE_UNDEAD, faction.setup_id)
CustomMelee.add_heroID('U00I', RACE_UNDEAD, faction.setup_id)
CustomMelee.add_heroID('U00K', RACE_UNDEAD, faction.setup_id)
faction.setup = function(whichplayer, startloc, doheroes, docamera, dopreload)
local nearestMine = MeleeFindNearestMine(startloc, bj_MELEE_MINE_SEARCH_RADIUS)
local peonX
local peonY
local heroLoc
local townHall
if (nearestMine ~= nil) then
-- Spawn Town Hall at the start location.
townHall = CreateUnitAtLoc(whichplayer, faction.townHallId, startloc, bj_UNIT_FACING)
-- Spawn Peasants near the mine.
local mineLoc = GetUnitLoc(nearestMine)
local nearMineLoc = MeleeGetProjectedLoc(mineLoc, startloc, 280, 0)
peonX, peonY = GetLocationX(nearMineLoc), GetLocationY(nearMineLoc)
ShowUnit(nearestMine, false)
local newMine = CreateUnit(whichplayer, faction.mineId, GetLocationX(mineLoc), GetLocationY(mineLoc), bj_UNIT_FACING)
SetResourceAmount(newMine, GetResourceAmount(nearestMine))
CreateUnit(whichplayer, faction.engineerId, peonX + 0.00 * funcs.unitSpacing, peonY + 1.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, faction.shadeId, peonX + 1.00 * funcs.unitSpacing, peonY + 0.15 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, faction.shadeId, peonX - 1.00 * funcs.unitSpacing, peonY + 0.15 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, faction.shadeId, peonX + 0.60 * funcs.unitSpacing, peonY - 1.35 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, faction.shadeId, peonX - 0.60 * funcs.unitSpacing, peonY - 1.35 * funcs.unitSpacing, bj_UNIT_FACING)
-- Set random hero spawn point to be off to the side of the start location.
heroLoc = MeleeGetProjectedLoc(mineLoc, startloc, 384, 45)
RemoveLocation(mineLoc)
RemoveLocation(nearMineLoc)
else
-- Spawn Town Hall at the start location.
townHall = CreateUnitAtLoc(whichplayer, faction.townHallId, startloc, bj_UNIT_FACING)
-- Spawn Peasants directly south of the town hall.
peonX = GetLocationX(startloc)
peonY = GetLocationY(startloc) - 224.00
CreateUnit(whichplayer, faction.contractId, peonX + 2.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, faction.engineerId, peonX + 1.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, faction.engineerId, peonX + 0.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, faction.shadeId, peonX - 1.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
CreateUnit(whichplayer, faction.shadeId, peonX - 2.00 * funcs.unitSpacing, peonY + 0.00 * funcs.unitSpacing, bj_UNIT_FACING)
-- Set random hero spawn point to be just south of the start location.
heroLoc = Location(peonX, peonY - 2.00 * funcs.unitSpacing)
end
return peonX, peonY, heroLoc, townHall, nearestMine
end
faction.setup = CustomMelee.finalize_setup_func(RACE_UNDEAD, faction.name, faction.setup)
CustomMelee.race_faction_setup(RACE_UNDEAD, faction.name, faction.setup)
end
VoidRejuvenator = {}
do
local spawn = setmetatable({}, protected_t())
m_spawn = getmetatable(spawn)
VoidRejuvenator.Spawn = spawn
m_spawn.custom = {
UNIT_ID = FourCC("e001"),
FOG_EFFECT = "war3mapImported\\Radioactivecloud (generic).mdx",
}
UnitDex.register("ENTER_EVENT", function()
if GetUnitTypeId(UnitDex.eventUnit) == m_spawn.custom.UNIT_ID then
SetUnitAnimation(UnitDex.eventUnit, "morph alternate")
QueueUnitAnimation(UnitDex.eventUnit, "stand")
doAfter(0.75, function(unit)
local fx = AddSpecialEffect(m_spawn.custom.FOG_EFFECT, GetUnitX(unit), GetUnitY(unit))
BlzSetSpecialEffectColor(fx, 32, 64, 32)
BlzSetSpecialEffectTimeScale(fx, 2.5)
SetEffectScale(fx, 0.5)
DestroyEffect(fx, 0.5)
end, UnitDex.eventUnit)
end
end)
end
do
local rejuv = setmetatable({}, protected_t())
local m_rejuv = getmetatable(rejuv)
VoidRejuvenator.VoidRejuvenate = rejuv
m_rejuv.custom = {
BASE_HP_REGEN = 3,
RATIO_HP_REGEN = 0.02,
REGEN_INTERVAL = 0.25,
MAX_HP_CHECK_INTERVAL = 3.00,
}
Initializer.registerBJ("SYSTEM", function()
m_rejuv.custom.UNIT_ID = VoidRejuvenator.Spawn.custom.UNIT_ID
m_rejuv._TIMER = CreateTimer()
m_rejuv.CHECK_TICKS = math.floor(m_rejuv.custom.MAX_HP_CHECK_INTERVAL/m_rejuv.custom.REGEN_INTERVAL)
m_rejuv.custom.BASE_HP_REGEN = m_rejuv.custom.BASE_HP_REGEN*m_rejuv.custom.REGEN_INTERVAL
m_rejuv.custom.RATIO_HP_REGEN = m_rejuv.custom.RATIO_HP_REGEN*m_rejuv.custom.REGEN_INTERVAL
end)
m_rejuv.max_hp = {}
m_rejuv.list = ClassTimer:create(m_rejuv.custom.REGEN_INTERVAL)
m_rejuv.list:on_callback(function(transport)
m_rejuv.max_hp[transport].ticks = math.fmod(m_rejuv.max_hp[transport].ticks + 1, m_rejuv.CHECK_TICKS)
local is_zero = m_rejuv.max_hp[transport].ticks == 0
ForGroup(GetTransportedGroup(transport), function()
local unit = GetEnumUnit()
SetWidgetLife(unit, GetWidgetLife(unit) + m_rejuv.custom.BASE_HP_REGEN
+ m_rejuv.custom.RATIO_HP_REGEN*m_rejuv.max_hp[transport][unit])
if is_zero then
m_rejuv.max_hp[transport][unit] = GetUnitMaxHP(unit)
end
end)
end)
UnitState.register("LOAD_EVENT", function()
local transport = UnitState.eventTransport
if GetUnitTypeId(transport) == m_rejuv.custom.UNIT_ID then
if not m_rejuv.list:is_in(transport) then
m_rejuv.list:include(transport)
m_rejuv.max_hp[transport] = {ticks=0}
end
m_rejuv.max_hp[transport][UnitState.eventUnit] = GetUnitMaxHP(UnitState.eventUnit)
end
end)
UnitState.register("UNLOAD_EVENT", function()
local transport = UnitState.eventTransport
if m_rejuv.list:is_in(transport) then
m_rejuv.max_hp[transport][UnitState.eventUnit] = nil
if BlzGroupGetSize(GetTransportedGroup(transport)) == 0 then
m_rejuv.list:remove(transport)
m_rejuv.max_hp[transport] = nil
end
end
end)
end
GenericSummon = {}
do
local gen = GenericSummon
function gen.register(unitId, abilId, foodCost, off_x, off_y, gold, lumber)
unitId = (type(unitId) == 'string' and FourCC(unitId)) or (type(unitId) == 'number' and math.floor(unitId)) or 0
abilId = (type(abilId) == 'string' and FourCC(abilId)) or (type(abilId) == 'number' and math.floor(abilId)) or 0
foodCost = math.max(foodCost, 0)
local tb = {
UNIT_TYPE = unitId,
ABIL_TYPE = abilId,
FOOD_COST = foodCost,
OFFSET_X = off_x,
OFFSET_Y = off_y,
GOLD_COST = gold,
LUMBER_COST = lumber,
flag = {},
paid = {},
}
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_CAST, tb.ABIL_TYPE, function()
tb.caster = GetTriggerUnit()
tb.owner = GetOwningPlayer(tb.caster)
tb.used = GetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_FOOD_USED)
tb.cap = GetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_FOOD_CAP)
tb.cur_gold = GetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_GOLD)
tb.cur_lumb = GetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_LUMBER)
tb.id = GetHandleId(tb.caster)
if tb.used + tb.FOOD_COST > tb.cap or (tb.cur_gold < tb.GOLD_COST) or (tb.cur_lumb < tb.LUMBER_COST) then
if tb.used + tb.FOOD_COST > tb.cap then
GameError(tb.owner, "Cannot summon "..GetObjectName(tb.UNIT_TYPE)..".")
elseif (tb.cur_gold < tb.GOLD_COST) then
GameError(tb.owner, "Not enough Gold.")
elseif (tb.cur_lumb < tb.LUMBER_COST) then
GameError(tb.owner, "Not enough Lumber.")
end
BlzPauseUnitEx(tb.caster, true)
IssueImmediateOrderById(tb.caster, 851972)
BlzPauseUnitEx(tb.caster, false)
else
tb.flag[tb.id] = true
SetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_GOLD, tb.cur_gold - tb.GOLD_COST)
SetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_LUMBER, tb.cur_lumb - tb.LUMBER_COST)
SetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_FOOD_USED, tb.used + tb.FOOD_COST)
end
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_FINISH, tb.ABIL_TYPE, function()
tb.caster = GetTriggerUnit()
tb.id = GetHandleId(tb.caster)
tb.owner = GetOwningPlayer(tb.caster)
tb.paid[tb.id] = true
bj_lastCreatedUnit = CreateUnit(tb.owner, tb.UNIT_TYPE, GetUnitX(tb.caster) + tb.OFFSET_X,
GetUnitY(tb.caster) + tb.OFFSET_Y, bj_UNIT_FACING)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, tb.ABIL_TYPE, function()
tb.caster = GetTriggerUnit()
tb.owner = GetOwningPlayer(tb.caster)
tb.used = GetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_FOOD_USED)
tb.cap = GetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_FOOD_CAP)
tb.id = GetHandleId(tb.caster)
tb.cur_gold = GetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_GOLD)
tb.cur_lumb = GetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_LUMBER)
if tb.flag[tb.id] then
tb.flag[tb.id] = false
SetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_FOOD_USED, tb.used - tb.FOOD_COST)
if tb.paid[tb.id] then
tb.paid[tb.id] = false
else
SetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_GOLD, tb.cur_gold + tb.GOLD_COST)
SetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_LUMBER, tb.cur_lumb + tb.LUMBER_COST)
end
end
end)
UnitDex.register("ENTER_EVENT", function()
if tb['onCreate'] and (GetUnitTypeId(UnitDex.eventUnit) == tb.UNIT_TYPE) then tb['onCreate'](UnitDex.eventUnit) end
end)
return tb
end
end
do
local tb = GenericSummon.register("u001", "A000", 1, -96, -64, 50, 0)
function tb.onCreate(whichunit)
SetUnitAnimation(whichunit, "birth")
QueueUnitAnimation(whichunit, "stand")
end
end
do
local tb = GenericSummon.register("u002", "A001", 1, -96, -64, 50, 0)
tb.STARTING_VIS = 16
tb.END_VIS = 128
tb.TRANS_DUR = 2.00
tb.TICK_RATE = 0.25
function tb.onCreate(whichunit)
local vis = tb.STARTING_VIS
local incr = (tb.END_VIS - tb.STARTING_VIS)/tb.TRANS_DUR*tb.TICK_RATE
SetUnitVertexColor(whichunit, 255, 255, 255, vis)
SetUnitPathing(whichunit, false)
doRepeat(tb.TICK_RATE, function()
vis = vis + incr
SetUnitVertexColor(whichunit, 255, 255, 255, math.floor(vis + 0.5))
if vis >= tb.END_VIS then
PauseTimer(GetExpiredTimer())
end
end)
end
end
do
local tb = {
ABILITY_ID = FourCC("A00G"),
BUFF_ID = FourCC("B002"),
REDUCTION_AMOUNT = 0.60,
protected = {
[FourCC("u001")] = true,
[FourCC("u002")] = true
},
EFFECT_MODEL = "war3mapImported\\MagicShield.mdl",
OFFSET_X = -24,
OFFSET_Y = -12,
}
DamageEvent.register_modifier(function()
if GetUnitAbilityLevel(DamageEvent.current.target, tb.BUFF_ID) ~= 0 then
if tb.protected[GetUnitTypeId(DamageEvent.current.target)] then
DamageEvent.current.dmg = DamageEvent.current.dmg*(1-tb.REDUCTION_AMOUNT)
local cx, cy = GetUnitX(DamageEvent.current.target) + tb.OFFSET_X, GetUnitY(DamageEvent.current.target) + tb.OFFSET_Y
local fx = AddSpecialEffect(tb.EFFECT_MODEL, cx, cy)
BlzPlaySpecialEffectWithTimeScale(fx, ANIM_TYPE_STAND, 3.00)
DestroyEffect(fx, 0.50)
fx = nil
end
end
end, "MODIFIER_EVENT_DELTA")
end
do
local tb = {
ABIL_ID = FourCC("A003"),
BUFF_ID = FourCC("B000"),
HP_DECAY_RATE = 0.02,
HP_DECAY_INTERVAL = 1.00,
DMG_STACK_MAX = 3,
ATTACK_TYPE = ATTACK_TYPE_NORMAL,
DAMAGE_TYPE = DAMAGE_TYPE_POISON,
EFFECT_MODEL = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl",
ATTACH = "origin",
hasBuff = {},
}
function tb.checkForActive(target)
return UnitAlive(target) and GetUnitAbilityLevel(target, tb.BUFF_ID) > 0
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.ABIL_ID, function()
tb.caster = GetTriggerUnit()
tb.target = GetSpellTargetUnit()
SetUnitInvulnerable(tb.caster, true)
ShowUnit(tb.caster, false)
if not IsUnitType(tb.target, UNIT_TYPE_UNDEAD) then
local data = {}
data.caster = tb.caster
data.target = tb.target
data.id = GetHandleId(tb.target)
doAfter(0.00, function(data)
PauseUnit(data.caster, true)
UnitShareVision(data.target, GetOwningPlayer(data.caster), true)
end, data)
if not tb.hasBuff[data.id] then
tb.hasBuff[data.id] = {}
tb.hasBuff[data.id][1] = BuffUtils:watch(tb.BUFF_ID, data.target)
tb.hasBuff[data.id][2] = LinkedList:create()
tb.hasBuff[data.id][1]:observe(tb.HP_DECAY_INTERVAL, function()
tb.id = GetHandleId(BuffUtils.eventUnit)
local data = tb.hasBuff[tb.id][2]:first()
tb.amount = tb.HP_DECAY_RATE*tb.HP_DECAY_INTERVAL*BlzGetUnitMaxHP(data.target)
tb.amount = tb.amount*math.min(tb.hasBuff[tb.id][2].size, tb.DMG_STACK_MAX)
UnitDamageTarget(data.target, data.target,
tb.amount, false, false,
tb.ATTACK_TYPE, tb.DAMAGE_TYPE, nil)
end)
tb.hasBuff[data.id][1]:on_dispel(function()
tb.id = GetHandleId(BuffUtils.eventUnit)
for data in tb.hasBuff[tb.id][2]:iterator() do
SetUnitX(data.caster, GetUnitX(data.target))
SetUnitY(data.caster, GetUnitY(data.target))
ShowUnit(data.caster, true)
SetUnitInvulnerable(data.caster, false)
PauseUnit(data.caster, false)
tb.hasBuff[tb.id][2]:remove(data)
end
tb.hasBuff[tb.id][2]:destroy()
tb.hasBuff[tb.id] = nil
end)
end
tb.hasBuff[data.id][2]:insert(data)
data.id = nil
else
SetUnitOwner(tb.target, GetOwningPlayer(tb.caster), true)
DestroyEffect( AddSpecialEffectTarget(tb.EFFECT_MODEL, tb.target, tb.ATTACH))
doAfter(0.00, function(cast, targ)
if IsUnitSelected(cast, GetOwningPlayer(cast)) then
if GetLocalPlayer() == GetOwningPlayer(cast) then
SelectUnit(targ, true)
end
end
SetUnitInvulnerable(targ, true)
SetUnitInvulnerable(targ, false)
doAfter(0, function(targ)
UnitRemoveAbility(targ, tb.BUFF_ID)
end, targ)
RemoveUnit(cast)
end, tb.caster, tb.target)
end
end)
end
do
local tb = {
ABIL_ID = FourCC('A004'),
DUMMY_ABIL_ID = FourCC('A005'),
DUMMY_BUFF_ID = FourCC('B001'),
GHOST_ABIL_ID = FourCC('Agho'),
RESEARCH_ID = FourCC('R003'),
DUMMY_ABIL_ORDER = "slow",
MAX_FOR_REVEAL = 3,
MAX_TIME_SPAN = 5.00,
PHASE_INTERVAL = 0.10,
PHASE_RANGE = 50.00,
REVEAL_RANGE = 600.00,
REVEAL_DELAY = 6.00,
REVEAL_PRELUDE = 3.00,
REVEAL_DURATION = 7.50,
REVEAL_NOTIFICATION = "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl",
disable_count = {}, -- DEBUG_MODE only variable
target_grp = {},
target_vis_grp = {},
target_rev_grp = {},
target_rev_time = {},
timer_tb = {},
}
tb.target_reveal_timer = {}
tb.target_rev_timer_started = {}
tb.target_reveal_fx = {}
tb.REVEAL_PRELUDE = tb.REVEAL_DELAY - tb.REVEAL_PRELUDE
function tb.is_researched(whichunit)
return true -- GetPlayerTechCount(GetOwningPlayer(whichunit), tb.RESEARCH_ID, true) > 0
end
function tb.is_active(whichunit)
return UnitAlive(whichunit) and tb.is_researched(whichunit)
end
function tb.filter_target(target, whichunit)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(whichunit))
and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
and not IsUnitType(target, UNIT_TYPE_MECHANICAL)
and not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)
and not IsUnitType(target, UNIT_TYPE_FLYING)
end
function tb.slow_target(targ)
SetUnitX(tb.DUMMY_CASTER, GetUnitX(targ))
SetUnitY(tb.DUMMY_CASTER, GetUnitY(targ))
IssueTargetOrder(tb.DUMMY_CASTER, tb.DUMMY_ABIL_ORDER, targ)
end
Initializer.register(function()
tb.DUMMY_CASTER = DummyUtils.request()
tb.GROUP = Group()
UnitAddAbility(tb.DUMMY_CASTER, tb.DUMMY_ABIL_ID)
UnitMakeAbilityPermanent(tb.DUMMY_CASTER, true, tb.DUMMY_ABIL_ID)
tb.PHASE_DURATION = BlzGetAbilityRealLevelField(BlzGetUnitAbility(tb.DUMMY_CASTER, tb.DUMMY_ABIL_ID),
ABILITY_RLF_DURATION_NORMAL, 0)
end)
local function unshare_vision(caster)
local main_grp = tb.target_grp[caster]
local visual_grp = tb.target_vis_grp[caster]
ForGroup(visual_grp, function()
local target = GetEnumUnit()
GroupRemoveUnit(visual_grp, target)
BlzUnitDisableAbility(caster, tb.GHOST_ABIL_ID, false, false)
end)
DestroyTimer(tb.timer_tb[caster])
tb.timer_tb[caster] = nil
end
tb.ghost_list = ClassTimer:create(tb.PHASE_INTERVAL, function(unit)
local main_grp = tb.target_grp[unit]
local visual_grp = tb.target_vis_grp[unit]
local reveal_grp = tb.target_rev_grp[unit]
local active = tb.is_active(unit)
local alive = UnitAlive(unit)
if alive then
GroupEnumUnitsInRange(tb.GROUP, GetUnitX(unit), GetUnitY(unit), tb.REVEAL_RANGE, nil)
GroupRemoveUnit(tb.GROUP, unit)
ForGroup(tb.GROUP, function()
local uu = GetEnumUnit()
if tb.filter_target(uu, unit) then
if active and IsUnitInRange(uu, unit, tb.PHASE_RANGE) and not IsUnitInGroup(uu, main_grp) then
tb.slow_target(uu)
GroupAddUnit(main_grp, uu)
doAfter(tb.PHASE_DURATION, function(target, unit)
GroupRemoveUnit(main_grp, target)
end, uu, unit)
if BlzGroupGetSize(main_grp) >= tb.MAX_FOR_REVEAL then
if not tb.timer_tb[unit] then
tb.timer_tb[unit] = CreateTimer()
TimerStart(tb.timer_tb[unit], tb.REVEAL_DURATION, false, function()
unshare_vision(unit)
end)
ForGroup(main_grp, function()
local target = GetEnumUnit()
GroupAddUnit(visual_grp, target)
BlzUnitDisableAbility(unit, tb.GHOST_ABIL_ID, true, false)
end)
else
PauseTimer(tb.timer_tb[unit])
TimerStart(tb.timer_tb[unit], tb.REVEAL_DURATION, false, function()
unshare_vision(unit)
end)
GroupAddUnit(visual_grp, uu)
BlzUnitDisableAbility(unit, tb.GHOST_ABIL_ID, true, false)
end
end
end
if not IsUnitInGroup(uu, reveal_grp) then
GroupAddUnit(reveal_grp, uu)
end
end
end)
end
ForGroup(reveal_grp, function()
local uu = GetEnumUnit()
if not IsUnitInRange(uu, unit, tb.REVEAL_RANGE) or not alive then
GroupRemoveUnit(reveal_grp, uu)
end
end)
if BlzGroupGetSize(reveal_grp) > 0 then
if tb.target_rev_time[unit] < tb.REVEAL_DELAY then
tb.target_rev_time[unit] = tb.target_rev_time[unit] + tb.PHASE_INTERVAL
if tb.target_rev_time[unit] >= tb.REVEAL_DELAY then
if tb.target_reveal_fx[unit] then
DestroyEffect(tb.target_reveal_fx[unit])
tb.target_reveal_fx[unit] = nil
end
if not tb.target_reveal_timer[unit] then
tb.target_reveal_timer[unit] = CreateTimer()
BlzUnitDisableAbility(unit, tb.GHOST_ABIL_ID, true, false)
else
PauseTimer(tb.target_reveal_timer[unit])
end
elseif tb.target_rev_time[unit] >= tb.REVEAL_PRELUDE and not tb.target_reveal_fx[unit] then
tb.target_reveal_fx[unit] = AddSpecialEffectTarget(tb.REVEAL_NOTIFICATION, unit, "overhead")
end
end
else
if not tb.target_reveal_timer[unit] then
tb.target_rev_time[unit] = 0.
if tb.target_reveal_fx[unit] then
DestroyEffect(tb.target_reveal_fx[unit])
tb.target_reveal_fx[unit] = nil
end
else
if not tb.target_rev_timer_started[unit] then
tb.target_rev_timer_started[unit] = true
TimerStart(tb.target_reveal_timer[unit], tb.REVEAL_DURATION, false, function()
DestroyTimer(tb.target_reveal_timer[unit])
tb.target_reveal_timer[unit] = nil
tb.target_rev_timer_started[unit] = nil
BlzUnitDisableAbility(unit, tb.GHOST_ABIL_ID, false, false)
end)
end
end
end
end)
do
local function UpdateVisuals(unit, vis)
if (GetUnitTypeId(unit) == 0) or UnitAlive(unit) then
PauseTimer(GetExpiredTimer())
return unit, vis
end
SetUnitVertexColor(unit, 255, 255, 255, vis)
vis = math.max(vis - 16, 0)
return unit, vis
end
UnitState.register("DEATH_EVENT", function()
local unit = UnitState.eventUnit
local vis = 128
if tb.ghost_list:is_in(unit) then
doRepeat(0.25, function(unit)
unit, vis = UpdateVisuals(unit, vis)
end, unit)
end
end)
end
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
if GetUnitAbilityLevel(unit, tb.ABIL_ID) ~= 0 then
SetUnitPathing(unit, false)
tb.target_grp[unit] = CreateGroup()
tb.target_vis_grp[unit] = CreateGroup()
tb.target_rev_grp[unit] = CreateGroup()
tb.target_rev_time[unit] = 0.00
tb.ghost_list:include(unit)
end
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
if tb.ghost_list:is_in(unit) then
tb.ghost_list:remove(unit)
DestroyGroup(tb.target_grp[unit])
DestroyGroup(tb.target_vis_grp[unit])
DestroyGroup(tb.target_rev_grp[unit])
DestroyTimer(tb.target_reveal_timer[unit])
DestroyEffect(tb.target_reveal_fx[unit])
tb.timer_tb[unit] = nil
tb.target_grp[unit] = nil
tb.target_vis_grp[unit] = nil
tb.target_rev_grp[unit] = nil
tb.target_rev_time[unit] = nil
tb.target_reveal_timer[unit] = nil
tb.target_rev_timer_started[unit] = nil
tb.target_reveal_fx[unit] = nil
end
end)
end
do
local tb = {
ABILITY_LIST = {FourCC("A009"), FourCC("A00E"), FourCC("A00F")},
RESEARCH_ID = FourCC("R002"),
SHADE_ID = FourCC("u002"),
ORDER_DEPTH_COMP = 2
}
function tb.modify(whichunit, prev_level, cur_level)
prev_level, cur_level = prev_level + 1, cur_level + 1
if UnitAddAbility(whichunit, tb.ABILITY_LIST[cur_level]) then
UnitMakeAbilityPermanent(whichunit, true, tb.ABILITY_LIST[cur_level])
else
BlzUnitDisableAbility(whichunit, tb.ABILITY_LIST[cur_level], false, false)
end
BlzUnitDisableAbility(whichunit, tb.ABILITY_LIST[prev_level], true, true)
doAfter(0.00, function()
IssueImmediateOrder(whichunit, "resumeharvesting")
end)
end
ResearchUtils.register_research(tb.RESEARCH_ID, tb.modify, tb.SHADE_ID)
end
do
local tb = {
ABILITY_ID = FourCC("A00C"),
COIN_EFFECT = "Abilities\\Spells\\Items\\ResourceItems\\ResourceEffectTarget.mdl",
ATTACH_EFFECT = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl",
STRIKE_EFFECT = "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl",
DEFAULT_GOLD = 250,
DEFAULT_LUMBER = 200,
MAX_TARGETS = 3,
DMG_PER_TARGET = 25,
RUSH_SPEED = 10000,
RUSH_UPDATE = 1/64.,
RUSH_AOE = 350,
rush_list = LinkedList:create(),
}
tb.rush_list = ClassTimer:create(tb.RUSH_UPDATE)
tb.RUSH_SPEED = tb.RUSH_SPEED*tb.RUSH_UPDATE
tb.RUSH_SPEED_SQ = tb.RUSH_SPEED*tb.RUSH_SPEED
function tb.filter(caster, target)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster)) and
not IsUnitType(target, UNIT_TYPE_FLYING) and
not IsUnitType(target, UNIT_TYPE_STRUCTURE)
end
tb.rush_list:on_callback(function(t_data)
tb.owner = GetOwningPlayer(t_data.caster)
tb.cx = GetUnitX(t_data.caster)
tb.cy = GetUnitY(t_data.caster)
while GetUnitTypeId(t_data.grp:first()) == 0 and t_data.grp.size > 0 do
t_data.grp:unshift()
end
if t_data.grp.size > 0 then
tb.target = t_data.grp:first()
tb.tx = GetUnitX(tb.target)
tb.ty = GetUnitY(tb.target)
else
tb.tx = t_data.tx
tb.ty = t_data.ty
end
tb.dist = (tb.cx-tb.tx)*(tb.cx-tb.tx) + (tb.cy-tb.ty)*(tb.cy-tb.ty)
tb.theta = Atan2(tb.ty - tb.cy, tb.tx - tb.cx)
if tb.dist < tb.RUSH_SPEED_SQ then
SetUnitX(t_data.caster, tb.tx)
SetUnitY(t_data.caster, tb.ty)
if t_data.grp.size > 0 then
t_data.grp:remove(tb.target)
if t_data.grp.size == 0 then
t_data.tx = t_data.cx
t_data.ty = t_data.cy
end
SetUnitTimeScale(t_data.caster, 5)
SetUnitAnimationByIndex(t_data.caster, 16)
doAfter(0.20, function(caster)
SetUnitTimeScale(caster, 4)
SetUnitAnimationByIndex(caster, 17)
end, t_data.caster)
UnitDamageTargetPure(t_data.caster, tb.target, tb.DMG_PER_TARGET)
DestroyEffect( AddSpecialEffectTarget(tb.STRIKE_EFFECT, tb.target, "chest"))
DestroyEffect( AddSpecialEffect(tb.COIN_EFFECT, tb.tx, tb.ty))
tb.gold_worth = math.floor(tb.DEFAULT_GOLD*tb.DMG_PER_TARGET/GetUnitMaxHP(tb.target))
tb.lumb_worth = math.floor(tb.DEFAULT_LUMBER*tb.DMG_PER_TARGET/GetUnitMaxHP(tb.target))
SetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_GOLD) + tb.gold_worth)
SetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(tb.owner, PLAYER_STATE_RESOURCE_LUMBER) + tb.lumb_worth)
else
SetUnitX(t_data.caster, tb.tx)
SetUnitY(t_data.caster, tb.ty)
SetUnitTimeScale(t_data.caster, 1)
DestroyEffect(t_data.attach)
SetUnitInvulnerable(t_data.caster, false)
BlzPauseUnitEx(t_data.caster, false)
tb.rush_list:remove(t_data)
end
else
SetUnitX(t_data.caster, tb.cx + tb.RUSH_SPEED*math.cos(tb.theta))
SetUnitY(t_data.caster, tb.cy + tb.RUSH_SPEED*math.sin(tb.theta))
SetUnitFacingTimed(t_data.caster, tb.theta*180/math.pi, 0.15)
end
end)
Initializer.register(function()
tb.RUSH_TICKER = CreateTimer()
tb.ENUM_GROUP = Group()
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.ABILITY_ID, function()
local t_data = {caster = GetTriggerUnit(), tx = GetSpellTargetX(), ty = GetSpellTargetY(), grp = LinkedList:create()}
t_data.cx, t_data.cy = GetUnitX(t_data.caster), GetUnitY(t_data.caster)
GroupEnumUnitsInRange(tb.ENUM_GROUP, t_data.tx, t_data.ty, tb.RUSH_AOE, nil)
GroupRemoveUnit(tb.ENUM_GROUP, t_data.caster)
local enum_unit = FirstOfGroup(tb.ENUM_GROUP)
while enum_unit do
if tb.filter(t_data.caster, enum_unit) and t_data.grp.size < tb.MAX_TARGETS then
local rand = math.random()
if rand <= 0.5 or BlzGroupGetSize(tb.ENUM_GROUP) <= tb.MAX_TARGETS then
t_data.grp:insert(enum_unit)
end
end
if t_data.grp.size >= tb.MAX_TARGETS then break end
GroupRemoveUnit(tb.ENUM_GROUP, enum_unit)
enum_unit = FirstOfGroup(tb.ENUM_GROUP)
end
doAfter(0.00, function()
if t_data.grp.size ~= 0 then
t_data.attach = AddSpecialEffectTarget(tb.ATTACH_EFFECT, t_data.caster, "left hand")
SetUnitInvulnerable(t_data.caster, true)
BlzPauseUnitEx(t_data.caster, true)
SetUnitAnimationByIndex(t_data.caster, 16)
SetUnitTimeScale(t_data.caster, 4)
tb.rush_list:include(t_data)
else
DestroyEffect(AddSpecialEffectTarget(tb.ATTACH_EFFECT, t_data.caster, "left hand"))
BlzEndUnitAbilityCooldown(t_data.caster, tb.ABILITY_ID)
end
end)
end)
end
do
local tb = {
ABILITY_ID = FourCC("A00D"),
activeUnits = LinkedList:create(),
listPointer = {},
cautionMsg = "|n|n|cffffcc00Can only be used in day time.|r",
}
Initializer.register(function()
tb.defAbilityDesc = BlzGetAbilityExtendedTooltip(tb.ABILITY_ID, 0)
BlzSetAbilityExtendedTooltip(tb.ABILITY_ID, tb.defAbilityDesc .. tb.cautionMsg, 0)
BlzSetAbilityExtendedTooltip(tb.ABILITY_ID, tb.defAbilityDesc, 0)
end)
UnitDex.register("ENTER_EVENT", function()
if GetUnitAbilityLevel(UnitDex.eventUnit, tb.ABILITY_ID) ~= 0 then
tb.listPointer[UnitDex.eventUnit] = select(3, tb.activeUnits:insert(UnitDex.eventUnit))
if TimeOfDay.status == "NIGHT_TIME" then
BlzUnitDisableAbility(UnitDex.eventUnit, tb.ABILITY_ID, true, false)
end
end
end)
UnitDex.register("LEAVE_EVENT", function()
if tb.listPointer[UnitDex.eventUnit] then
tb.activeUnits:erase(tb.listPointer[UnitDex.eventUnit])
tb.listPointer[UnitDex.eventUnit] = nil
end
end)
TimeOfDay.register("NIGHT_TIME", function()
BlzSetAbilityExtendedTooltip(tb.ABILITY_ID, tb.defAbilityDesc .. tb.cautionMsg, 0)
for unit in tb.activeUnits:iterator() do
BlzUnitDisableAbility(unit, tb.ABILITY_ID, true, false)
end
end)
TimeOfDay.register("DAY_TIME", function()
BlzSetAbilityExtendedTooltip(tb.ABILITY_ID, tb.defAbilityDesc, 0)
for unit in tb.activeUnits:iterator() do
BlzUnitDisableAbility(unit, tb.ABILITY_ID, false, false)
end
end)
end
do
local tb = {
ABIL_ID = FourCC("A00H"),
BONUS_DAMAGE_ID = FourCC("A00J"),
MAX_DEATH_FOR_CHANGE_OWNER = 12,
DEATH_COUNTER_COOLDOWN = 20,
DEATH_AOE_RANGE = 600,
HP_RATIO_FOR_INSTANT_KILL = 0.07,
BAR_HEIGHT = 200.00,
BAR_ADJUST_TIME = 2.00,
BAR_DISPLAY_CRITICAL = 8, -- The number of deaths before the bar starts to highlight itself.
BAR_OSCILLATE_PERIOD = 1.00,
BONUS_DAMAGE_PER_DEFECT = 5,
BONUS_DAMAGE_STACKS = -1,
INSTA_KILL_MODEL = "war3mapImported\\Coup de Grace Purple.mdx",
DEFECT_MODEL = "war3mapImported\\DarkOrb.mdx",
activeList = LinkedList:create(),
}
tb.data = {}
function tb.filterTarget(target, attacker)
return UnitAlive(target, attacker) and not IsUnitType(target, UNIT_TYPE_HERO)
end
function tb.filterCheckForBetrayal(acolyte, dead)
return GetOwningPlayer(acolyte) == GetOwningPlayer(dead)
end
--[[Recode everything from scratch.]]
Initializer.register(function()
local GRP = Group()
local bonus = tb.BONUS_DAMAGE_PER_DEFECT
local maxc = tb.MAX_DEATH_FOR_CHANGE_OWNER
local function onWarning(unit, count)
tb.data[unit].bar:set_progress(tb.data[unit].betray_art/maxc)
tb.data[unit].bar:progress_to(count/maxc, tb.BAR_ADJUST_TIME, false, BezierEase.easeInOut)
tb.data[unit].bar:on_progress_end(function(bar, prog)
tb.data[unit].betray_art = count
end)
end
local function onBetray(unit, newOwner)
tb.data[unit].betray_art = 0
tb.data[unit].bar:set_progress(0)
tb.data[unit].bar:progress_to(0, tb.BAR_ADJUST_TIME, false, BezierEase.easeInOut)
tb.data[unit].bar:show(false)
tb.data[unit].bar:show_for_player(false, GetOwningPlayer(unit))
tb.data[unit].bar:show_for_player(true, newOwner)
DestroyEffect(AddSpecialEffectTarget(tb.DEFECT_MODEL, unit, "overhead"), 0.25)
end
local function onInstantKill(unit, killer)
local theta = math.atan(GetUnitY(unit) - GetUnitY(killer), GetUnitX(unit) - GetUnitX(killer))
if not IsUnitType(unit, UNIT_TYPE_MECHANICAL) then
local fx = AddSpecialEffect(tb.INSTA_KILL_MODEL, GetUnitX(unit), GetUnitY(unit))
BlzSetSpecialEffectYaw(fx, theta)
BlzSetSpecialEffectHeight(fx, GetUnitFlyHeight(unit) + 25.00)
DestroyEffect(fx, 2.0)
end
end
UnitState.register("DEATH_EVENT", function()
local dead, killer = UnitState.eventUnit, UnitState.eventKiller
local deadOwner, killerOwner = GetOwningPlayer(dead), GetOwningPlayer(killer)
local cx, cy, tx, ty
cx, cy = GetUnitX(dead), GetUnitY(dead)
if tb.data[dead] then
local prev_num = tb.data[dead].list.size
for timer, tpointer in tb.data[dead].list:iterator() do
PauseTimer(timer)
DestroyTimer(timer)
tb.data[dead].list:erase(tpointer)
end
onWarning(dead, 0)
end
-- If the unit was killed by a unit owned by the same player, do nothing
if not tb.filterCheckForBetrayal(killer, dead) then
GroupEnumUnitsInRange(GRP, cx, cy, tb.DEATH_AOE_RANGE, nil)
ForGroup(GRP, function()
local unit = GetEnumUnit()
if tb.data[unit] and UnitAlive(unit) and tb.filterCheckForBetrayal(unit, dead) then
if tb.data[unit].list.size < maxc - 1 then
-- Add a timer
local timer = CreateTimer()
local tpointer = select(3, tb.data[unit].list:insert(timer))
TimerStart(timer, tb.DEATH_COUNTER_COOLDOWN, false, function()
PauseTimer(timer)
DestroyTimer(timer)
tb.data[unit].list:erase(tpointer)
onWarning(unit, tb.data[unit].list.size)
end)
onWarning(unit, tb.data[unit].list.size)
else
local prev_num = tb.data[dead].list.size
for timer, tpointer in tb.data[unit].list:iterator() do
PauseTimer(timer)
DestroyTimer(timer)
tb.data[unit].list:erase(tpointer)
end
onBetray(unit, killerOwner)
-- Notify the player
-- Change ownership
SetUnitOwner(unit, killerOwner, true)
tb.data[unit].betray = tb.data[unit].betray + 1
local abil = BlzGetUnitAbility(unit, tb.BONUS_DAMAGE_ID)
if not abil then
UnitAddAbility(unit, tb.BONUS_DAMAGE_ID)
UnitMakeAbilityPermanent(unit, true, tb.BONUS_DAMAGE_ID)
else
IncUnitAbilityLevel(unit, tb.BONUS_DAMAGE_ID)
BlzSetAbilityIntegerLevelField(abil, ABILITY_ILF_ATTACK_BONUS, 0,
tb.data[unit].betray*bonus)
DecUnitAbilityLevel(unit, tb.BONUS_DAMAGE_ID)
end
end
end
end)
end
end)
DamageEvent.register_modifier("MODIFIER_EVENT_DELTA", function()
local unit = DamageEvent.current.source
local targ = DamageEvent.current.target
if tb.data[unit] then
if tb.filterTarget(targ, unit) then
local max_hp = GetUnitMaxHP(targ)
local hp_ratio = GetWidgetLife(targ)/max_hp
if hp_ratio <= tb.HP_RATIO_FOR_INSTANT_KILL then
UnitDamageTargetPure(unit, targ, GetWidgetLife(targ))
onInstantKill(targ, unit)
end
end
end
end)
end)
do
local critical = tb.BAR_DISPLAY_CRITICAL/tb.MAX_DEATH_FOR_CHANGE_OWNER
local period_cache = cachetable(0, 2*math.pi, EffectUtils.INTERVAL*2*math.pi/tb.BAR_OSCILLATE_PERIOD, math.cos)
local function OnBarUpdate(bar, unit)
local result = bar:get_progress()
if result > 0 then
if not bar:visible() then bar:show(true); end
else
if bar:visible() then bar:show(false); end
end
if result >= critical then
if tb.data[unit].oscillate_art == 0 then
tb.data[unit].oscillate_art = 2
else
tb.data[unit].oscillate_art = period_cache.next[tb.data[unit].oscillate_art]
end
local gb_color = 127.5 - 127.5*period_cache[tb.data[unit].oscillate_art]
bar:set_color(255, math.floor(gb_color), math.floor(gb_color))
else
if tb.data[unit].oscillate_art > 1 then
tb.data[unit].oscillate_art = period_cache.next[tb.data[unit].oscillate_art]
local gb_color = 127.5 - 127.5*period_cache[tb.data[unit].oscillate_art]
bar:set_color(255, math.floor(gb_color), math.floor(gb_color))
if tb.data[unit].oscillate_art == 1 then
tb.data[unit].oscillate_art = 0
end
end
end
end
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
if GetUnitAbilityLevel(unit, tb.ABIL_ID) ~= 0 then
tb.data[unit] = {}
tb.data[unit].pointer = select(3, tb.activeList:insert(unit))
tb.data[unit].bar = GameBarDisplay:create()
tb.data[unit].list = LinkedList:create()
tb.data[unit].betray = 0
tb.data[unit].betray_art = 0
tb.data[unit].oscillate_art = 0
tb.data[unit].bar:set_color(255, 0, 0)
tb.data[unit].bar:attach_to_unit(unit, tb.BAR_HEIGHT)
tb.data[unit].bar:show(false)
tb.data[unit].bar:show_for_player(true, GetOwningPlayer(unit))
tb.data[unit].bar:on_update(OnBarUpdate)
end
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
if tb.data[unit] then
tb.activeList:erase(tb.data[unit].pointer)
tb.data[unit].list:destroy()
tb.data[unit] = nil
end
end)
end
end
do
local dagger = protected_t()
local bez = BezierEase.linear
dagger.custom = {
ABILITY_ID = FourCC("A026"),
BASE_DAMAGE = 50,
MAX_DAMAGE = 125,
MISSING_HP_DAMAGE = 0.15,
DAGGER_RADIUS = 75,
DAGGER_SPEED = 1200,
DAGGER_COUNT = 5,
DAGGER_TRANSITION = 0.25,
RETREAT_DIST = 400,
RETREAT_SPEED = 800,
TAG_OFFSET = 50.,
DAGGER_MODEL = "war3mapImported\\DaggerOfTime.mdx",
BLEED_MODEL = "Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl",
DASH_MODEL = "war3mapImported\\Windwalk.mdx",
ANIM = "spell slam"
}
dagger.custom.RETREAT_TIME = dagger.custom.RETREAT_DIST/dagger.custom.RETREAT_SPEED
dagger.landed = false
Initializer("USER", function()
dagger.GROUP = Group()
dagger.TEMP_TB = {}
dagger.custom.DAGGER_SPREAD = tonumber(ObjectReader.read(dagger.custom.ABILITY_ID, "Area1", ",."))
end)
dagger.move_list = ClassTimer:create(EffectUtils.INTERVAL)
dagger.hide_list = ClassTimer:create(dagger.move_list._INTERVAL)
dagger.custom.RETREAT_SPEED = dagger.custom.RETREAT_SPEED*dagger.move_list._INTERVAL
function dagger.filter_target(target, caster)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster))
and not IsUnitType(target, UNIT_TYPE_FLYING)
and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
end
function dagger.filter_cap(target)
return IsUnitType(target, UNIT_TYPE_HERO)
end
function dagger.is_walkable(x, y)
if not IsTerrainWalkable(x, y) then
return not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
end
return true
end
function dagger.add_effect(target)
DestroyEffect( AddSpecialEffectTarget(dagger.custom.BLEED_MODEL, target, "chest"), 0.5)
end
function dagger.add_texttag(target, dmg)
local tag = CreateTextTag()
SetTextTagPermanent(tag, false)
SetTextTagColor(tag, 255, 0, 0, 255)
SetTextTagLifespan(tag, 3.00)
SetTextTagFadepoint(tag, 2.00)
SetTextTagVelocity(tag, 0, TextTagSpeed2Velocity(60.))
if not dagger.TEMP_TB[target] then
dagger.TEMP_TB[target] = 1
SetTextTagPosUnit(tag, target, 50.)
doAfter(0.5, function()
dagger.TEMP_TB[target] = nil
end)
else
SetTextTagPosUnit(tag, target, 50*(1 + dagger.TEMP_TB[target]))
dagger.TEMP_TB[target] = dagger.TEMP_TB[target] + 1
end
SetTextTagText(tag, tostring(math.floor(dmg + 0.5)), TextTagSize2Height(10.5))
end
function dagger.damage_target(target, caster)
local max_hp = GetUnitMaxHP(target)
local bonus_dmg = (max_hp - GetWidgetLife(target))*dagger.custom.MISSING_HP_DAMAGE
+ dagger.custom.BASE_DAMAGE
if dagger.filter_cap(target) then
bonus_dmg = math.min(bonus_dmg, dagger.custom.MAX_DAMAGE)
end
dagger.add_texttag(target, bonus_dmg)
dagger.add_effect(target)
UnitDamageTargetPure(caster, target, bonus_dmg)
end
dagger.hide_list:on_callback(function(tb)
local delta; tb.time = tb.time + dagger.hide_list._INTERVAL; delta = tb.time/dagger.custom.DAGGER_TRANSITION
if delta < 1 then
BlzSetSpecialEffectAlpha(tb.point.missile.effect, math.floor(255*(1 - bez[delta])))
else
ShowEffect(tb.point.missile.effect, false)
dagger.hide_list:remove(tb)
tb.point:destroy()
end
end)
local function new_update_func(point, unit)
return function(theta)
GroupEnumUnitsInRange(dagger.GROUP, point.missile:get_x(), point.missile:get_y(),
dagger.custom.DAGGER_RADIUS, nil)
while true do
local enum_unit = FirstOfGroup(dagger.GROUP)
if not enum_unit then break end
if dagger.filter_target(enum_unit, unit) then
dagger.damage_target(enum_unit, unit)
dagger.landed = true
point:land()
dagger.landed = false
break
end
GroupRemoveUnit(dagger.GROUP, enum_unit)
end
return theta
end
end
local function new_land_func(point, unit)
return function()
if not dagger.landed then
GroupEnumUnitsInRange(dagger.GROUP, point.missile:get_x(), point.missile:get_y(),
dagger.custom.DAGGER_RADIUS, nil)
while true do
local enum_unit = FirstOfGroup(dagger.GROUP)
if not enum_unit then break end
if dagger.filter_target(enum_unit, unit) then
dagger.damage_target(enum_unit, unit)
break
end
GroupRemoveUnit(dagger.GROUP, enum_unit)
end
end
dagger.hide_list:include({point = point, time = 0.})
end
end
local function generate_daggers(unit, theta, tx, ty)
local cx, cy = GetUnitX(unit), GetUnitY(unit)
if dagger.custom.DAGGER_COUNT == 1 then
local point = Missile.Point:create(dagger.custom.DAGGER_MODEL, cx, cy)
point.missile:set_speed(dagger.custom.DAGGER_SPEED)
point:set_target_pos(tx, ty)
point:on_land(new_land_func(point, unit))
point:on_update(new_update_func(point, unit))
point:launch()
else
local off = 2*dagger.custom.DAGGER_SPREAD/dagger.custom.DAGGER_COUNT
local co,si = math.cos(theta), math.sin(theta)
tx, ty = tx + dagger.custom.DAGGER_SPREAD*math.cos(theta),
ty + dagger.custom.DAGGER_SPREAD*math.sin(theta)
if math.fmod(dagger.custom.DAGGER_COUNT, 2) == 0 then
-- Relocate the starting point in case DAGGER_COUNT is even.
tx, ty = tx - dagger.custom.DAGGER_SPREAD/dagger.custom.DAGGER_COUNT*co,
ty - dagger.custom.DAGGER_SPREAD/dagger.custom.DAGGER_COUNT*si
else
off = dagger.custom.DAGGER_SPREAD/(dagger.custom.DAGGER_COUNT - 1)
end
for i = 1,dagger.custom.DAGGER_COUNT do
local point = Missile.Point:create(dagger.custom.DAGGER_MODEL, cx, cy)
point.missile:set_speed(dagger.custom.DAGGER_SPEED)
point:set_target_pos(tx, ty)
point:on_land(new_land_func(point, unit))
point:on_update(new_update_func(point, unit))
point:launch()
tx, ty = tx - off*co,
ty - off*si
end
end
end
dagger.move_list:on_callback(function(tb)
tb.time = tb.time + dagger.move_list._INTERVAL
if tb.time > dagger.custom.RETREAT_TIME then
DestroyEffect(tb.effect)
dagger.move_list:remove(tb)
elseif IsUnitType(tb.unit, UNIT_TYPE_SNARED) then
DestroyEffect(tb.effect)
dagger.move_list:remove(tb)
else
local tx, ty = GetUnitX(tb.unit) + dagger.custom.RETREAT_SPEED*math.cos(tb.theta),
GetUnitY(tb.unit) + dagger.custom.RETREAT_SPEED*math.sin(tb.theta)
if dagger.is_walkable(tx, ty) then
SetUnitX(tb.unit, tx); SetUnitY(tb.unit, ty)
end
end
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, dagger.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local tx, ty = GetSpellTargetX(), GetSpellTargetY()
local theta = math.atan(ty - GetUnitY(unit), tx - GetUnitX(unit))
local tb = {unit = unit}
tb.theta = theta - math.pi
tb.time = 0.
tb.effect = AddSpecialEffectTarget(dagger.custom.DASH_MODEL, tb.unit, "origin")
SetUnitAnimation(unit, dagger.custom.ANIM)
generate_daggers(unit, theta + math.pi/2, tx, ty)
dagger.move_list:include(tb)
end)
end
BusinessZombie = {}
do
local m_purch = protected_t()
local purch = setmetatable({}, m_purch)
local trans
BusinessZombie.PurchaseNeutralBuilding = purch
m_purch.custom = {
ABILITY_ID = FourCC("A00R"),
NEXUS_ID = FourCC("u00A"),
VALID_BUILDINGS = LinkedList(FourCC("ngol"), FourCC("ngme"), FourCC("nmr5")),
DEFAULT_GOLD_COST = 500,
DEFAULT_LUMBER_COST = 200,
CHANNEL_EFFECT = "war3mapImported\\Gold.mdx",
TELEPORT_EFFECT = "war3mapImported\\Void Teleport Yellow To.mdx",
COIN_EFFECT = "Abilities\\Spells\\Items\\ResourceItems\\ResourceEffectTarget.mdl",
SOUL_EFFECT = "war3mapImported\\Fountain of Souls.mdx",
MISSILE_EFFECT = "war3mapImported\\Psionic Shot Yellow.mdx",
SUCCESS_EFFECT = "Abilities\\Spells\\Other\\Levelup\\Levelupcaster.mdx",
LIGHT_EXTEND_DUR = 0.50,
LIGHT_DECAY_DUR = 2.00,
CHANNEL_PRELUDE = 1.00,
MISSILE_SPEED = 1500.,
OFFSET_RAD = 3*math.pi/2,
OFFSET_DIST = 100,
DIRECTION_CHECK = 4,
}
m_purch.owners = {}
m_purch.ownership = {}
m_purch.castpointer = {}
m_purch.custom.DIRECTION_CHECK_RAD = 2*math.pi/(m_purch.custom.DIRECTION_CHECK)
Initializer.register(function()
m_purch.custom.CHANNEL_DUR = tonumber(ObjectReader.read(m_purch.custom.ABILITY_ID, "DataA1", ",."))
if CorporateNexus then
trans = CorporateNexus.TransferOwnership
function m_purch.transferOwner(structure, target)
local subtarg = m_purch.owners[structure]
local tb = m_purch.ownership[subtarg]
local cOwner, tOwner = GetOwningPlayer(structure), GetOwningPlayer(target)
tb.owner = target
SetUnitOwner(subtarg, tOwner, true)
DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, GetPlayerName(cOwner) .. " has transferred ownership of "
.. GetUnitName(subtarg) .. " to " .. GetPlayerName(tOwner) .. ".")
trans.removeAbility(structure)
end
end
end)
function m_purch.validBuilding(target)
return GetOwningPlayer(target) == Player(PLAYER_NEUTRAL_PASSIVE) and
m_purch.custom.VALID_BUILDINGS:is_in(GetUnitTypeId(target)) and
not m_purch.ownership[target]
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_CHANNEL, m_purch.custom.ABILITY_ID, function()
local caster, targ = GetTriggerUnit(), GetSpellTargetUnit()
if not m_purch.validBuilding(targ) then
GameError(GetOwningPlayer(caster), GetUnitName(targ) .. " is currently under purchase.")
PauseUnit(caster, true)
IssueImmediateOrderById(caster, 851972) -- stop
PauseUnit(caster, false)
return;
end
local tb = {owner = caster, building = targ, purchased = false, prev_owner = GetOwningPlayer(targ)}
local cx, cy, tx, ty = GetUnitX(caster), GetUnitY(caster), GetUnitX(targ), GetUnitY(targ)
tb.gold_cost, tb.wood_cost = GetUnitGoldCost(GetUnitTypeId(targ)), GetUnitWoodCost(GetUnitTypeId(targ))
if tb.gold_cost == 0 and tb.wood_cost == 0 then
tb.gold_cost, tb.wood_cost = m_purch.custom.DEFAULT_GOLD_COST, m_purch.custom.DEFAULT_LUMBER_COST
end
tb.timer = CreateTimer()
tb.soul_effect = AddSpecialEffect(m_purch.custom.SOUL_EFFECT, cx, cy)
SetEffectScale(tb.soul_effect, 0.25)
tb.lightning_effect = LightningUtils:add("HWSB", true, cx, cy, cx, cy)
tb.lightning_effect:move_to(m_purch.custom.LIGHT_EXTEND_DUR, true, cx, cy, tx, ty)
tb.lightning_effect:on_event(function(lightning)
-- Add something here later.
local fx = AddSpecialEffect(m_purch.custom.TELEPORT_EFFECT, tx, ty)
tb.teleport_fx = fx
tb.teleport_timer = CreateTimer()
SetEffectScale(fx, 2.0)
TimerStart(tb.teleport_timer, m_purch.custom.CHANNEL_DUR - m_purch.custom.LIGHT_EXTEND_DUR
- m_purch.custom.CHANNEL_PRELUDE, false, function()
DestroyEffect(tb.teleport_fx)
tb.teleport_timer = nil
tb.teleport_fx = nil
end)
end)
m_purch.ownership[targ] = tb
m_purch.castpointer[caster] = targ
local func = function()
DestroyEffect( AddSpecialEffect(m_purch.custom.CHANNEL_EFFECT,
cx, cy), 1.00)
DestroyEffect( AddSpecialEffectTarget(m_purch.custom.COIN_EFFECT,
tb.owner, "chest"), 2.00)
end
func()
TimerStart(tb.timer, 2.00, true, function()
local point = Missile.Point:create(m_purch.custom.MISSILE_EFFECT, tx, ty)
point.missile:set_speed(m_purch.custom.MISSILE_SPEED)
point.missile:set_scale(3.2)
point:set_target_pos(cx, cy)
point:on_land(function()
func()
point:destroy()
end)
point:launch()
end)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_FINISH, m_purch.custom.ABILITY_ID, function()
local caster, targ; caster = GetTriggerUnit(); targ = m_purch.castpointer[caster]
local tb = m_purch.ownership[targ]
local cx, cy, tx, ty = GetUnitX(caster), GetUnitY(caster), GetUnitX(targ), GetUnitY(targ)
local cOwner = GetOwningPlayer(caster)
PauseTimer(tb.timer)
DestroyTimer(tb.timer)
DestroyEffect(tb.soul_effect)
tb.lightning_effect:apply_color(1.75, 0)
tb.lightning_effect:add_callback("color", "onEvent", function(lightning)
doAfter(0.00, function() lightning:destroy() end)
end)
local fx = AddSpecialEffect(m_purch.custom.SUCCESS_EFFECT, tx, ty)
SetEffectScale(fx, 3.00)
DestroyEffect(fx, 1.00)
-- Charge the player the normal cost of buying out the building
local structure = CreateUnit(cOwner, m_purch.custom.NEXUS_ID,
cx + m_purch.custom.OFFSET_DIST*math.cos(m_purch.custom.OFFSET_RAD),
cy + m_purch.custom.OFFSET_DIST*math.sin(m_purch.custom.OFFSET_RAD),
bj_UNIT_FACING )
if trans then
trans.disableAbility(structure)
end
tb.owner = structure
m_purch.owners[structure] = targ
PauseUnit(structure, true)
SetUnitTimeScale(structure, 6.00)
SetUnitAnimation(structure, "birth")
SetUnitPosition(caster, GetUnitX(caster), GetUnitY(caster))
doAfter(10.00, function(tb)
PauseUnit(structure, false)
SetUnitTimeScale(structure, 1.00)
QueueUnitAnimation(structure, "stand")
local cx, cy = GetUnitX(structure), GetUnitY(structure)
tb.lightning_art = LightningUtils:add("HWPB", true, cx, cy, cx, cy)
tb.lightning_art:move_to(0.25, true, cx, cy, tx, ty)
end, tb)
-- Notify the system to add some form of debt.
local debt = DebtSystem:add_debt_item(cOwner, tb.gold_cost, tb.wood_cost)
debt:on_forgive(function(debt)
-- Enable a sub-ability.
if trans then
trans.enableAbility(structure)
end
end)
tb.lightning_effect = nil
tb.timer = nil
tb.soul_effect = nil
m_purch.castpointer[caster] = nil
-- Broadcast to the players that one of them has already purchased the building.
SetUnitOwner(targ, cOwner, true)
DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, GetPlayerName(cOwner) .. " has purchased " .. GetUnitName(targ) .. ".")
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, m_purch.custom.ABILITY_ID, function()
local caster = GetTriggerUnit()
local targ, tb
if m_purch.castpointer[caster] then
targ = m_purch.castpointer[caster]
tb = m_purch.ownership[targ]
else
return
end
if not tb.purchased then
PauseTimer(tb.timer)
DestroyTimer(tb.timer)
DestroyEffect(tb.soul_effect)
tb.lightning_effect:apply_color(1.75, 0)
tb.lightning_effect:add_callback("color", "onEvent", function(lightning)
doAfter(0.00, function() lightning:destroy() end)
end)
if tb.teleport_timer then
PauseTimer(tb.teleport_timer)
TimerStart(tb.teleport_timer, 0.0, false, function()
DestroyEffect(tb.teleport_fx)
tb.teleport_timer = nil
tb.teleport_fx = nil
end)
end
tb.timer = nil
tb.owner = nil
tb.building = nil
tb.purchased = nil
tb.soul_effect = nil
tb.lightning_effect = nil
m_purch.ownership[targ] = nil
end
end)
local function on_owner_removal(structure)
if m_purch.owners[structure] then
local targ = m_purch.owners[structure]
local tb = m_purch.ownership[targ]
SetUnitOwner(targ, tb.prev_owner, true)
if tb.lightning_art then
tb.lightning_art:apply_color(2.0, 0)
tb.lightning_art:add_callback("color", "onEvent", function(lightning)
doAfter(0.00, function() lightning:destroy() end)
end)
end
tb = nil
m_purch.ownership[targ] = nil
m_purch.owners[structure] = nil
end
end
Initializer.register(function()
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function()
on_owner_removal(GetTriggerUnit())
end)
end)
UnitDex.register("LEAVE_EVENT", function()
on_owner_removal(UnitDex.eventUnit)
end)
end
BusinessZombie.SalesTalk = setmetatable({}, protected_t())
do
local sales, m_sales = BusinessZombie.SalesTalk, getmetatable(BusinessZombie.SalesTalk)
m_sales.custom = {
ABILITY_ID = FourCC("A00S"),
BORED_ID = FourCC("A00T"),
ITEM_ID = FourCC("I000"),
BORED_ORDER = "sleep",
SOUND_PATHS = {
"Units\\Creeps\\Bandit\\BanditPissed5.wav",
"Units\\Creeps\\Bandit\\BanditPissed3.wav",
"Units\\Creeps\\Bandit\\BanditPissed4.wav",
},
SOUND_PATH_DUR = {
1796,
4284,
3645,
},
CASTER_EFFECT = "war3mapImported\\AnimatedBash.mdx",
AREA_NOTIFY_EFFECT = "war3mapImported\\Wizardry Missile.mdx",
LEAVE_EFFECT = "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl",
LISTEN_EFFECT_DELAY = 3.0,
LISTEN_TICK_INTERVAL = 0.5,
LISTEN_TIMER_OFFSET = 1.0,
PERSISTENT_ORDER_TICK = 0.10,
PERSISTENT_ORDER_DUR = 3.00,
LEAVE_DIST = 2, -- This is a factor that will be multiplied to AREA_OF_EFFECT
-- upon finalization.
RANDOM_QUOTES = {
"You won't believe what happened to this guy after |nhe applied this to his foot.",
"Top 6 natural remedies to cure life disease.",
"This gal did one thing for 70 days, and you |nwon't believe what happens next",
"And this one time, at bandit camp...",
"Doctors hate him. See the trick he used to lose 21 pounds instantly."
},
RANDOM_QUOTE_INTERVAL = 3.,
RANDOM_QUOTE_FADE = 1.5,
RANDOM_QUOTE_LIFESPAN = 2.5,
RANDOM_QUOTE_OFFSET = 25.,
RANDOM_QUOTE_VELOCITY = 90.,
}
m_sales.container = {}
m_sales._LIST = ClassTimer:create(m_sales.custom.LISTEN_TICK_INTERVAL)
Initializer.register(function()
m_sales._GROUP = Group()
m_sales.custom.DUMMY_UNIT = DummyUtils.request()
UnitAddAbility(m_sales.custom.DUMMY_UNIT, m_sales.custom.BORED_ID)
m_sales.custom.AREA_OF_EFFECT = tonumber(ObjectReader.read(m_sales.custom.ABILITY_ID, "Area1", ",."))
m_sales.custom.LEAVE_DIST = m_sales.custom.AREA_OF_EFFECT * m_sales.custom.LEAVE_DIST
end)
function m_sales.addQuote(caster)
local index = math.random(1, #m_sales.custom.RANDOM_QUOTES - 1)
local tag = CreateTextTag()
local dist = math.random()*m_sales.custom.RANDOM_QUOTE_OFFSET
local rad = math.random()*2*math.pi
SetTextTagPermanent(tag, false)
SetTextTagPos(tag, GetUnitX(caster) + dist*math.cos(rad), GetUnitY(caster) + dist*math.sin(rad), 50.)
SetTextTagLifespan(tag, m_sales.custom.RANDOM_QUOTE_LIFESPAN); SetTextTagFadepoint(tag, m_sales.custom.RANDOM_QUOTE_FADE);
SetTextTagColor(tag, 127 + GetRandomInt(0, 128), 127 + GetRandomInt(0, 128), 127 + GetRandomInt(0, 128), 255)
rad, dist = math.random()*2*math.pi, m_sales.custom.RANDOM_QUOTE_VELOCITY;
SetTextTagVelocity(tag, TextTagSpeed2Velocity(dist*math.cos(rad)), TextTagSpeed2Velocity(dist*math.sin(rad)))
SetTextTagText(tag, m_sales.custom.RANDOM_QUOTES[index], TextTagSize2Height(9))
end
function m_sales.getSound(i)
i = i or math.random(1, 3)
local snd = CreateSound(m_sales.custom.SOUND_PATHS[i], false, true, true, 10, 10, "DefaultEAXON")
SetSoundParamsFromLabel(snd, "BanditPissed")
SetSoundDuration(snd, m_sales.custom.SOUND_PATH_DUR[i])
SetSoundVolume(snd, 127)
return snd
end
function m_sales.filterTarget(target, caster)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster))
and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
and not IsUnitType(target, UNIT_TYPE_MECHANICAL)
end
function m_sales.bore(target, caster)
BlzUnitDisableAbility(m_sales.custom.DUMMY_UNIT, FourCC("Amov"), true, false)
SetUnitX(m_sales.custom.DUMMY_UNIT, GetUnitX(target))
SetUnitY(m_sales.custom.DUMMY_UNIT, GetUnitY(target))
IssueTargetOrder(m_sales.custom.DUMMY_UNIT, m_sales.custom.BORED_ORDER, target)
BlzUnitDisableAbility(m_sales.custom.DUMMY_UNIT, FourCC("Amov"), false, false)
end
function m_sales.give(target, caster)
if GetUnitAbilityLevel(target, FourCC("AInv")) ~= 0 then
UnitAddItemById(target, m_sales.custom.ITEM_ID)
end
end
function m_sales.leave(target, caster)
local cx, cy = GetUnitX(caster), GetUnitY(caster)
local tb, theta = 0, math.atan(GetUnitY(target) - cy, GetUnitX(target) - cx)
local limit = m_sales.custom.PERSISTENT_ORDER_DUR/m_sales.custom.PERSISTENT_ORDER_TICK
if UnitIsSleeping(target) then UnitWakeUp(target) end
DestroyEffect(AddSpecialEffectTarget(m_sales.custom.LEAVE_EFFECT, target, "overhead"), m_sales.custom.PERSISTENT_ORDER_DUR)
doUntil(m_sales.custom.PERSISTENT_ORDER_TICK, function() return tb >= limit end, function()
IssuePointOrder(target, "move", cx + m_sales.custom.LEAVE_DIST*math.cos(theta),
cy + m_sales.custom.LEAVE_DIST*math.sin(theta))
end)
end
m_sales.effects = {
m_sales.bore,
m_sales.give,
m_sales.leave,
}
function m_sales.randomEffect(target, caster)
local i = math.floor(GetRandomReal(1.5, 3.5))
m_sales.effects[i](target, caster)
end
local function on_sound_callback()
local i = math.random(1, 3)
local snd, dur = m_sales.getSound(i), m_sales.custom.SOUND_PATH_DUR[i]/1000
local tb = GetTimerData(GetExpiredTimer())
doAfter(0.00, function()
SetSoundPosition(snd, GetUnitX(tb.caster), GetUnitY(tb.caster), 0)
StartSound(snd)
KillSoundWhenDone(snd)
end)
TimerStart(tb.sound_timer, m_sales.custom.LISTEN_TIMER_OFFSET + dur, false, on_sound_callback)
m_sales.addQuote(tb.caster)
end
m_sales._LIST:on_callback(function(unit)
local cx, cy = GetUnitX(unit), GetUnitY(unit)
local tb = m_sales.container[unit]
GroupEnumUnitsInRange(m_sales._GROUP, cx, cy, m_sales.custom.AREA_OF_EFFECT, nil)
GroupRemoveUnit(m_sales._GROUP, unit)
ForGroup(m_sales._GROUP, function()
local uu = GetEnumUnit()
if tb.unit_tb[uu] or m_sales.filterTarget(uu, unit) then
tb.unit_tb[uu] = tb.unit_tb[uu] or select(3, tb.unit_tb.list:insert(uu))
if not tb.unit_tb.done[uu] then
if not tb.unit_tb.time[uu] then
tb.unit_tb.time[uu] = m_sales.custom.LISTEN_TICK_INTERVAL
else
tb.unit_tb.time[uu] = tb.unit_tb.time[uu] + m_sales.custom.LISTEN_TICK_INTERVAL
end
if tb.unit_tb.time[uu] >= m_sales.custom.LISTEN_EFFECT_DELAY and not tb.unit_tb.done[uu] then
tb.unit_tb.done[uu] = true
m_sales.randomEffect(uu, unit)
end
end
end
end)
for uu in tb.unit_tb.list:iterator() do
if not IsUnitInRange(uu, unit, m_sales.custom.AREA_OF_EFFECT) or not UnitAlive(uu) then
tb.unit_tb.list:erase(tb.unit_tb[uu])
tb.unit_tb[uu] = nil
tb.unit_tb.time[uu] = nil
tb.unit_tb.done[uu] = nil
end
end
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_sales.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local tb = {caster_fx = AddSpecialEffectTarget(m_sales.custom.CASTER_EFFECT, unit, "overhead")}
tb.caster = unit
tb.sound_timer = CreateTimer()
tb.unit_tb = {list=LinkedList:create(), time={}, done={}}
tb.notifier = Missile.Orbit:create(m_sales.custom.AREA_NOTIFY_EFFECT, GetUnitX(unit), GetUnitY(unit))
m_sales._LIST:include(unit)
-- Sound playback setup
SetTimerData(tb.sound_timer, tb)
TimerStart(tb.sound_timer, m_sales.custom.LISTEN_TIMER_OFFSET, false, on_sound_callback)
-- Missile setup
tb.notifier:set_target(unit)
tb.notifier:set_radius(m_sales.custom.AREA_OF_EFFECT)
tb.notifier:set_revs(2.00)
tb.notifier:launch()
m_sales.container[unit] = tb
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, m_sales.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local tb = m_sales.container[unit]
m_sales.container[unit] = nil
-- Destroy sound playback timer
PauseTimer(tb.sound_timer)
DestroyTimer(tb.sound_timer)
-- Clear out all units from the list
for uu in tb.unit_tb.list:iterator() do
tb.unit_tb.list:erase(tb.unit_tb[uu])
tb.unit_tb[uu] = nil
tb.unit_tb.time[uu] = nil
tb.unit_tb.done[uu] = nil
end
tb.unit_tb.list:destroy()
tb.notifier:destroy()
-- Remove unit from the _LIST
m_sales._LIST:remove(unit)
DestroyEffect(tb.caster_fx)
end)
end
ExecutiveGargoyle = {}
do
local tb = protected_t()
tb.custom = {
NORMAL_ID = FourCC("u00B"),
TRANSFORM_ID = FourCC("u00C"),
STORE_ID = FourCC("h005"),
OFFSET_RAD = 0.,
OFFSET_DIST = 300.,
DEATH_TIME = 0.8,
DEATH_MODEL = "Objects\\Spawnmodels\\Undead\\UCancelDeath\\UCancelDeath.mdl"
}
ExecutiveGargoyle.StoreForm = setmetatable({}, tb)
tb.store_cont = {}
Initializer.register(function() tb.STORE_GROUP = CreateGroup() end)
function tb.request(unit)
local cx, cy = GetUnitX(unit), GetUnitY(unit)
local store
if BlzGroupGetSize(tb.STORE_GROUP) > 0 then
-- A store exists, get it from group
store = BlzGroupUnitAt(tb.STORE_GROUP, 0)
GroupRemoveUnit(tb.STORE_GROUP, store)
SetUnitPosition(store, cx + tb.custom.OFFSET_DIST*math.cos(tb.custom.OFFSET_RAD), cy + tb.custom.OFFSET_DIST*math.sin(tb.custom.OFFSET_RAD))
PauseUnit(store, false); ShowUnit(store, true);
SetUnitAnimation(store, "stand")
tb.store_cont[unit] = store
else
-- No store exists in group, attempt to create one
store = CreateUnit(GetOwningPlayer(unit), tb.custom.STORE_ID, cx + tb.custom.OFFSET_DIST*math.cos(tb.custom.OFFSET_RAD),
cy + tb.custom.OFFSET_DIST*math.sin(tb.custom.OFFSET_RAD), bj_UNIT_FACING)
tb.store_cont[unit] = store
end
end
function tb.recycle(unit, showEyeCandy)
if tb.store_cont[unit] then
local store = tb.store_cont[unit]
tb.store_cont[unit] = nil
if showEyeCandy then
SetUnitAnimation(store, "death")
UnitAddAbility(store, FourCC("Aloc")) -- Sell the idea that the building is being destroyed.
DestroyEffect(AddSpecialEffect(tb.custom.DEATH_MODEL, GetUnitX(store), GetUnitY(store)))
end
PauseUnit(store, true)
doAfter((showEyeCandy and tb.custom.DEATH_TIME) or 0, function()
UnitRemoveAbility(store, FourCC("Aloc"))
ShowUnit(store, false)
SetUnitMoveSpeed(store, 1)
SetUnitX(store, WorldRect.rectMinX); SetUnitY(store, WorldRect.rectMinY);
SetUnitMoveSpeed(store, 0)
GroupAddUnit(tb.STORE_GROUP, store)
end)
end
end
UnitState.register("TRANSFORM_EVENT", function()
local unit = UnitState.eventUnit
local typeId = GetUnitTypeId(unit)
if typeId == tb.custom.TRANSFORM_ID then
tb.request(unit)
elseif typeId == tb.custom.NORMAL_ID then
tb.recycle(unit, true)
end
end)
UnitState.register("DEATH_EVENT", function()
local unit = UnitState.eventUnit
tb.recycle(unit, true)
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitState.eventUnit
tb.recycle(unit, false)
end)
end
do
local m_trap = protected_t()
m_trap.custom = {
ABILITY_ID = FourCC("A01Z"),
SLOW_BUFF_ID = FourCC("A020"),
SLOW_ORDER = "slow",
CORPORATE_ID = FourCC("u00L"),
POO_CHANCE = 50,
CORPORATE_CHANCE = 50,
POOP_MODEL = "war3mapImported\\Poop1.mdx",
POOP_MISSILE_MODEL = "war3mapImported\\PoopMissile.mdx",
POOP_DESTROY_MODEL = "war3mapImported\\Damnation Orange.mdx",
POOP_SCALE = 1,
POOP_CHECK_INTERVAL = 0.1,
POOP_ENUM_RADIUS = 75,
POOP_ERUPT_RADIUS = 300,
POOP_DISPLAY = {
"Do you guys not have phones?", "Buy our exclusive DLC skin pack for only $100",
"It's not gambling, it's surprise mechanics.", "Work from Home jobs may earn more than you think"
},
CORPORATE_DELAY = 5.00,
CORPORATE_BUILD_TIME = 60.00,
MISSILE_LAND_TIME = 0.25,
}
m_trap.chance_table = weightedtable(m_trap.custom.POO_CHANCE, m_trap.custom.CORPORATE_CHANCE)
m_trap.poop_list = ClassTimer:create(m_trap.custom.POOP_CHECK_INTERVAL)
m_trap.missile_list = ClassTimer:create()
m_trap.missile_info = {}
ExecutiveGargoyle.CorporateTrap = setmetatable({}, m_trap)
Initializer.register(function()
m_trap._GROUP = Group()
m_trap._DUMMY = DummyUtils.request()
UnitAddAbility(m_trap._DUMMY, m_trap.custom.SLOW_BUFF_ID)
DisableUnitMovement(m_trap._DUMMY)
end)
function m_trap.filter_trap_target(target, source)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(source))
and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
and not IsUnitType(target, UNIT_TYPE_FLYING)
end
function m_trap.poop_display(tx, ty)
local tag = CreateTextTag()
SetTextTagPermanent(tag, false)
SetTextTagFadepoint(tag, 3.00)
SetTextTagLifespan(tag, 4.00)
SetTextTagColor(tag, math.random(127, 255), math.random(127, 255), math.random(127, 255), 255)
SetTextTagPos(tag, tx, ty, math.random(3, 5)*10)
local rad, vel = math.random()*2*math.pi, math.random(4, 8)*10
SetTextTagVelocity(tag, TextTagSpeed2Velocity(vel*math.cos(rad)), TextTagSpeed2Velocity(vel*math.sin(rad)))
SetTextTagText(tag, m_trap.custom.POOP_DISPLAY[math.random(1, #m_trap.custom.POOP_DISPLAY - 1)], TextTagSize2Height(12))
end
function m_trap.apply_slow(target)
SetUnitX(m_trap._DUMMY, GetUnitX(target)); SetUnitY(m_trap._DUMMY, GetUnitY(target));
IssueTargetOrder(m_trap._DUMMY, m_trap.custom.SLOW_ORDER, target)
end
m_trap.poop_list:on_callback(function(poo)
local do_erupt = false
GroupEnumUnitsInRange(m_trap._GROUP, poo.tx, poo.ty, m_trap.custom.POOP_ENUM_RADIUS, nil)
while true do
local uu = FirstOfGroup(m_trap._GROUP)
if not uu then break end
if m_trap.filter_trap_target(uu, poo.source) then do_erupt = true; break; end
GroupRemoveUnit(m_trap._GROUP, uu)
end
if do_erupt then
GroupEnumUnitsInRange(m_trap._GROUP, poo.tx, poo.ty, m_trap.custom.POOP_ERUPT_RADIUS, nil)
while true do
local uu = FirstOfGroup(m_trap._GROUP)
if not uu then break end
if m_trap.filter_trap_target(uu, poo.source) then
m_trap.apply_slow(uu)
end
GroupRemoveUnit(m_trap._GROUP, uu)
end
DestroyEffect(poo.poo)
m_trap.poop_list:remove(poo)
m_trap.poop_display(poo.tx, poo.ty)
DestroyEffect( AddSpecialEffect(m_trap.custom.POOP_DESTROY_MODEL, poo.tx, poo.ty), 0.35)
end
end)
function m_trap.plant_burrow(source, tx, ty)
local burrow = CreateUnit(GetOwningPlayer(source), m_trap.custom.CORPORATE_ID, tx, ty, bj_UNIT_FACING)
PauseUnit(burrow, true)
QueueUnitAnimation(burrow, "stand")
SetUnitAnimation(burrow, "build")
SetUnitTimeScale(burrow, m_trap.custom.CORPORATE_BUILD_TIME/m_trap.custom.CORPORATE_DELAY)
doAfter(m_trap.custom.CORPORATE_DELAY, function()
PauseUnit(burrow, false)
end)
end
function m_trap.plant_poo(source, tx, ty)
local poo = {poo = AddSpecialEffect(m_trap.custom.POOP_MODEL, tx, ty), source = source}
poo.tx, poo.ty = tx, ty
SetEffectScale(poo.poo, m_trap.custom.POOP_SCALE)
m_trap.poop_list:include(poo)
end
m_trap.callback = {m_trap.plant_poo, m_trap.plant_burrow}
function m_trap.plant_object(source, tx, ty)
local i = m_trap.chance_table:get_weight_index(math.random(1, m_trap.chance_table.sum))
m_trap.callback[i](source, tx, ty)
end
m_trap.missile_list:on_callback(function(point)
local tb = m_trap.missile_info[point]
tb.time = tb.time + m_trap.missile_list._INTERVAL
local delta = tb.time/m_trap.custom.MISSILE_LAND_TIME
if delta >= 1 then
m_trap.missile_info[point] = nil
m_trap.missile_list:remove(point)
point:destroy()
m_trap.plant_object(tb.source, tb.tx, tb.ty)
else
point:set_height(tb.base_height*(1 - BezierEase.linear[delta]))
end
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_trap.custom.ABILITY_ID, function()
local unit,tx,ty = GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY()
local point = Missile:create(m_trap.custom.POOP_MISSILE_MODEL, tx, ty)
local tb = {base_height = GetUnitFlyHeight(unit), time = 0}
tb.source = unit
tb.tx, tb.ty = tx, ty
point:set_height(tb.base_height)
m_trap.missile_list:include(point)
m_trap.missile_info[point] = tb
end)
end
do
local tb = {
ITEM_ID = FourCC("I001"),
ABIL_INFO_ID = FourCC("A014"),
BENEFIT_COUNT = 2,
MAX_STACKS = 2,
PASS_DURATION = 480.0,
LUMBER_COST = 1000,
GOLD_COST = 2000,
BONUS_EFFECT = "Abilities\\Spells\\Items\\AIsm\\AIsmTarget.mdl",
BONUS_ABILITY_IDS = {FourCC("A00W"), FourCC("A00Y"), FourCC("A00Z"), FourCC("A010"),
FourCC("A011"), FourCC("A012"), FourCC("A013")},
MAGIC_REDUCTION = 0.05,
BONUS_ABILITY_TEXT = {"Bonus Damage", "Bonus HP Regen", "Bonus Armor",
"Bonus Mana Regen", "Bonus Movement Speed",
"Bonus Magic Resistance", "Bonus Attack Speed"},
BONUS_TEXT_DELAY = 2.00,
BONUS_TEXT_FADE = 1.00,
}
tb.color = {
BAD = {red = 255, green = 64, blue = 64},
GOOD = {red = 136, green = 255, blue = 0},
}
tb.BONUS_TEXT_COLOR = {tb.color.BAD, tb.color.GOOD, tb.color.GOOD,
tb.color.BAD, tb.color.BAD, tb.color.BAD,
tb.color.GOOD}
-- 0 1 1 0 0 0 1
tb.stacks = {}
tb.timer = {}
tb.enemy_weight = weightedtable(2, 1, 1, 1, 2, 3, 1)
tb.ally_weight = weightedtable(1, 2, 2, 1, 1, 1, 2)
tb.weight = weightedtable(1, 1, 1, 1, 1, 1, 1)
function tb.displayBonus(buyer, index, i)
i = (i - 1)*tb.BONUS_TEXT_DELAY
doAfter(i, function()
local tag = CreateTextTag()
local col = tb.BONUS_TEXT_COLOR[index]
SetTextTagPermanent(tag, false)
SetTextTagFadepoint(tag, tb.BONUS_TEXT_FADE); SetTextTagLifespan(tag, tb.BONUS_TEXT_DELAY);
SetTextTagPos(tag, GetUnitX(buyer), GetUnitY(buyer), 50)
SetTextTagColor(tag, col.red, col.green, col.blue, 255)
SetTextTagVelocity(tag, 0, TextTagSpeed2Velocity(90))
SetTextTagText(tag, tb.BONUS_ABILITY_TEXT[index], TextTagSize2Height(8))
SetTextTagVisibility(tag, IsUnitAlly(PlayerUtils.player))
end)
end
function tb.grantBonusesCore(buyer, seller)
local weight = tb.weight
local owner1, owner2 = GetOwningPlayer(buyer), GetOwningPlayer(seller)
DestroyEffect( AddSpecialEffectTarget(tb.BONUS_EFFECT, buyer, "origin"), 1.00)
if IsUnitEnemy(buyer, owner2) then
weight = tb.enemy_weight
elseif (GetPlayerAlliance(owner2, owner1, ALLIANCE_SHARED_SPELLS) == GetPlayerAlliance(owner1, owner2, ALLIANCE_SHARED_SPELLS)) == true then
weight = tb.ally_weight
end
for i = 1, tb.BENEFIT_COUNT do
local j = math.random(1, weight.sum)
j = weight:get_weight_index(j)
UnitAddAbility(buyer, tb.BONUS_ABILITY_IDS[j])
UnitMakeAbilityPermanent(buyer, true, tb.BONUS_ABILITY_IDS[j])
tb.displayBonus(buyer, j, i)
end
end
local function data_cleanup(buyer)
UnitRemoveAbility(buyer, tb.ABIL_INFO_ID)
for i = 1,(#tb.BONUS_ABILITY_IDS-1) do
UnitRemoveAbility(buyer, tb.BONUS_ABILITY_IDS[i])
end
DestroyTimer(tb.timer[buyer])
tb.stacks[buyer] = nil
tb.timer[buyer] = nil
end
function tb.grantBonuses(buyer, seller)
-- If allied, chances of picking more beneficial abilities become higher
-- will be implemented later.
if not tb.stacks[buyer] then
tb.stacks[buyer] = 1
tb.timer[buyer] = CreateTimer()
UnitAddAbility(buyer, tb.ABIL_INFO_ID)
UnitMakeAbilityPermanent(buyer, true, tb.ABIL_INFO_ID)
TimerStart(tb.timer[buyer], tb.PASS_DURATION, false, function()
data_cleanup(buyer)
end)
tb.grantBonusesCore(buyer, seller)
else
if tb.stacks[buyer] < tb.MAX_STACKS then
tb.stacks[buyer] = tb.stacks[buyer] + 1
tb.grantBonusesCore(buyer, seller)
else
local owner = GetOwningPlayer(buyer)
local remaining = math.floor(TimerGetRemaining(tb.timer[buyer]))
GameError(owner, "Cannot purchase more than two Season Passes. Please wait for " .. tostring(remaining) .. " more seconds.")
-- Don't be unfair to players. Refund where necessary.
SetPlayerState(owner, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(owner, PLAYER_STATE_RESOURCE_GOLD) + tb.GOLD_COST)
SetPlayerState(owner, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(owner, PLAYER_STATE_RESOURCE_LUMBER) + tb.LUMBER_COST)
end
end
end
Initializer.register(function()
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SELL_ITEM, function()
local itemId = GetItemTypeId(GetSoldItem())
local buyer = GetBuyingUnit()
if itemId == tb.ITEM_ID then
tb.grantBonuses(buyer, GetSellingUnit())
doAfter(0.00, function(item)
-- Clean-up.
RemoveItem(item)
end, GetSoldItem())
end
end)
end)
DamageEvent.register_modifier("MODIFIER_EVENT_ALPHA", function()
if not IsSpellDamage() then return end
if GetUnitAbilityLevel(DamageEvent.current.target, tb.BONUS_ABILITY_IDS[6]) ~= 0 then
DamageEvent.current.dmg = DamageEvent.current.dmg*(1 - tb.MAGIC_REDUCTION)
end
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
if tb.stacks[unit] then
data_cleanup(unit)
end
end)
end
FacelessManager = {}
do
local amp = setmetatable({}, protected_t())
local m_amp = getmetatable(amp)
FacelessManager.Karen = amp
m_amp.custom = {
UNIT_TYPE = FourCC("u00H"),
ABILITY_ID = FourCC("A01F"),
DAMAGE_FACTOR = 5.0,
KAREN_CHANCE = 0.35,
}
-- Filter unit types which are considered female.
m_amp.filter = LinkedList:create()
do
m_amp.filter:insert(FourCC('hsor'), FourCC('nhef'), FourCC('nhea'), FourCC('Hjai'))
m_amp.filter:insert(FourCC('Hvwd'), FourCC('uban'), FourCC('uswb'), FourCC('Usyl'))
m_amp.filter:insert(FourCC('earc'), FourCC('esen'), FourCC('edry'), FourCC('ehpr'))
m_amp.filter:insert(FourCC('Emoo'), FourCC('Ewar'), FourCC('enec'), FourCC('nwat'))
m_amp.filter:insert(FourCC('ensh'), FourCC('nssn'), FourCC('eshd'), FourCC('Ewrd'))
m_amp.filter:insert(FourCC('Etyr'), FourCC('nnsw'), FourCC('nhyc'), FourCC('Hvsh'))
m_amp.filter:insert(FourCC('nrwm'), FourCC('nbwm'), FourCC('nbzd'), FourCC('ngrd'))
m_amp.filter:insert(FourCC('nard'), FourCC('ngh1'), FourCC('ngh2'), FourCC('nsbm'))
m_amp.filter:insert(FourCC('nhar'), FourCC('nhrr'), FourCC('nhrw'), FourCC('nhrq'))
m_amp.filter:insert(FourCC('ninm'), FourCC('nnwq'), FourCC('ndqn'), FourCC('ndqv'))
m_amp.filter:insert(FourCC('ndqt'), FourCC('ndqp'), FourCC('ndqs'), FourCC('nska'))
m_amp.filter:insert(FourCC('nfgo'), FourCC('nsca'), FourCC('ngh2'), FourCC('Nngs'))
m_amp.filter:insert(FourCC('Nbrn'), FourCC('nvlw'), FourCC('e000'))
end
function m_amp.filterFemale(target)
return m_amp.filter:is_in(GetUnitTypeId(target))
end
DamageEvent.register_modifier("DAMAGE_EVENT_ALPHA", function()
local targ, src = DamageEvent.current.target, DamageEvent.current.source
if GetUnitTypeId(targ) == m_amp.custom.UNIT_TYPE then
if GetUnitAbilityLevel(src, m_amp.custom.ABILITY_ID) ~= 0 then
-- This unit is a Karen, amplify damage.
DamageEvent.current.dmg = DamageEvent.current.dmg*m_amp.custom.DAMAGE_FACTOR
end
end
end)
UnitDex.register("ENTER_EVENT", function()
if m_amp.filterFemale(UnitDex.eventUnit) and math.random() <= m_amp.custom.KAREN_CHANCE then
UnitAddAbility(UnitDex.eventUnit, m_amp.custom.ABILITY_ID)
UnitMakeAbilityPermanent(UnitDex.eventUnit, true, m_amp.custom.ABILITY_ID)
end
end)
end
do
local rest = setmetatable({}, protected_t())
local m_rest = getmetatable(rest)
FacelessManager.RestoreGoldMine = rest
m_rest.custom = {
ABILITY_ID = FourCC("A01E"),
BASE_GOLD = 10,
BONUS_GOLD = 10,
MAX_GOLD = 40,
CASTER_EFFECT = "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl",
LIGHTNING_PATH = "WHCH",
GOLD_INTERVAL = 1.0,
GOLD_ADD_INTERVAL = 5.0,
}
m_rest.custom.UPDATE_TICKS = m_rest.custom.GOLD_ADD_INTERVAL/m_rest.custom.GOLD_INTERVAL
m_rest.channeling = {}
function m_rest.filterGoldMine(target)
local prev_res = GetResourceAmount(target)
SetResourceAmount(target, 1)
local result = (GetResourceAmount(target) == 1)
SetResourceAmount(target, prev_res)
return result
end
function m_rest.displayGold(mine, amount)
local tag = CreateTextTag()
SetTextTagPermanent(tag, false)
SetTextTagLifespan(tag, 3.0); SetTextTagFadepoint(tag, 2.0);
SetTextTagPos(tag, GetUnitX(mine), GetUnitY(mine), 75)
SetTextTagColor(tag, 0xff, 0xea, 0, 0xff)
SetTextTagVelocity(tag, 0, TextTagSpeed2Velocity(90))
SetTextTagText(tag, "+" .. tostring(amount), TextTagSize2Height(11))
end
do
local data = m_rest.channeling
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_CAST, m_rest.custom.ABILITY_ID, function()
if not m_rest.filterGoldMine(GetSpellTargetUnit()) then
-- Cancel the spell
local unit = GetTriggerUnit()
PauseUnit(unit, true)
IssueImmediateOrderById(unit, 851972)
PauseUnit(unit, false)
GameError(GetOwningPlayer(unit), "Must target a gold mine.")
end
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_rest.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local cx, cy, tx, ty
data[unit] = {gold_mine = GetSpellTargetUnit()}
cx, cy = GetUnitX(unit), GetUnitY(unit)
tx, ty = GetUnitX(data[unit].gold_mine), GetUnitY(data[unit].gold_mine)
data[unit].gold = m_rest.custom.BASE_GOLD
data[unit].timer = CreateTimer()
data[unit].tick = 0
data[unit].fx = AddSpecialEffectTarget(m_rest.custom.CASTER_EFFECT, unit, "origin")
data[unit].fx2 = AddSpecialEffect(m_rest.custom.CASTER_EFFECT, tx, ty)
data[unit].light = LightningUtils:add(m_rest.custom.LIGHTNING_PATH, IsUnitVisible(data[unit].gold_mine),
cx, cy, tx, ty)
data[unit].light:move_to(m_rest.custom.GOLD_INTERVAL, IsUnitVisible(data[unit].gold_mine, PlayerUtils.player), cx, cy, tx, ty)
TimerStart(data[unit].timer, m_rest.custom.GOLD_INTERVAL, true, function()
cx, cy = GetUnitX(unit), GetUnitY(unit)
tx, ty = GetUnitX(data[unit].gold_mine), GetUnitY(data[unit].gold_mine)
data[unit].light:move_to(m_rest.custom.GOLD_INTERVAL, IsUnitVisible(data[unit].gold_mine, PlayerUtils.player), cx, cy, tx, ty)
BlzSetSpecialEffectTime(data[unit].fx, 0.00)
BlzSetSpecialEffectTime(data[unit].fx2, 0.00)
SetResourceAmount(data[unit].gold_mine, GetResourceAmount(data[unit].gold_mine) + data[unit].gold)
m_rest.displayGold(data[unit].gold_mine, data[unit].gold)
data[unit].tick = data[unit].tick + 1
local bonus_incr = math.floor(data[unit].tick/m_rest.custom.UPDATE_TICKS)
if bonus_incr > 0 then
data[unit].gold = math.min(data[unit].gold + m_rest.custom.BONUS_GOLD, m_rest.custom.MAX_GOLD)
data[unit].tick = math.fmod(data[unit].tick, m_rest.custom.UPDATE_TICKS)
end
end)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, m_rest.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
PauseTimer(data[unit].timer)
DestroyTimer(data[unit].timer)
DestroyEffect(data[unit].fx)
DestroyEffect(data[unit].fx2)
data[unit].light:apply_color(1.0, 0)
data[unit].light:add_callback("color", "onEvent", function(light)
doAfter(0.00, function() light:destroy() end)
end)
data[unit] = nil
end)
end
end
do
local m_trees = protected_t()
local trees = setmetatable({}, m_trees)
FacelessManager.RestoreTrees = trees
m_trees.custom = {
ABILITY_ID = FourCC("A01G"),
AREA_EFFECT = "war3mapImported\\MagicCircle_Devil.mdx",
RESTORE_NOVA = "war3mapImported\\DarkLightningNova.mdx",
RESTORE_SND_FX = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl",
MISSILE_MODEL = "war3mapImported\\Void Rain Missile.mdx",
MISSILE_COUNT = 40,
RESTORE_DELAY = 10.0,
}
m_trees.list = LinkedList:create()
do
m_trees.custom.MISSILE_SPAWN_INTERVAL = m_trees.custom.RESTORE_DELAY/m_trees.custom.MISSILE_COUNT
end
Initializer.register(function()
local root_2 = 2^(0.5)
m_trees.custom.AOE = tonumber(ObjectReader.read(m_trees.custom.ABILITY_ID, "Area1", ",."))
m_trees.custom.AOE2 = m_trees.custom.AOE*m_trees.custom.AOE
m_trees._RECT = Rect(-m_trees.custom.AOE/root_2, -m_trees.custom.AOE/root_2, m_trees.custom.AOE/root_2, m_trees.custom.AOE/root_2)
end)
function m_trees.getSound()
local snd = CreateSound("CustomSounds\\human-male-scream-edited.wav", false, true, false, 10, 10, "DefaultEAXON")
SetSoundParamsFromLabel(snd, "MaleScreamTorture")
SetSoundVolume(snd, 192)
SetSoundDuration(snd, 10004)
return snd
end
function m_trees.replant(tx, ty)
local list = LinkedList:create()
local tb = {}
MoveRectTo(m_trees._RECT, tx, ty)
EnumDestructablesInRect(m_trees._RECT, nil, function()
local dd = GetEnumDestructable()
if IsDestructableTree(dd) and not IsTreeAlive(dd) then
local dx, dy = GetDestructableX(dd), GetDestructableY(dd)
local dist = (dx-tx)*(dx-tx) + (dy-ty)*(dy-ty)
if not m_trees.list:is_in(dd) and (dist <= m_trees.custom.AOE2) then
list:insert(dd)
tb[dd] = select(3, m_trees.list:insert(dd))
SetWidgetLife(dd, GetDestructableMaxLife(dd))
SetDestructableAnimationSpeed(dd, -1)
SetDestructableAnimation(dd, "death")
end
end
end)
doAfter(3.50, function(list, tb)
for dd, pointer in list:iterator() do
m_trees.list:erase(tb[dd])
list:erase(pointer)
SetDestructableAnimation(dd, "stand")
SetDestructableAnimationSpeed(dd, 1)
end
list:destroy()
end, list, tb)
list, tb = nil
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_trees.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local tx, ty = GetSpellTargetX(), GetSpellTargetY()
local fx = AddSpecialEffect(m_trees.custom.AREA_EFFECT, tx, ty)
local count = 0
local landed = 0
local destroyed = false
SetEffectScale(fx, m_trees.custom.AOE/100.0)
BlzSetSpecialEffectTimeScale(fx, 2.00)
DestroyEffect(fx, m_trees.custom.RESTORE_DELAY)
do
local i = 0
doRepeat(0.33, function()
i = i + 1
if i > 3 then
PauseTimer(GetExpiredTimer())
else
doAfter(0.00, function(snd)
SetSoundPosition(snd, tx, ty, 0)
StartSound(snd)
KillSoundWhenDone(snd)
end, m_trees.getSound())
end
end)
end
doRepeat(1.25, function()
if not destroyed then
BlzSetSpecialEffectTime(fx, 0.00)
end
end)
doRepeat(m_trees.custom.MISSILE_SPAWN_INTERVAL, function()
count = count + 1
local cx, cy = GetUnitX(unit), GetUnitY(unit)
local theta = ModuloReal(math.atan(ty - cy, tx - cx), math.pi*2)
local point = Missile.Point:create(m_trees.custom.MISSILE_MODEL, cx, cy)
local rho = math.random()*math.pi - math.pi/2
local phi = math.random()*2*math.pi
local dist = m_trees.custom.AOE/2
point.missile:set_facing(theta + rho, "raw")
point.missile:set_speed(1500)
point.missile:set_turn_rate(150.)
point.missile:set_scale(2.00)
doAfter(0.30, function()
point.missile:set_turn_rate(-1.)
end)
point:set_target_pos(tx + dist*math.cos(phi), ty + dist*math.sin(phi))
point:on_update(function(theta)
return theta
end)
point:on_land(function()
point:destroy()
landed = landed + 1
if landed == count and count >= m_trees.custom.MISSILE_COUNT then
local fx = AddSpecialEffect(m_trees.custom.RESTORE_NOVA, tx, ty)
SetEffectScale(fx, m_trees.custom.AOE/150.0)
SetEffectHeight(fx, -350)
DestroyEffect(fx)
for i = 1,6 do
local fx = AddSpecialEffect(m_trees.custom.RESTORE_NOVA, tx + dist*math.cos(i/6*2*math.pi), ty + dist*math.sin(i/6*2*math.pi))
SetEffectScale(fx, m_trees.custom.AOE/150.0)
SetEffectHeight(fx, -350)
DestroyEffect(fx)
end
for i = 1,2 do
fx = AddSpecialEffect(m_trees.custom.RESTORE_SND_FX, tx, ty)
ShowEffect(fx, false)
DestroyEffect(fx)
end
doAfter(0.40, function()
for i = 1,5 do
fx = AddSpecialEffect(m_trees.custom.RESTORE_SND_FX, tx, ty)
ShowEffect(fx, false)
DestroyEffect(fx)
end
m_trees.replant(tx, ty)
end)
end
end)
point:launch()
if count >= m_trees.custom.MISSILE_COUNT then
PauseTimer(GetExpiredTimer())
destroyed = true
end
end)
end)
end
do
local tb = {}
tb.custom = {
CHECK_ABIL_ID = FourCC("A025"),
ABILITY_ID = FourCC("A02D"),
ORDER_ID = 852625,
}
Initializer("USER", function()
tb.DUMMY_UNIT = DummyUtils.request()
UnitAddAbility(tb.DUMMY_UNIT, tb.custom.ABILITY_ID)
BlzUnitDisableAbility(tb.DUMMY_UNIT, FourCC("Aatk"), true, false)
DisableUnitMovement(tb.DUMMY_UNIT)
end)
local function do_reveal(src, targ)
SetUnitX(tb.DUMMY_UNIT, GetUnitX(targ)); SetUnitY(tb.DUMMY_UNIT, GetUnitY(targ));
SetUnitOwner(tb.DUMMY_UNIT, GetOwningPlayer(src), false)
IssueImmediateOrderById(tb.DUMMY_UNIT, tb.custom.ORDER_ID)
SetUnitOwner(tb.DUMMY_UNIT, DummyUtils.PLAYER, false)
end
DamageEvent.register_damage(function()
if DamageEvent.current.dmgtype == DAMAGE_TYPE_NORMAL then
local attacker = DamageEvent.current.source
if GetUnitAbilityLevel(attacker, tb.custom.CHECK_ABIL_ID) > 0 then
do_reveal(attacker, DamageEvent.current.target)
end
end
end)
end
CorporateBurrow = {}
do
local tax = protected_t()
CorporateBurrow.TaxRebate = setmetatable({}, tax)
tax.custom = {
ABILITY_ID = FourCC("A021"),
FOOD_COST = 10,
GOLD_BONUS = 200,
LUMBER_BONUS = 50,
}
tax.enforced = {}
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tax.custom.ABILITY_ID, function()
local unit, targ = GetTriggerUnit(), GetSpellTargetUnit()
doAfter(0.00, function()
local owner = GetOwningPlayer(targ)
local old_owner = GetOwningPlayer(unit)
local cx, cy = GetUnitX(unit), GetUnitY(unit)
if not tax.enforced[unit] then
tax.enforced[unit] = true
else
-- Deduct food cost from previous owner
SetPlayerState(old_owner, PLAYER_STATE_RESOURCE_FOOD_USED, GetPlayerState(old_owner, PLAYER_STATE_RESOURCE_FOOD_USED) - tax.custom.FOOD_COST)
end
-- General Stuff
SetPlayerState(owner, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(owner, PLAYER_STATE_RESOURCE_GOLD) + tax.custom.GOLD_BONUS)
SetPlayerState(owner, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(owner, PLAYER_STATE_RESOURCE_LUMBER) + tax.custom.GOLD_BONUS)
SetPlayerState(owner, PLAYER_STATE_RESOURCE_FOOD_USED, GetPlayerState(owner, PLAYER_STATE_RESOURCE_FOOD_USED) + tax.custom.FOOD_COST)
SetUnitOwner(unit, owner, targ)
if PlayerUtils.player == owner then
PingMinimapEx(cx, cy, 10.0, 255, 0, 0, true)
SetCameraQuickPosition(cx, cy)
end
GameSounds.play_for_player(GameSounds.get_ping(), owner)
end)
end)
local function remove_food_cost(whichunit)
if tax.enforced[whichunit] then
tax.enforced[whichunit] = nil
SetPlayerState(GetOwningPlayer(whichunit), PLAYER_STATE_RESOURCE_FOOD_USED, GetPlayerState(GetOwningPlayer(whichunit), PLAYER_STATE_RESOURCE_FOOD_USED) - tax.custom.FOOD_COST)
end
end
UnitState.register("DEATH_EVENT", function()
remove_food_cost(UnitState.eventUnit)
end)
UnitDex.register("LEAVE_EVENT", function()
remove_food_cost(UnitDex.eventUnit)
end)
end
CorporateNexus = {}
if BusinessZombie and BusinessZombie.PurchaseNeutralBuilding then
do
local purch = BusinessZombie.PurchaseNeutralBuilding
local trans = setmetatable({}, protected_t())
local m_trans = getmetatable(trans)
CorporateNexus.TransferOwnership = trans
m_trans.custom = {
ABILITY_ID = FourCC("A00V"),
TRANSFER_EFFECT = "",
}
function m_trans.removeAbility(structure)
UnitRemoveAbility(structure, m_trans.custom.ABILITY_ID)
end
function m_trans.disableAbility(structure)
BlzUnitDisableAbility(structure, m_trans.custom.ABILITY_ID, true, false)
end
function m_trans.enableAbility(structure)
BlzUnitDisableAbility(structure, m_trans.custom.ABILITY_ID, false, false)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_trans.custom.ABILITY_ID, function()
local unit, target = GetTriggerUnit(), GetSpellTargetUnit()
purch.transferOwner(unit, target)
end)
Initializer.register(function()
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function()
local structure = GetTriggerUnit()
if GetUnitTypeId(structure) == purch.custom.NEXUS_ID then
local cx, cy = GetUnitX(structure), GetUnitY(structure)
local fx = AddSpecialEffect("Objects\\Spawnmodels\\Undead\\UCancelDeath\\UCancelDeath.mdl", cx, cy)
SetEffectScale(fx, 1.25)
DestroyEffect(fx, 5.00)
end
end)
end)
end
else
Initializer.register(function()
print("BusinessZombie.PurchaseNeutralBuilding not found.")
end)
end
do
local tb = {
HAUNT_MINE_ID = FourCC("u003"),
GOLD_MINE_ID = FourCC("ngol"),
MAX_DIST_CHECK = 256,
GROUP = CreateGroup(),
gold_mine = {},
is_constructing = {},
is_visible = {}
}
Initializer.register(function()
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_START, function()
if GetUnitTypeId(GetConstructingStructure()) == tb.HAUNT_MINE_ID then
local construct = GetConstructingStructure()
tb.id = GetHandleId(GetTriggerUnit())
GroupEnumUnitsOfPlayer(tb.GROUP, Player(PLAYER_NEUTRAL_PASSIVE), nil)
local enum_unit = FirstOfGroup(tb.GROUP)
while enum_unit ~= nil do
GroupRemoveUnit(tb.GROUP, enum_unit)
if GetUnitTypeId(enum_unit) == tb.GOLD_MINE_ID and IsUnitInRange(enum_unit, construct, tb.MAX_DIST_CHECK) then
SetResourceAmount(construct, GetResourceAmount(enum_unit))
break
end
enum_unit = FirstOfGroup(tb.GROUP)
end
tb.gold_mine[tb.id] = enum_unit
tb.is_constructing[tb.id] = true
tb.id = GetHandleId(enum_unit)
tb.is_visible[enum_unit] = not IsUnitHidden(enum_unit)
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, function()
tb.id = GetHandleId(UnitState.eventUnit)
if tb.gold_mine[tb.id] then
tb.is_constructing[tb.id] = false
end
end)
local function show_mine(whichunit)
tb.id = GetHandleId(whichunit)
if tb.gold_mine[tb.id] then
local mine = tb.gold_mine[tb.id]
if tb.is_constructing[tb.id] then
tb.is_constructing[tb.id] = nil
end
SetResourceAmount(mine, GetResourceAmount(whichunit))
if GetResourceAmount(mine) <= 0 then
KillUnit(mine, true)
else
ShowUnit(mine, true)
end
tb.id2 = GetHandleId(mine)
tb.gold_mine[tb.id] = nil
tb.is_visible[tb.id2] = nil
else
if GetUnitTypeId(whichunit) == tb.HAUNT_MINE_ID then
GroupEnumUnitsOfPlayer(tb.GROUP, Player(PLAYER_NEUTRAL_PASSIVE), nil)
local enum_unit = FirstOfGroup(tb.GROUP)
while enum_unit ~= nil do
GroupRemoveUnit(tb.GROUP, enum_unit)
if GetUnitTypeId(enum_unit) == tb.GOLD_MINE_ID and IsUnitInRange(enum_unit, whichunit, tb.MAX_DIST_CHECK) then
break
end
enum_unit = FirstOfGroup(tb.GROUP)
end
if not enum_unit then
doAfter(0.00, function(x, y, amount)
if amount > 0 then
bj_lastCreatedUnit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), tb.GOLD_MINE_ID, x, y, 270.00)
SetResourceAmount(bj_lastCreatedUnit, amount)
end
end, GetUnitX(whichunit), GetUnitY(whichunit), GetResourceAmount(whichunit))
else
SetResourceAmount(enum_unit, GetResourceAmount(whichunit))
if GetResourceAmount(enum_unit) <= 0 then
KillUnit(enum_unit, true)
else
ShowUnit(enum_unit, true)
end
end
end
end
end
UnitDex.register("LEAVE_EVENT", function()
show_mine(UnitDex.eventUnit)
end)
UnitState.register("DEATH_EVENT", function()
show_mine(UnitState.eventUnit)
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function()
show_mine(GetTriggerUnit())
end)
end)
end
do
local tb = {
RESEARCH_ID = FourCC("R001"),
GOLD_MINE_ID = FourCC("u003"),
ORDER_DEPTH_COMP = 2,
curLevel = {},
ABILITY_LIST = {FourCC("A006"), FourCC("A00A"), FourCC("A00B")},
ENUM_RANGE = 512,
MAX_MINERS = 5,
}
do
local i = 0
while i < bj_MAX_PLAYER_SLOTS do
tb.curLevel[i] = 1
i = i + 1
end
end
function tb.modify(unit, prev_level, cur_level)
local last_amt = GetResourceAmount(unit)
prev_level, cur_level = prev_level + 1, cur_level + 1
PauseUnit(unit, true)
if UnitAddAbility(unit, tb.ABILITY_LIST[cur_level]) then
UnitMakeAbilityPermanent(unit, true, tb.ABILITY_LIST[cur_level])
else
BlzUnitDisableAbility(unit, tb.ABILITY_LIST[cur_level], false, false)
end
BlzUnitDisableAbility(unit, tb.ABILITY_LIST[prev_level], true, false)
SetResourceAmount(unit, last_amt)
PauseUnit(unit, false)
-- Re-issue the order to displaced units
local g = CreateGroup()
local c = 0
local owner = GetOwningPlayer(unit)
GroupEnumUnitsInRange(g, GetUnitX(unit), GetUnitY(unit), tb.ENUM_RANGE, nil)
GroupRemoveUnit(g, unit)
ForGroup(g, function()
local uu = GetEnumUnit()
for index=1,tb.ORDER_DEPTH_COMP do
if HandleCheck.is_unit(OrderMatrix[uu].target[index]) then
if OrderMatrix[uu].target[index] == unit and GetOwningPlayer(uu) == owner then
if OrderMatrix[uu].order[index] == "smart" or OrderMatrix[uu].order[index] == "harvest" then
PauseUnit(uu, true)
IssueImmediateOrder(uu, "stop")
PauseUnit(uu, false)
break
else
GroupRemoveUnit(g, uu)
end
else
GroupRemoveUnit(g, uu)
end
else
GroupRemoveUnit(g, uu)
end
end
end)
doAfter(0.00, function(g, unit)
ForGroup(g, function()
IssueTargetOrder(GetEnumUnit(), "harvest", unit)
end)
DestroyGroup(g)
end, g, unit)
end
ResearchUtils.register_research(tb.RESEARCH_ID, tb.modify, tb.GOLD_MINE_ID)
end
ExtremnioticTower = {}
do
local tb = protected_t()
ExtremnioticTower.DisableAttack = setmetatable({}, tb)
tb.custom = {
UNIT_ID = FourCC("h002"),
ATTACK_INDEX = 0
}
UnitDex.register("ENTER_EVENT", function()
if GetUnitTypeId(UnitDex.eventUnit) == tb.custom.UNIT_ID then
BlzSetUnitAttackCooldown(UnitDex.eventUnit, 0, tb.custom.ATTACK_INDEX)
end
end)
end
do
local tb = protected_t()
ExtremnioticTower.TieredAttack = setmetatable({}, tb)
tb.custom = {
UNIT_ID = ExtremnioticTower.DisableAttack.custom.UNIT_ID,
DEATH_MODEL = "Objects\\Spawnmodels\\Undead\\UCancelDeath\\UCancelDeath.mdl",
TIER_MODEL = "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl",
DMG_MULTIPLIER = 5,
FIRE_LIGHTNING = "WHNL",
DEF_ATTACK_RANGE = 900, -- Can't be read via Object Editor shenanigans
RAY_START_HEIGHT = 220,
RAY_END_OFFSET = 40,
RAY_FLYING_OFFSET = 140,
TIER_DMG = {5, 25, 125, 625, 3125},
TIER_PROG_TIME = {2.0, 4.0, 4.0, 6.0, 8.0},
ATTACK_TYPE = ATTACK_TYPE_CHAOS,
}
tb.ray_check = ClassTimer:create()
tb.target = {}
tb.tier = {}
tb.cur_ray = {}
for i = 1, #tb.custom.TIER_DMG do
tb.custom.TIER_DMG[i] = tb.custom.TIER_DMG[i]*tb.ray_check._INTERVAL
end
UnitDex.register("ENTER_EVENT", function()
if GetUnitTypeId(UnitDex.eventUnit) == tb.custom.UNIT_ID then
tb.tier[UnitDex.eventUnit] = 0
end
end)
UnitDex.register("LEAVE_EVENT", function()
tb.tier[UnitDex.eventUnit] = nil
end)
Initializer("USER", function()
local function remove_ray(ray)
doAfter(0.00, function(light)
light:destroy()
end, ray.light)
tb.ray_check:remove(ray)
end
local function notify_fx(src)
DestroyEffect( AddSpecialEffect(tb.custom.TIER_MODEL, GetUnitX(src), GetUnitY(src)))
end
local function notify_tier(src)
local tag = CreateTextTag()
local tier = tostring(tb.tier[src])
SetTextTagPermanent(tag, false)
SetTextTagLifespan(tag, 2.00)
SetTextTagFadepoint(tag, 1.50)
SetTextTagPosUnit(tag, src, 100)
SetTextTagColor(tag, 255, 0, 0, 255)
SetTextTagVelocity(tag, 0, TextTagSpeed2Velocity(80))
SetTextTagText(tag, "Tier " .. tier, TextTagSize2Height(10))
end
tb.ray_check:on_callback(function(ray)
if not UnitAlive(ray.targ) or not IsUnitInRange(ray.targ, ray.src, tb.custom.DEF_ATTACK_RANGE) then
IssueImmediateOrderById(ray.src, 851972)
remove_ray(ray)
return
end
local height = tb.custom.RAY_END_OFFSET +
((IsUnitType(ray.targ, UNIT_TYPE_FLYING) and tb.custom.RAY_FLYING_OFFSET) or 0)
ray.time = ray.time + tb.ray_check._INTERVAL
ray.light:moveEX(IsUnitVisible(ray.src, PlayerUtils.player), GetUnitX(ray.src), GetUnitY(ray.src), tb.custom.RAY_START_HEIGHT,
GetUnitX(ray.targ), GetUnitY(ray.targ), GetUnitFlyHeight(ray.targ) + height)
UnitDamageTarget(ray.src, ray.targ, tb.custom.TIER_DMG[tb.tier[ray.src]], true, true,
tb.custom.ATTACK_TYPE, DAMAGE_TYPE_NORMAL, nil)
if ray.time >= tb.custom.TIER_PROG_TIME[tb.tier[ray.src]] and
tb.custom.TIER_PROG_TIME[tb.tier[ray.src] + 1] then
ray.time = ray.time - tb.custom.TIER_PROG_TIME[tb.tier[ray.src]]
tb.tier[ray.src] = tb.tier[ray.src] + 1
notify_tier(ray.src)
notify_fx(ray.src)
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function()
local attacker = GetAttacker()
if tb.tier[attacker] then
local targ = GetTriggerUnit()
if tb.target[attacker] ~= targ or tb.cur_ray[attacker] then
tb.target[attacker] = targ
tb.tier[attacker] = 1
if tb.cur_ray[attacker] then
remove_ray(tb.cur_ray[attacker])
end
local height = tb.custom.RAY_END_OFFSET +
((IsUnitType(targ, UNIT_TYPE_FLYING) and tb.custom.RAY_FLYING_OFFSET) or 0)
tb.cur_ray[attacker] = {src = attacker, targ = tb.target[attacker], time = 0}
tb.cur_ray[attacker].light = LightningUtils:addEX(tb.custom.FIRE_LIGHTNING, IsUnitVisible(attacker, PlayerUtils.player),
GetUnitX(attacker), GetUnitY(attacker), tb.custom.RAY_START_HEIGHT,
GetUnitX(tb.target[attacker]), GetUnitY(tb.target[attacker]), GetUnitFlyHeight(tb.target[attacker]) + height)
tb.ray_check:include(tb.cur_ray[attacker])
end
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function()
local unit = GetTriggerUnit()
if tb.cur_ray[unit] then
remove_ray(tb.cur_ray[unit])
tb.target[unit] = nil
tb.cur_ray[unit] = nil
tb.tier[unit] = 0
end
end)
end)
UnitState.register("DEATH_EVENT", function()
local unit = UnitState.eventUnit
if tb.tier[unit] then
DestroyEffect( AddSpecialEffect(tb.custom.DEATH_MODEL, GetUnitX(unit), GetUnitY(unit)))
end
end)
end
do
local tb = protected_t()
local buff_field
ExtremnioticTower.OutervoidBarrier = setmetatable({}, tb)
tb.custom = {
ABILITY_ID = FourCC("A029"),
DUMMY_ABIL_ID = FourCC("A02A"),
DUMMY_BUFF_ID = FourCC("B00Z"),
IMMUNE_ID = FourCC("A02B"),
FIELD_MODEL = "war3mapImported\\ForceField.mdx",
FIELD_DURATION = {20},
DMG_REDUCTION = {0.6},
}
tb.immune = {}
tb.barrier = {}
Initializer("USER", function()
local levels = tonumber(ObjectReader.read(tb.custom.ABILITY_ID, "levels"))
for i = 1, levels do
tb.custom.FIELD_DURATION[i] = tonumber(ObjectReader.read(tb.custom.ABILITY_ID, "Dur" .. tostring(i), ",."))
end
buff_field = ConvertAbilityStringLevelArrayField(FourCC("abuf"))
end)
local function remove_immunity(unit)
if not tb.immune[unit] then return; end
tb.immune[unit] = tb.immune[unit] - 1
if tb.immune[unit] <= 0 then
DestroyEffect(tb.barrier[unit])
UnitRemoveAbility(unit, tb.custom.IMMUNE_ID)
UnitRemoveAbility(unit, tb.custom.DUMMY_ABIL_ID)
UnitRemoveAbility(unit, tb.custom.DUMMY_BUFF_ID)
tb.immune[unit] = nil
end
end
local function add_immunity(unit)
if not tb.immune[unit] then
tb.immune[unit] = 0
tb.barrier[unit] = AddSpecialEffect(tb.custom.FIELD_MODEL, GetUnitX(unit), GetUnitY(unit))
SetEffectScale(tb.barrier[unit], 0.35)
SetEffectHeight(tb.barrier[unit], 45)
UnitAddAbility(unit, tb.custom.IMMUNE_ID)
UnitAddAbility(unit, tb.custom.DUMMY_ABIL_ID)
UnitMakeAbilityPermanent(unit, true, tb.custom.IMMUNE_ID)
UnitMakeAbilityPermanent(unit, true, tb.custom.DUMMY_ABIL_ID)
end
tb.immune[unit] = tb.immune[unit] + 1
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local unit, level; unit = GetTriggerUnit(); level = GetUnitAbilityLevel(unit, tb.custom.ABILITY_ID)
add_immunity(unit)
print(tb.custom.FIELD_DURATION[level])
doAfter(tb.custom.FIELD_DURATION[level], function()
remove_immunity(unit)
end)
end)
DamageEvent.register_modifier("MODIFIER_EVENT_DELTA", function()
local unit = DamageEvent.current.target
if tb.immune[unit] then
local level = GetUnitAbilityLevel(unit, tb.custom.ABILITY_ID)
DamageEvent.current.dmg = DamageEvent.current.dmg*(1 - tb.custom.DMG_REDUCTION[level])
end
end)
end
Necrovizier = {}
Necrovizier.SnowSpirits = setmetatable({}, protected_t())
do
local snow = Necrovizier.SnowSpirits
local m_snow = getmetatable(snow)
local m_chill
local tb_container = {}
local snow_custom = {
ABILITY_ID = FourCC("A00K"),
FROST_NOVA_ID = FourCC("A00L"),
FROST_NOVA_ORDER = "coldarrowstarg",
SPIRIT_COUNT = {3, 4, 5},
SPIRIT_INTERVAL = {},
CHANNEL_DUR = {},
SPIRIT_DAMAGE = {5, 10, 15},
SLOW_DUR = {3, 3, 3},
SPIRIT_DIST = 75.0,
SPIRIT_DEPS_DELAY = 4.0,
SPIRIT_PEAK_DELAY = 1.0,
SPIRIT_DECAY = 1.0,
SPIRIT_PEAK = 200.0, -- The maximum height to be reached by the spirit
SPIRIT_PEAK_REV = 1.0,
SPIRIT_AOE = 75.0,
SPIRIT_DURATION = 12.0,
LINGER_TIME = 1.0,
ATTACK_TYPE = ATTACK_TYPE_NORMAL,
DAMAGE_TYPE = DAMAGE_TYPE_COLD,
SPIRIT_MODEL = "war3mapImported\\FrostOrb.mdx",
}
m_snow.custom = snow_custom
m_snow.container = tb_container
Initializer.registerBJ("USER", function()
local j = tonumber(ObjectReader.read(snow_custom.ABILITY_ID, "levels"))
for i = 1, j do
snow_custom.CHANNEL_DUR[i] = tonumber(ObjectReader.read(snow_custom.ABILITY_ID, "DataA" .. tostring(i), ",."))
snow_custom.SPIRIT_INTERVAL[i] = snow_custom.CHANNEL_DUR[i]/snow_custom.SPIRIT_COUNT[i]
end
snow_custom.ENUM_GROUP = CreateGroup()
snow_custom.DUMMY_UNIT = DummyUtils.request(DummyUtils.PLAYER)
UnitAddAbility(snow_custom.DUMMY_UNIT, snow_custom.FROST_NOVA_ID)
BlzSetUnitAttackCooldown(snow_custom.DUMMY_UNIT, 0.0001, 0)
end)
function m_snow.filterTarget(target, caster)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster))
and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
end
function m_snow.apply_slow(target, level)
-- Ensure that the dummy unit isn't attacking.
BlzUnitInterruptAttack(snow_custom.DUMMY_UNIT)
SetUnitX(snow_custom.DUMMY_UNIT, GetUnitX(target))
SetUnitY(snow_custom.DUMMY_UNIT, GetUnitY(target))
BlzUnitDisableAbility(snow_custom.DUMMY_UNIT, FourCC("Amov"), true, false)
SetUnitAbilityLevel(snow_custom.DUMMY_UNIT, snow_custom.FROST_NOVA_ID, level)
IssueTargetOrder(snow_custom.DUMMY_UNIT, snow_custom.FROST_NOVA_ORDER, target)
BlzUnitInterruptAttack(snow_custom.DUMMY_UNIT)
BlzUnitDisableAbility(snow_custom.DUMMY_UNIT, FourCC("Amov"), false, false)
end
-- Additional filter to attacks
DamageEvent.register_modifier("MODIFIER_EVENT_DELTA", function()
if DamageEvent.current.source == snow_custom.DUMMY_UNIT then
-- Negate all damage from DUMMY_UNIT
DamageEvent.current.dmg = 0
end
end)
do
local INTERVAL = EffectUtils.INTERVAL
local BEZIER = BezierEasing.create(0, 0, 0.6, 1)
local BEZIER2 = BezierEase.easeInOut
local PEAK, PERIOD = snow_custom.SPIRIT_PEAK, snow_custom.SPIRIT_PEAK_DELAY
local LINGER = snow_custom.LINGER_TIME
local PHASES = {_TRANSITION=1, _ACTIVE=2}
local ENUM_GRP
local function PointMissileEnum(point)
local cx, cy = point.missile:get_pos()
local caster = point:get_target()
local tempGrp = tb_container[point].tempGrp
GroupEnumUnitsInRange(ENUM_GRP, cx, cy, snow_custom.SPIRIT_AOE, nil)
ForGroup(ENUM_GRP, function()
-- get_target in this case refers to the caster
local unit = GetEnumUnit()
if m_snow.filterTarget(unit, caster) and not IsUnitInGroup(unit, tempGrp) then
UnitDamageTarget(caster, unit, snow_custom.SPIRIT_DAMAGE[tb_container[caster].level],
true, true, snow_custom.ATTACK_TYPE, snow_custom.DAMAGE_TYPE,
nil)
m_snow.apply_slow(unit, tb_container[caster].level)
GroupAddUnit(tempGrp, unit)
doAfter(LINGER, function(grp, unit)
GroupRemoveUnit(tempGrp, unit)
end, tempGrp, unit)
end
end)
end
function m_snow.phase_three(theta)
local point = Missile.Orbit.current
tb_container[point].time = tb_container[point].time + INTERVAL
if tb_container[point].time <= snow_custom.SPIRIT_DECAY then
local delta = 1 - tb_container[point].time/snow_custom.SPIRIT_DECAY
point.missile:set_height(tb_container[point].height*BEZIER[delta])
else
point.missile:set_height(0.)
point:land()
end
end
function m_snow.phase_two(theta)
local point = Missile.Orbit.current
tb_container[point].time = tb_container[point].time + INTERVAL
if tb_container[point].time > snow_custom.SPIRIT_DURATION then
local unit = point:get_target()
tb_container[point].time = 0
tb_container[point].orbit_phase = 3
-- The Missile is marked for destruction.
tb_container[unit].spirits:erase(tb_container[point].pointer)
tb_container[point].pointer = nil
point:on_update(m_snow.phase_three)
if m_chill and tb_container[unit].spirits.size <= 0 then
m_chill.disableAbility(unit)
end
end
PointMissileEnum(point)
return theta
end
function m_snow.phase_one(theta)
local point = Missile.Orbit.current
tb_container[point].time = tb_container[point].time + INTERVAL
if tb_container[point].time <= snow_custom.SPIRIT_DEPS_DELAY then
local delta = tb_container[point].time/snow_custom.SPIRIT_DEPS_DELAY
point.missile:set_height(tb_container[point].height - BEZIER2[delta]*PEAK)
point:set_revs(tb_container[point].rev + BEZIER2[delta]*tb_container[point].rev_delt)
else
point.missile:set_height(tb_container[point].height - PEAK)
point:set_revs(tb_container[point].rev + tb_container[point].rev_delt)
tb_container[point].height = point.missile:get_height()
tb_container[point].time = 0.
tb_container[point].orbit_phase = 2
point:on_update(m_snow.phase_two)
end
PointMissileEnum(point)
return theta
end
function m_snow.clearMissile(point)
tb_container[point].caster = nil
tb_container[point].rev = nil
tb_container[point].rev_delt = nil
tb_container[point].phase = nil
tb_container[point].orbit_phase = nil
tb_container[point].tempGrp = nil
tb_container[point].height = nil
tb_container[point].time = nil
tb_container[point] = nil
end
function m_snow.onOrbitLand()
local point = Missile.Orbit.current
local unit = point:get_target()
DestroyGroup(tb_container[point].tempGrp)
if m_chill then
m_chill.onSpiritFreeze(point.missile:get_x(), point.missile:get_y(), unit)
end
m_snow.clearMissile(point)
point:destroy()
end
function m_snow.onPointLand()
local point = Missile.Point.current
local unit = tb_container[point].target
if m_chill then
m_chill.onSpiritFreeze(point.missile:get_x(), point.missile:get_y(), unit)
end
tb_container[point].target = nil
tb_container[point].height = nil
tb_container[point].dist = nil
tb_container[point] = nil
point:destroy()
end
function m_snow.hasSpirits(whichunit)
return tb_container[whichunit] and tb_container[whichunit].spirits.size > 0
end
function m_snow.throwSpirit(whichunit, tx, ty)
if not m_snow.hasSpirits(whichunit) then
return
end
-- Get a snow spirit
local min_theta = 2*math.pi
local point
for point2 in tb_container[whichunit].spirits:iterator() do
-- Get the minimal difference between yaw and math.atan
local theta = math.fmod(math.atan(ty - point2.missile:get_y(), tx - point2.missile:get_x()), 2*math.pi)
local phi = point2.missile:get_yaw()
if math.abs(phi - theta) <= min_theta then
point = point2
min_theta = math.abs(phi - theta)
end
end
tb_container[whichunit].spirits:erase(tb_container[point].pointer)
tb_container[point].pointer = nil
if tb_container[point].phase == PHASES._TRANSITION then
tb_container[point].time = nil
tb_container[point].radius = nil
tb_container[point].phase = nil
tb_container[point].target = whichunit
else
m_snow.clearMissile(point)
point:on_land(nil)
point = Missile.change_type(point, "Orbit", "Point")
tb_container[point] = {target = whichunit}
end
tb_container[point].height = point.missile:get_height()
tb_container[point].dist = (tx - point.missile:get_x())*(tx - point.missile:get_x()) +
(ty - point.missile:get_y())*(ty - point.missile:get_y())
point.missile:set_turn_rate(360)
point:set_target_pos(tx, ty)
if m_chill then
point.missile:set_speed(m_chill.custom.SPIRIT_SPEED)
point:on_update(m_chill.onHeightUpdate)
end
point:on_land(m_snow.onPointLand)
point:launch()
doAfter(0.50, function(point)
point.missile:set_turn_rate(-1)
end, point)
if m_chill and tb_container[whichunit].spirits.size <= 0 then
m_chill.disableAbility(whichunit)
end
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_CHANNEL, snow_custom.ABILITY_ID, function()
local tb, unit; unit = GetTriggerUnit()
local spirit_count = 0
if not tb_container[unit] then
tb = {caster=unit, spirits=LinkedList:create()}
tb_container[unit] = tb
else
tb = tb_container[unit]
end
tb.timer = CreateTimer()
tb.level = GetUnitAbilityLevel(unit, snow_custom.ABILITY_ID)
TimerStart(tb.timer, snow_custom.SPIRIT_INTERVAL[tb.level], true, function()
-- Due to floating point errors, there may be more spirits created than intended.
spirit_count = spirit_count + 1
if spirit_count > snow_custom.SPIRIT_COUNT[tb.level] then
return
end
local theta = GetRandomReal(0, 2*math.pi)
local cx, cy = GetUnitX(unit), GetUnitY(unit)
local point = Missile.Point:create(snow_custom.SPIRIT_MODEL, cx, cy)
local j = spirit_count
local dist = snow_custom.SPIRIT_DIST*j
local speed = dist/snow_custom.SPIRIT_PEAK_DELAY
point.missile:set_height(50.)
point.missile:set_scale(0.25)
if m_chill then
m_chill.enableAbility(unit)
end
tb_container[point] = {time=0., height=point.missile:get_height(), target=unit, radius=dist}
tb_container[point].pointer = select(3, tb.spirits:insert(point))
tb_container[point].phase = PHASES._TRANSITION
point:set_target_pos(cx + dist*math.cos(theta), cy + dist*math.sin(theta))
point.missile:set_speed(speed)
point:on_update(function(theta)
local point = Missile.Point.current
tb_container[point].time = tb_container[point].time + INTERVAL
if tb_container[point].time < PERIOD then
point.missile:set_height(tb_container[point].height + PEAK*BEZIER[tb_container[point].time/PERIOD])
else
point.missile:set_height(tb_container[point].height + PEAK)
end
return theta
end)
point:on_land(function()
local point = Missile.Point.current
local unit, radius = tb_container[point].target, tb_container[point].radius
local speed = point.missile:get_speed()
local rev_time = speed/radius/(2*math.pi)
point.missile:set_height(tb_container[point].height + PEAK)
tb_container[unit].spirits:erase(tb_container[point].pointer)
tb_container[point].pointer = nil
tb_container[point] = nil
-- Switch over
point = Missile.change_type(point, "Point", "Orbit")
point:set_radius(radius)
point:set_target(unit)
point:set_revs(rev_time)
point:theta_from_unit("set")
tb_container[point] = {time=0., height=point.missile:get_height(), rev=rev_time}
tb_container[point].caster = unit
tb_container[point].pointer = select(3, tb.spirits:insert(point))
tb_container[point].rev_delt = snow_custom.SPIRIT_PEAK_REV - rev_time
tb_container[point].tempGrp = CreateGroup()
tb_container[point].phase = PHASES._ACTIVE
tb_container[point].orbit_phase = 1
if m_snow.deactivate then
tb_container[unit].spirits:erase(tb_container[point].pointer)
tb_container[point].orbit_phase = 3
tb_container[point].pointer = nil
point:on_update(m_snow.phase_three)
if m_chill then
if tb_container[unit].spirits.size <= 0 then
m_chill.disableAbility(unit)
end
end
else
point:on_update(m_snow.phase_one)
end
point:on_land(m_snow.onOrbitLand)
point:launch()
end)
point:launch()
end)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, snow_custom.ABILITY_ID, function()
local tb = tb_container[GetTriggerUnit()]
DestroyTimer(tb.timer)
tb.timer = nil
end)
Initializer.register(function()
ENUM_GRP = snow_custom.ENUM_GROUP
if Necrovizier.ChillingBlast then
m_chill = getmetatable(Necrovizier.ChillingBlast)
end
end)
local function Deactivate(unit)
tb_container[unit].caster = nil
tb_container[unit].level = nil
-- tb_container[unit].timer = nil
m_snow.deactivate = true
for point in tb_container[unit].spirits:iterator() do
if tb_container[point].phase == PHASES._TRANSITION then
point:land()
else
tb_container[unit].spirits:erase(tb_container[point].pointer)
tb_container[point].time = 0.
tb_container[point].orbit_phase = 3
tb_container[point].pointer = nil
point:on_update(m_snow.phase_three)
if m_chill and tb_container[unit].spirits.size <= 0 then
m_chill.disableAbility(unit)
end
end
end
m_snow.deactivate = false
tb_container[unit] = nil
end
UnitState.register("DEATH_EVENT", function()
if tb_container[UnitState.eventUnit] then
Deactivate(UnitState.eventUnit)
end
end)
UnitDex.register("LEAVE_EVENT", function()
if tb_container[UnitDex.eventUnit] then
Deactivate(UnitDex.eventUnit)
if m_chill then
m_chill.removeEntry(unit)
end
end
end)
end
end
Necrovizier.ChillingBlast = setmetatable({}, protected_t())
do
local chill = Necrovizier.ChillingBlast
local snow = Necrovizier.SnowSpirits
local m_chill = getmetatable(chill)
local m_snow = getmetatable(snow)
local BEZ = BezierEasing.create(0, 0.4, 0.4, 1)
m_chill.disable = {}
m_chill.custom = {
ABILITY_ID = FourCC("A00M"),
COLD_NOVA_ID = FourCC("A00N"),
COLD_NOVA_DMG = {20, 35, 50},
COLD_NOVA_ORDER = "entanglingroots",
CHILL_AOE = {},
DEFAULT_TEXT = {},
SPIRIT_SPEED = 1000.,
REQUIRE_TEXT = "|n|n|cffffcc00Requires at least one Snow Spirit.|r",
NOVA_MODEL1 = "war3mapImported\\ArcaneExplosion.mdx",
NOVA_MODEL2 = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl",
ATTACK_TYPE = ATTACK_TYPE_NORMAL,
DAMAGE_TYPE = DAMAGE_TYPE_COLD,
}
Initializer.registerBJ("USER", function()
local j = tonumber(ObjectReader.read(m_chill.custom.ABILITY_ID, "levels"))
m_chill.custom.DUMMY_UNIT = DummyUtils.request(DummyUtils.PLAYER)
UnitAddAbility(m_chill.custom.DUMMY_UNIT, m_chill.custom.COLD_NOVA_ID)
UnitAddAbility(m_chill.custom.DUMMY_UNIT, m_chill.custom.ABILITY_ID)
for i = 1, j do
m_chill.custom.CHILL_AOE[i] = tonumber(ObjectReader.read(m_chill.custom.ABILITY_ID, "Area" .. tostring(i), ",."))
m_chill.custom.DEFAULT_TEXT[i] = BlzGetAbilityExtendedTooltip(m_chill.custom.ABILITY_ID, i - 1)
end
m_chill.custom.ENUM_GROUP = CreateGroup()
m_chill.custom.DEFAULT_TEXT = BlzGetAbilityExtendedTooltip(m_chill.custom.ABILITY_ID, 0)
UnitRemoveAbility(m_chill.custom.DUMMY_UNIT, m_chill.custom.ABILITY_ID)
end)
function m_chill.filterTarget(target, caster)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster))
and not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)
and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
and not IsUnitType(target, UNIT_TYPE_FLYING)
and not BlzIsUnitInvulnerable(target)
end
function m_chill.doNova(target, level)
SetUnitAbilityLevel(m_chill.custom.DUMMY_UNIT, m_chill.custom.COLD_NOVA_ID, level)
SetUnitX(m_chill.custom.DUMMY_UNIT, GetUnitX(target))
SetUnitY(m_chill.custom.DUMMY_UNIT, GetUnitY(target))
BlzUnitDisableAbility(m_chill.custom.DUMMY_UNIT, FourCC("Amov"), true, false)
IssueTargetOrder(m_chill.custom.DUMMY_UNIT, m_chill.custom.COLD_NOVA_ORDER, target)
BlzUnitDisableAbility(m_chill.custom.DUMMY_UNIT, FourCC("Amov"), false, false)
end
function m_chill.artFunc()
local j = 3
return function()
j = j - 1
return j >= 0
end
end
function m_chill.burstArt(cx, cy)
-- Add some art here.
doUntil(0.05, m_chill.artFunc(), function()
DestroyEffect( AddSpecialEffect(m_chill.custom.NOVA_MODEL1, cx, cy))
end)
local fx = AddSpecialEffect(m_chill.custom.NOVA_MODEL2, cx, cy)
SetEffectScale(fx, 2.5)
BlzSetSpecialEffectTimeScale(fx, 0.8)
doAfter(1.25, function()
ShowEffect(fx, false)
DestroyEffect(fx)
end)
end
function m_chill.onSpiritFreeze(tx, ty, caster)
local level = GetUnitAbilityLevel(caster, m_chill.custom.ABILITY_ID)
m_chill.burstArt(tx, ty)
if level <= 0 then return end
GroupEnumUnitsInRange(m_chill.custom.ENUM_GROUP, tx, ty, m_chill.custom.CHILL_AOE[level], nil)
ForGroup(m_chill.custom.ENUM_GROUP, function()
local unit = GetEnumUnit()
if m_chill.filterTarget(unit, caster) then
UnitDamageTarget(caster, unit, m_chill.custom.COLD_NOVA_DMG[level], false, true,
m_chill.custom.ATTACK_TYPE, m_chill.custom.DAMAGE_TYPE, nil)
m_chill.doNova(unit, level)
end
end)
end
function m_chill.onHeightUpdate(theta)
local point = Missile.Point.current
local cx, cy = point.missile:get_x(), point.missile:get_y()
local tx, ty = point.tx, point.ty
local delta = (tx-cx)*(tx-cx) + (ty-cy)*(ty-cy)
delta = 1 - math.min(math.max(delta/m_snow.container[point].dist, 0), 1)
point.missile:set_height(m_snow.container[point].height*BEZ[delta])
return theta
end
function m_chill.disableAbility(caster)
if not m_chill.disable[caster] then
m_chill.disable[caster] = -1
BlzUnitDisableAbility(caster, m_chill.custom.ABILITY_ID, true, false)
else
if m_chill.disable[caster] > -1 then
m_chill.disable[caster] = m_chill.disable[caster] - 1
BlzUnitDisableAbility(caster, m_chill.custom.ABILITY_ID, true, false)
end
end
end
function m_chill.enableAbility(caster)
if not m_chill.disable[caster] then
m_chill.disable[caster] = 0
else
if m_chill.disable[caster] < 0 then
m_chill.disable[caster] = m_chill.disable[caster] + 1
BlzUnitDisableAbility(caster, m_chill.custom.ABILITY_ID, false, false)
end
end
end
function m_chill.removeEntry(caster)
m_chill.disable[caster] = nil
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_chill.custom.ABILITY_ID, function()
local tx, ty = GetSpellTargetX(), GetSpellTargetY()
m_snow.throwSpirit(GetTriggerUnit(), tx, ty)
end)
SpellEvent.register_spell(EVENT_PLAYER_HERO_SKILL, m_chill.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
if not m_snow.hasSpirits(unit) then
m_chill.disableAbility(unit)
end
end)
end
Necrovizier.Quintessence = setmetatable({}, protected_t())
do
local m_quint = getmetatable(Necrovizier.Quintessence)
m_quint.custom = {
ABILITY_ID = FourCC("A00O"),
LIFE_STEAL = {0.05, 0.1, 0.15},
RESIST_EFFECT = {0.1, 0.2, 0.3},
LIFE_STEAL_EFFECT = "war3mapImported\\Heal Blue.mdx",
DAMAGE_TYPE = DAMAGE_TYPE_COLD,
}
m_quint.display = {}
DamageEvent.register_after_damage(function()
local unit = DamageEvent.current.source
local level = GetUnitAbilityLevel(unit, m_quint.custom.ABILITY_ID)
if level == 0 then return end
if DamageEvent.current.dmgtype == m_quint.custom.DAMAGE_TYPE then
SetWidgetLife(unit, GetWidgetLife(unit) + DamageEvent.current.dmg*m_quint.custom.LIFE_STEAL[level])
if not m_quint.display[unit] then
m_quint.display[unit] = true
local fx = AddSpecialEffectTarget(m_quint.custom.LIFE_STEAL_EFFECT, unit, "origin")
SetEffectScale(fx, 0.20)
DestroyEffect(fx, 0.45)
doAfter(1.00, function(unit)
m_quint.display[unit] = nil
end, unit)
end
end
end)
DamageEvent.register_modifier("MODIFIER_EVENT_DELTA", function()
local unit = DamageEvent.current.target
local level = GetUnitAbilityLevel(unit, m_quint.custom.ABILITY_ID)
if level == 0 then return end
if DamageEvent.current.dmgtype == m_quint.custom.DAMAGE_TYPE then
DamageEvent.current.dmg = DamageEvent.current.dmg * (1 - m_quint.custom.RESIST_EFFECT[level])
end
end)
end
Necrovizier.BackToWork = setmetatable({}, readonly_t())
do
local work = Necrovizier.BackToWork
local m_work = getmetatable(work)
m_work.custom = {
ABILITY_ID = FourCC("A00P"),
TRANSMUTE_ID = FourCC("A00Q"),
BUFF_ID = FourCC("B009"),
TRANSMUTE_ORDER = "polymorph",
RACE_LEVEL = {
[RACE_HUMAN] = 1,
[RACE_ORC] = 2,
[RACE_UNDEAD] = 3,
[RACE_NIGHTELF] = 4,
[RACE_DEMON] = 1,
[RACE_OTHER] = 3,
},
MISSILE_MODEL = "war3mapImported\\SoundWave.mdx",
CASTER_MODEL = "Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl",
MISSILE_SPEED = 1200.,
MISSILE_DIST = 1400.,
DAMAGE_AMOUNT = 175.,
ENUM_RANGE = 300.,
ATTACK_TYPE = ATTACK_TYPE_NORMAL,
DAMAGE_TYPE = DAMAGE_TYPE_COLD,
}
m_work.monitor = {}
Initializer.register(function()
m_work.custom.DUMMY_UNIT = DummyUtils.request(DummyUtils.PLAYER)
m_work.custom.GROUP = Group()
m_work.custom.BUFF_DUR = tonumber(ObjectReader.read(m_work.custom.TRANSMUTE_ID, "Dur1", ",."))
UnitAddAbility(m_work.custom.DUMMY_UNIT, m_work.custom.TRANSMUTE_ID)
m_work.custom.DUMMY_ABIL = BlzGetUnitAbility(m_work.custom.DUMMY_UNIT, m_work.custom.TRANSMUTE_ID)
DisableUnitMovement(m_work.custom.DUMMY_UNIT)
end)
function m_work.get_race_id(race)
return m_work.custom.RACE_LEVEL[race] or 4
end
function m_work.transmute(target)
SetUnitX(m_work.custom.DUMMY_UNIT, GetUnitX(target))
SetUnitY(m_work.custom.DUMMY_UNIT, GetUnitY(target))
local level = m_work.get_race_id(GetUnitRace(target))
SetUnitAbilityLevel(m_work.custom.DUMMY_UNIT, m_work.custom.TRANSMUTE_ID, level)
IssueTargetOrder(m_work.custom.DUMMY_UNIT, m_work.custom.TRANSMUTE_ORDER, target)
if not m_work.monitor[target] then
m_work.monitor[target] = {time=m_work.custom.BUFF_DUR, buff=BuffUtils:watch(m_work.custom.BUFF_ID, target)}
m_work.monitor[target].debuff = CreateTimer()
TimerStart(m_work.monitor[target].debuff, m_work.custom.BUFF_DUR, false, nil)
m_work.monitor[target].buff:on_dispel(function()
-- Check if buff is still applicable.
local remaining = TimerGetRemaining(m_work.monitor[target].debuff)
if remaining > 0 and UnitAlive(target) then
doAfter(0.00, function(time)
local level = m_work.get_race_id(GetUnitRace(target))
BlzSetAbilityRealLevelField(m_work.custom.DUMMY_ABIL, ABILITY_RLF_DURATION_NORMAL, level - 1, time)
BlzSetAbilityRealLevelField(m_work.custom.DUMMY_ABIL, ABILITY_RLF_DURATION_HERO, level - 1, time)
m_work.transmute(target)
BlzSetAbilityRealLevelField(m_work.custom.DUMMY_ABIL, ABILITY_RLF_DURATION_NORMAL, level - 1, m_work.custom.BUFF_DUR)
BlzSetAbilityRealLevelField(m_work.custom.DUMMY_ABIL, ABILITY_RLF_DURATION_HERO, level - 1, m_work.custom.BUFF_DUR)
m_work.monitor[target].debuff = CreateTimer()
TimerStart(m_work.monitor[target].debuff, time, false, nil)
end, remaining)
end
DestroyTimer(m_work.monitor[target].debuff)
m_work.monitor[target] = nil
end)
else
PauseTimer(m_work.monitor[target].debuff)
TimerStart(m_work.monitor[target].debuff, 0., false, nil)
PauseTimer(m_work.monitor[target].debuff)
TimerStart(m_work.monitor[target].debuff, m_work.custom.BUFF_DUR, false, nil)
end
BlzUnitDisableAbility(m_work.custom.DUMMY_UNIT, FourCC("Amov"), false, false)
end
function m_work.getSound()
local snd = CreateSound("CustomSounds\\KelThuzadWarcry.wav", false, true, false, 10, 10, "DefaultEAXON")
SetSoundParamsFromLabel(snd, "KelthuzadWarcry")
SetSoundVolume(snd, 127)
SetSoundDuration(snd, 2099)
return snd
end
function m_work.filterTarget(target, caster)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster))
and not IsUnitType(target, UNIT_TYPE_MECHANICAL)
and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_work.custom.ABILITY_ID, function()
local tb = {unit = GetTriggerUnit(), group = CreateGroup()}
local point = Missile.Point:create(m_work.custom.MISSILE_MODEL, GetUnitX(tb.unit), GetUnitY(tb.unit))
local snd = m_work.getSound()
local theta = math.atan(GetSpellTargetY() - GetUnitY(tb.unit), GetSpellTargetX() - GetUnitX(tb.unit))
doAfter(0.00, function(snd)
DestroyEffect( AddSpecialEffectTarget(m_work.custom.CASTER_MODEL, tb.unit, "origin"), 1.00)
AttachSoundToUnit(snd, tb.unit)
-- SetSoundPosition(snd, GetUnitX(tb.unit), GetUnitY(tb.unit), 0)
StartSound(snd)
KillSoundWhenDone(snd)
end, snd)
point.missile:set_speed(m_work.custom.MISSILE_SPEED)
point.missile:set_scale(7.5)
point:set_target_pos(point.missile:get_x() + m_work.custom.MISSILE_DIST*math.cos(theta),
point.missile:get_y() + m_work.custom.MISSILE_DIST*math.sin(theta))
point:on_update(function(theta)
GroupEnumUnitsInRange(m_work.custom.GROUP, point.missile:get_x(), point.missile:get_y(),
m_work.custom.ENUM_RANGE, nil)
ForGroup(m_work.custom.GROUP, function()
local uu = GetEnumUnit()
if m_work.filterTarget(uu, tb.unit) and not IsUnitInGroup(uu, tb.group) then
if not IsUnitType(uu, UNIT_TYPE_SUMMONED) then
UnitDamageTarget(tb.unit, uu, m_work.custom.DAMAGE_AMOUNT, false, true,
m_work.custom.ATTACK_TYPE, m_work.custom.DAMAGE_TYPE, nil)
else
UnitDamageTargetPure(tb.unit, uu, GetUnitMaxHP(uu))
end
GroupAddUnit(tb.group, uu)
m_work.transmute(uu)
end
end)
return theta
end)
point:on_land(function()
DestroyGroup(tb.group)
tb.group, tb.unit = nil
tb = nil
point:destroy()
end)
point:launch()
end)
end
UndyingCommander = {}
do
local epitaph = {}
local m_epitaph = protected_t()
setmetatable(epitaph, m_epitaph)
UndyingCommander.WarriorsEpitaph = epitaph
m_epitaph.custom = {
ABILITY_ID = FourCC("A015"),
SUMMON_ABIL_ID = FourCC("A017"),
SUMMON_ORDER = "waterelemental",
SKELETON_COUNT = {3, 3, 3},
SUMMON_SCALE = {1, 1.3, 1.6},
SUMMON_EFFECT_MODEL = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl",
EFFECT_ID = "war3mapImported\\Desecrate_JFI (Green).mdx",
EFFECT_DEF_RADIUS = 125.,
PERSISTENT_STAMP = 0.20,
PERSISTENT_LOOP = 4.00,
PERSISTENT_PRELUDE = 0.00,
}
Initializer.register(function()
m_epitaph.custom.DUMMY_UNIT = DummyUtils.request()
m_epitaph.custom.DURATION = {}
m_epitaph.custom.SPAWN_INTERVAL = {}
m_epitaph.custom.AOE = {}
UnitAddAbility(m_epitaph.custom.DUMMY_UNIT, m_epitaph.custom.SUMMON_ABIL_ID)
local j = tonumber(ObjectReader.read(m_epitaph.custom.ABILITY_ID, "levels"))
for i = 1,j do
local dur = tonumber(ObjectReader.read(m_epitaph.custom.ABILITY_ID, "Dur" .. tostring(i), ",."))
m_epitaph.custom.DURATION[i] = dur
m_epitaph.custom.SPAWN_INTERVAL[i] = dur/m_epitaph.custom.SKELETON_COUNT[i]
m_epitaph.custom.AOE[i] = tonumber(ObjectReader.read(m_epitaph.custom.ABILITY_ID, "Area" .. tostring(i), ",."))
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SUMMON, function()
local summoner = GetSummoningUnit()
local summoned = GetTriggerUnit()
if summoner == m_epitaph.custom.DUMMY_UNIT then
m_epitaph.custom.SUMMONED_UNIT = summoned
end
end)
end)
function m_epitaph.summon(caster, level, x, y)
x, y = x or GetUnitX(caster), y or GetUnitY(caster)
local rad = math.random()*2*math.pi
local dist = math.random()*m_epitaph.custom.AOE[level]
local tx, ty = x + dist*math.cos(rad), y + dist*math.sin(rad)
SetUnitX(m_epitaph.custom.DUMMY_UNIT, tx)
SetUnitY(m_epitaph.custom.DUMMY_UNIT, ty)
SetUnitAbilityLevel(m_epitaph.custom.DUMMY_UNIT, m_epitaph.custom.SUMMON_ABIL_ID, level)
IssueImmediateOrder(m_epitaph.custom.DUMMY_UNIT, m_epitaph.custom.SUMMON_ORDER)
SetUnitOwner(m_epitaph.custom.SUMMONED_UNIT, GetOwningPlayer(caster), true)
SetUnitX(m_epitaph.custom.SUMMONED_UNIT, tx)
SetUnitY(m_epitaph.custom.SUMMONED_UNIT, ty)
SetUnitFacing(m_epitaph.custom.SUMMONED_UNIT, bj_UNIT_FACING)
SetUnitAnimation(m_epitaph.custom.SUMMONED_UNIT, "birth")
-- Add some special fx.
doAfter(0.00, function(summoned, tx, ty)
local fx = AddSpecialEffect(m_epitaph.custom.SUMMON_EFFECT_MODEL, GetUnitX(summoned) or tx, GetUnitY(summoned) or ty)
SetEffectScale(fx, m_epitaph.custom.SUMMON_SCALE[level])
DestroyEffect(fx, 1.00)
end, m_epitaph.custom.SUMMONED_UNIT, tx, ty)
m_epitaph.custom.SUMMONED_UNIT = nil
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_epitaph.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local tb = {timer = CreateTimer(), skelly = 0}
tb.cx, tb.cy = GetUnitX(unit), GetUnitY(unit)
tb.level = GetUnitAbilityLevel(unit, m_epitaph.custom.ABILITY_ID)
tb.caster = unit
tb.effect_timer = CreateTimer()
tb.effect = AddSpecialEffect(m_epitaph.custom.EFFECT_ID, GetUnitX(unit), GetUnitY(unit))
SetEffectScale(tb.effect, m_epitaph.custom.AOE[tb.level]/m_epitaph.custom.EFFECT_DEF_RADIUS)
TimerStart(tb.effect_timer, m_epitaph.custom.DURATION[tb.level] - m_epitaph.custom.PERSISTENT_PRELUDE, false, nil)
doAfter(m_epitaph.custom.PERSISTENT_STAMP, function(tb)
doRepeat(m_epitaph.custom.PERSISTENT_LOOP, function()
if TimerGetRemaining(tb.effect_timer) <= 0.00 then
PauseTimer(GetExpiredTimer())
else
BlzSetSpecialEffectTime(tb.effect, m_epitaph.custom.PERSISTENT_STAMP)
end
end)
end, tb)
TimerStart(tb.timer, m_epitaph.custom.SPAWN_INTERVAL[tb.level], true, function()
if tb.skelly < m_epitaph.custom.SKELETON_COUNT[tb.level] then
m_epitaph.summon(tb.caster, tb.level, tb.cx, tb.cy)
tb.skelly = tb.skelly + 1
else
PauseTimer(tb.timer)
DestroyTimer(tb.timer)
DestroyTimer(tb.effect_timer)
DestroyEffect(tb.effect)
tb = nil
end
end)
end)
end
do
-- This is basically just adding a model effect whenever he swings.
local cleave = setmetatable({}, protected_t())
local m_cleave = getmetatable(cleave)
UndyingCommander.CleavingAttack = cleave
m_cleave.custom = {
ABILITY_ID = FourCC("A016"),
CLEAVE_EFFECT = "war3mapImported\\Culling Cleave Red.mdx"
}
DamageEvent.register_modifier("MODIFIER_EVENT_SYSTEM", function()
local source, target = DamageEvent.current.source, DamageEvent.current.target
if GetUnitAbilityLevel(source, m_cleave.custom.ABILITY_ID) ~= 0 then
if DamageEvent.current.dmgtype == DAMAGE_TYPE_NORMAL then
local cx, cy = GetUnitX(source), GetUnitY(source)
local tx, ty = GetUnitX(target), GetUnitY(target)
local theta = math.fmod(math.atan(ty - cy, tx - cx), 2*math.pi)
local fx = AddSpecialEffect(m_cleave.custom.CLEAVE_EFFECT, cx + 20*math.cos(theta), cy + 20*math.sin(theta))
SetEffectScale(fx, 1.80)
SetEffectYaw(fx, theta + 0.15)
BlzSetSpecialEffectTimeScale(fx, 0.75)
DestroyEffect(fx, 1.00)
end
end
end)
end
do
local chaos, m_chaos = {}, protected_t()
setmetatable(chaos, m_chaos)
UndyingCommander.ChaosPortal = chaos
m_chaos.custom = {
ABILITY_ID = FourCC("A018"),
VOID_BURN_ID = FourCC("A019"),
STOMP_ID = FourCC("A01A"),
VOID_BURN_ORDER = "rainoffire",
STOMP_ORDER = "thunderclap",
VOID_BURN_DELAY = 2.0,
CAST_EFFECT = "war3mapImported\\Nether Blast III.mdx",
HP_DAMAGE_MULT = {25, 30, 35},
HP_DAMAGE_MAX_REQ = {500, 400, 300},
PORTAL_DURATION = {15, 15, 15},
PORTAL_TARG_HEIGHT = {550, 550, 550},
PORTAL_TARG_MODEL = "war3mapImported\\Void Disc.mdx",
PORTAL_SOURCE_OFFSET = {300, 300, 300},
PORTAL_SOURCE_HEIGHT = {150, 150, 150},
PORTAL_SOURCE_MODEL = "war3mapImported\\Void Rift Purple.mdx",
PORTAL_SOURCE_BLINK = "war3mapImported\\Blink Purple Caster.mdx",
PORTAL_TARGET_BLINK = "war3mapImported\\Blink Purple Target.mdx",
PORTAL_LAND_EFFECT = "war3mapImported\\DarkLightningNova.mdx",
PORTAL_SOURCE_DELAY = 1.00,
PORTAL_RECT_WIDTH = 100.,
PORTAL_RECT_LENGTH = 240.,
PORTAL_ENUM_INTERVAL = 0.10,
PORTAL_ENUM_CLEAR_DUR = 0.50,
PORTAL_BASE_LENGTH = 130,
PORTAL_BASE_SCALE = 0.625,
MAX_HEIGHT_FOR_STUN = 30.0,
HEIGHT_SUSPEND_DUR = 0.50,
HEIGHT_BEZIER = BezierEasing.create(0, 0, 0.5, 0),
ATTACK_TYPE = ATTACK_TYPE_NORMAL,
DAMAGE_TYPE = DAMAGE_TYPE_FIRE,
}
m_chaos.custom.PORTAL_ENUM_LIST = ClassTimer:create(m_chaos.custom.PORTAL_ENUM_INTERVAL)
function m_chaos.filterTarget(target, caster)
return UnitAlive(target) and IsUnitAlly(target, GetOwningPlayer(caster))
end
function m_chaos.filterDamageTarget(target, caster)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster))
and not IsUnitType(target, UNIT_TYPE_FLYING)
and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
end
-- Preload the sub-ability
Initializer.register(function()
local dummy = DummyUtils.request()
UnitAddAbility(dummy, m_chaos.custom.VOID_BURN_ID); UnitRemoveAbility(dummy, m_chaos.custom.VOID_BURN_ID)
UnitAddAbility(dummy, m_chaos.custom.STOMP_ID)
m_chaos.custom.DUMMY_UNIT = dummy
m_chaos.custom.TELEPORT_GRP = CreateGroup()
m_chaos.custom.ENUM_GROUP = Group()
-- Getting PORTAL_RADIUS
local l, w = m_chaos.custom.PORTAL_RECT_LENGTH, m_chaos.custom.PORTAL_RECT_WIDTH
m_chaos.custom.PORTAL_RECT_RADIUS = (l*l + w*w)^(0.5)
m_chaos.custom.AOE = tonumber(ObjectReader.read(m_chaos.custom.STOMP_ID, "Area1", ",."))
end)
do
local custom = m_chaos.custom
function m_chaos.inRectangle(targ, tb, cx, cy)
local tx, ty = GetUnitX(targ), GetUnitY(targ)
tx, ty = (tx - cx)*math.cos(tb.theta) + (ty - cy)*math.sin(tb.theta),
(tx - cx)*math.sin(tb.theta) - (ty - cy)*math.cos(tb.theta)
return math.abs(tx)/custom.PORTAL_RECT_LENGTH <= 0.5 and
math.abs(ty)/custom.PORTAL_RECT_WIDTH <= 0.5
end
function m_chaos.doDamage(caster, level)
local cx, cy = GetUnitX(caster), GetUnitY(caster)
local hp_dmg = custom.HP_DAMAGE_MULT[level]
hp_dmg = hp_dmg*math.floor(GetUnitMaxHP(caster)/custom.HP_DAMAGE_MAX_REQ[level])
if hp_dmg > 0 then
GroupEnumUnitsInRange(custom.ENUM_GROUP, cx, cy, custom.AOE, nil)
GroupRemoveUnit(custom.ENUM_GROUP, caster)
ForGroup(custom.ENUM_GROUP, function()
local uu = GetEnumUnit()
if m_chaos.filterDamageTarget(uu, caster) then
UnitDamageTarget(caster, uu, hp_dmg, false, true, custom.ATTACK_TYPE, custom.DAMAGE_TYPE, nil)
end
end)
end
end
function m_chaos.doStomp(caster)
local cx, cy = GetUnitX(caster), GetUnitY(caster)
SetUnitX(custom.DUMMY_UNIT, cx); SetUnitY(custom.DUMMY_UNIT, cy)
DestroyEffect( AddSpecialEffect(custom.PORTAL_LAND_EFFECT, cx, cy), 2.00)
DisableUnitMovement(custom.DUMMY_UNIT)
SetUnitOwner(custom.DUMMY_UNIT, GetOwningPlayer(caster), false)
IssueImmediateOrder(custom.DUMMY_UNIT, custom.STOMP_ORDER)
SetUnitOwner(custom.DUMMY_UNIT, DummyUtils.PLAYER, false)
EnableUnitMovement(custom.DUMMY_UNIT)
end
function m_chaos.dropUnit(targ, tx, ty, level)
DestroyEffect( AddSpecialEffect(custom.PORTAL_SOURCE_BLINK, GetUnitX(targ), GetUnitY(targ)))
SetUnitFlyHeight(targ, custom.PORTAL_TARG_HEIGHT[level], 0.00)
SetUnitFlyHeight(targ, GetUnitDefaultFlyHeight(targ), custom.HEIGHT_SUSPEND_DUR, custom.HEIGHT_BEZIER)
SetUnitX(targ, tx); SetUnitY(targ, ty)
DestroyEffect( AddSpecialEffectTarget(custom.PORTAL_SOURCE_BLINK, targ, "overhead"))
if GetUnitDefaultFlyHeight(targ) <= custom.MAX_HEIGHT_FOR_STUN then
doAfter(custom.HEIGHT_SUSPEND_DUR, function()
m_chaos.doStomp(targ)
m_chaos.doDamage(targ, level)
local cx, cy = GroundCoordinates(GetUnitX(targ), GetUnitY(targ))
SetUnitX(targ, cx); SetUnitY(targ, cy)
end)
end
end
custom.PORTAL_ENUM_LIST:on_callback(function(tb)
if tb.portal_start then
local cx, cy = GetEffectX(tb.portal_start), GetEffectY(tb.portal_start)
GroupEnumUnitsInRange(custom.ENUM_GROUP, cx, cy, custom.PORTAL_RECT_RADIUS, nil)
ForGroup(custom.ENUM_GROUP, function()
local uu = GetEnumUnit()
if m_chaos.filterTarget(uu, tb.caster) and not IsUnitInGroup(uu, custom.TELEPORT_GRP) then
-- Check if the target is within the rectangle.
if m_chaos.inRectangle(uu, tb, cx, cy) then
GroupAddUnit(custom.TELEPORT_GRP, uu)
doAfter(custom.PORTAL_ENUM_CLEAR_DUR, function(uu)
GroupRemoveUnit(custom.TELEPORT_GRP, uu)
end, uu)
m_chaos.dropUnit(uu, tb.tx, tb.ty, tb.level)
end
end
end)
end
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, custom.ABILITY_ID, function()
local tb = {caster = GetTriggerUnit(), tx = GetSpellTargetX(), ty = GetSpellTargetY()}
local cx, cy = GetUnitX(tb.caster), GetUnitY(tb.caster)
local theta = math.fmod(math.atan(tb.ty - cy, tb.tx - cx), 2*math.pi)
tb.level = GetUnitAbilityLevel(tb.caster, custom.ABILITY_ID)
cx = cx + custom.PORTAL_SOURCE_OFFSET[tb.level]*math.cos(theta)
cy = cy + custom.PORTAL_SOURCE_OFFSET[tb.level]*math.sin(theta)
tb.portal_end = AddSpecialEffect(custom.PORTAL_TARG_MODEL, tb.tx, tb.ty)
SetEffectHeight(tb.portal_end, custom.PORTAL_TARG_HEIGHT[tb.level])
SetEffectScale(tb.portal_end, custom.PORTAL_RECT_RADIUS/custom.PORTAL_BASE_LENGTH*custom.PORTAL_BASE_SCALE)
custom.PORTAL_ENUM_LIST:include(tb)
DestroyEffect( AddSpecialEffectTarget(custom.CAST_EFFECT, tb.caster, "origin"))
doAfter(custom.PORTAL_SOURCE_DELAY, function(tb)
local yaw = math.atan(tb.ty - cy, tb.tx - cx)
local roll = ((yaw > 0) and -math.pi/2) or math.pi/2
tb.theta = yaw + math.pi
tb.portal_start = AddSpecialEffect(custom.PORTAL_SOURCE_MODEL, cx, cy)
BlzSetSpecialEffectOrientation(tb.portal_start, yaw, roll, 0.00)
SetEffectHeight(tb.portal_start, custom.PORTAL_SOURCE_HEIGHT[tb.level])
doAfter(custom.VOID_BURN_DELAY, function()
tb.dummy = DummyUtils.request(GetOwningPlayer(tb.caster), tb.tx, tb.ty, 0, bj_UNIT_FACING)
UnitAddAbility(tb.dummy, custom.VOID_BURN_ID)
SetUnitAbilityLevel(tb.dummy, custom.VOID_BURN_ID, tb.level)
DisableUnitMovement(tb.dummy, true)
IssuePointOrder(tb.dummy, custom.VOID_BURN_ORDER, tb.tx, tb.ty)
end)
end, tb)
doAfter(custom.PORTAL_DURATION[tb.level], function(tb)
IssueImmediateOrderById(tb.dummy, 851972)
DisableUnitMovement(tb.dummy, false)
UnitRemoveAbility(tb.dummy, custom.VOID_BURN_ID)
custom.PORTAL_ENUM_LIST:remove(tb)
DestroyEffect(tb.portal_end)
DestroyEffect(tb.portal_start)
DummyUtils.recycle(tb.dummy)
end, tb)
tb = nil
end)
end
end
do
local will = {}
local m_will = protected_t()
setmetatable(will, m_will)
UndyingCommander.IndomitableWill = will
m_will.custom = {
ABILITY_ID = FourCC("A01B"),
BUFF_FLAG_ID = FourCC("B00I"),
BONUS_CRIT_ID = FourCC("A01C"),
ABILITY_REZ_ID = FourCC("A01D"),
DEACTIVATE_ORDER = "unimmolation",
SOUL_COUNT_FOR_REZ = 20,
SOUL_MAX = 80,
SOUL_UNITS = 1,
SOUL_HEROES = 5,
SOUL_COLLECT_RADIUS = 1200.,
SOUL_SPEED = 600.,
SOUL_PROJECTILE = "war3mapImported\\lostsoul.mdx",
REVIVE_NOTIF = "war3mapImported\\JH_SoulArmor.mdx",
HEALTH_COST = 0.04,
HEALTH_TICK = 0.10,
RECOVER_RATIO = 2.0,
RECOVER_EFFECT = "war3mapImported\\Heal Orange.mdx",
RECOVER_INTERVAL = 1.25,
RECOVER_DAMAGE_TYPE = DAMAGE_TYPE_FIRE,
}
m_will.custom.DEPLETE_HP_LIST = ClassTimer:create(m_will.custom.HEALTH_TICK)
m_will.is_displayed = {}
m_will.is_active = {}
m_will.souls = {}
m_will.is_rez_active = {}
m_will.notif_fx = {}
do
m_will.custom.HEALTH_COST = 1 - (1 - m_will.custom.HEALTH_COST)^(m_will.custom.HEALTH_TICK)
end
Initializer.register(function()
m_will.custom.TIMER = CreateTimer()
m_will.custom.GROUP = Group()
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, m_will.onDeactivate)
-- RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, m_will.onSoulCollect)
end)
function m_will.filterSoulCollect(unit)
return not IsUnitType(unit, UNIT_TYPE_MECHANICAL) and
not IsUnitType(unit, UNIT_TYPE_STRUCTURE)
end
function m_will.onDeactivate()
local order = OrderId2String(GetIssuedOrderId())
if order == m_will.custom.DEACTIVATE_ORDER then
local unit = GetTriggerUnit()
doAfter(0.00, function()
if m_will.is_active[unit] and GetUnitAbilityLevel(unit, m_will.custom.BUFF_FLAG_ID) == 0 then
m_will.is_active[unit] = nil
m_will.custom.DEPLETE_HP_LIST:remove(unit)
UnitRemoveAbility(unit, m_will.custom.BONUS_CRIT_ID)
end
end)
end
end
function m_will.onSoulCollect()
local dead = UnitState.eventUnit
local cx, cy = GetUnitX(dead), GetUnitY(dead)
if not m_will.filterSoulCollect(dead) then return end
local soul_count = (IsUnitType(dead, UNIT_TYPE_HERO) and m_will.custom.SOUL_HEROES) or m_will.custom.SOUL_UNITS
GroupEnumUnitsInRange(m_will.custom.GROUP, cx, cy, m_will.custom.SOUL_COLLECT_RADIUS, nil)
ForGroup(m_will.custom.GROUP, function()
local uu = GetEnumUnit()
if m_will.is_active[uu] then
local point = Missile.Point:create(m_will.custom.SOUL_PROJECTILE, cx, cy)
point.missile:set_speed(m_will.custom.SOUL_SPEED)
point:set_target_pos(GetUnitX(uu), GetUnitY(uu))
point:on_update(function(theta)
if GetUnitTypeId(uu) ~= 0 then
point:set_target_pos(GetUnitX(uu), GetUnitY(uu))
end
return theta
end)
point:on_land(function()
if m_will.is_active[uu] then
m_will.souls[uu] = math.min(m_will.souls[uu] + soul_count, m_will.custom.SOUL_MAX)
if not m_will.is_rez_active[uu] and m_will.souls[uu] >= m_will.custom.SOUL_COUNT_FOR_REZ then
m_will.is_rez_active[uu] = true
m_will.notif_fx[uu] = AddSpecialEffectTarget(m_will.custom.REVIVE_NOTIF, uu, "chest")
BlzUnitDisableAbility(uu, m_will.custom.ABILITY_REZ_ID, false, false)
end
end
point:destroy()
end)
point:launch()
end
end)
end
m_will.custom.DEPLETE_HP_LIST:on_callback(function(unit)
SetWidgetLife(unit, GetWidgetLife(unit)*(1 - m_will.custom.HEALTH_COST))
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_will.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
m_will.is_active[unit] = true
m_will.custom.DEPLETE_HP_LIST:include(unit)
if not m_will.souls[unit] then
m_will.souls[unit] = 0
UnitAddAbility(unit, m_will.custom.ABILITY_REZ_ID)
UnitMakeAbilityPermanent(unit, true, m_will.custom.ABILITY_REZ_ID)
BlzUnitDisableAbility(unit, m_will.custom.ABILITY_REZ_ID, true, false)
end
UnitAddAbility(unit, m_will.custom.BONUS_CRIT_ID)
UnitMakeAbilityPermanent(unit, true, m_will.custom.BONUS_CRIT_ID)
end)
DamageEvent.register_modifier("MODIFIER_EVENT_ALPHA", function()
local targ = DamageEvent.current.target
if GetUnitAbilityLevel(targ, m_will.custom.BUFF_FLAG_ID) ~= 0 then
-- Unit has activated the Will of the Council
if DamageEvent.current.dmgtype == m_will.custom.RECOVER_DAMAGE_TYPE then
DamageEvent.current.dmg = -math.abs(DamageEvent.current.dmg*m_will.custom.RECOVER_RATIO)
if not m_will.is_displayed[targ] then
m_will.is_displayed[targ] = true
DestroyEffect( AddSpecialEffectTarget(m_will.custom.RECOVER_EFFECT, targ, "origin"), 1.00)
doAfter(m_will.custom.RECOVER_INTERVAL, function()
m_will.is_displayed[targ] = nil
end)
end
end
end
end)
UnitState.register("DEATH_EVENT", function()
m_will.onSoulCollect()
local unit = UnitState.eventUnit
if m_will.souls[unit] then
m_will.souls[unit] = math.max(m_will.souls[unit] - m_will.custom.SOUL_COUNT_FOR_REZ, 0)
if m_will.is_rez_active[unit] and m_will.souls[unit] < m_will.custom.SOUL_COUNT_FOR_REZ then
DestroyEffect(m_will.notif_fx[unit])
m_will.is_rez_active[unit] = nil
m_will.notif_fx[unit] = nil
BlzUnitDisableAbility(unit, m_will.custom.ABILITY_REZ_ID, true, false)
end
end
end)
end
PillarSkeleton = {}
do
local shadow = setmetatable({}, protected_t())
local m_shadow = getmetatable(shadow)
local interval = Missile.INTERVAL
PillarSkeleton.ShadowPocket = shadow
m_shadow.custom = {
ABILITY_ID = FourCC("A01L"),
SHADOW_UNIT_ID = FourCC("u00J"),
SHADOW_BUFF_ID = FourCC("B00N"),
ARMOR_BUFF_ID = FourCC("A01O"),
ARMOR_BUFF_ORDER = "innerfire",
ANIM_PRELUDE = 0.2,
POCKET_OFFSET = 600,
MISSILE_SPEED = 600,
MISSILE_HEIGHT = 200,
PRELUDE_END_EFFECT = "Abilities\\Spells\\Undead\\Darksummoning\\DarkSummonTarget.mdl",
PRELUDE_MISSILE = "war3mapImported\\Void Rain Missile.mdl",
RELEASE_MODEL = "",
HIDE_DURATION = {3, 4, 5},
MAX_HP_HEAL = {0.15, 0.225, 0.3},
ALPHA_DUR = 1.5,
ALPHA_INTERVAL = 0.1,
BEZIER = BezierEase.inSine
}
m_shadow.custom.ALPHA_TICKS = math.floor(m_shadow.custom.ALPHA_DUR/m_shadow.custom.ALPHA_INTERVAL)
local missile_grad = m_shadow.custom.MISSILE_SPEED*interval
m_shadow.list = ClassTimer:create(m_shadow.custom.ALPHA_INTERVAL)
m_shadow.ready = {}
m_shadow.table = {}
m_shadow.alpha = {}
m_shadow.dum_tb = {}
Initializer.register(function()
m_shadow._TIMER = CreateTimer()
m_shadow.DUMMY_UNIT = DummyUtils.request()
UnitAddAbility(m_shadow.DUMMY_UNIT, m_shadow.custom.ARMOR_BUFF_ID)
end)
function m_shadow.addArmor(target, level)
SetUnitX(m_shadow.DUMMY_UNIT, GetUnitX(target)); SetUnitY(m_shadow.DUMMY_UNIT, GetUnitY(target));
DisableUnitMovement(m_shadow.DUMMY_UNIT)
SetUnitAbilityLevel(m_shadow.DUMMY_UNIT, m_shadow.custom.ARMOR_BUFF_ID, level)
IssueTargetOrder(m_shadow.DUMMY_UNIT, m_shadow.custom.ARMOR_BUFF_ORDER, target)
EnableUnitMovement(m_shadow.DUMMY_UNIT)
end
local function define_on_land(point, unit, selectflag)
point:on_land(function()
local tx, ty = point.missile:get_x(), point.missile:get_y()
local dummy = CreateUnit(GetOwningPlayer(unit), m_shadow.custom.SHADOW_UNIT_ID, tx, ty, GetUnitFacing(unit))
local tb = m_shadow.table[unit]
m_shadow.dum_tb[dummy] = unit
point:destroy()
UnitApplyTimedLife(dummy, m_shadow.custom.SHADOW_BUFF_ID, m_shadow.custom.HIDE_DURATION[tb.level])
if PlayerUtils.player == GetOwningPlayer(unit) and selectflag then
SelectUnit(dummy, true)
end
end)
end
local function define_on_update(point, tb, unit)
local cx, cy = GetUnitX(unit), GetUnitY(unit)
local dist = ((tb.tx - cx)*(tb.tx - cx) + (tb.ty - cy)*(tb.ty - cy))^(0.5)
local time = dist/m_shadow.custom.MISSILE_SPEED
local cust_time = 0.
local h = point.missile:get_height()
local prev_h = h
point:on_update(function(theta)
cust_time = cust_time + interval
local phi = math.min(cust_time/time, 1)*math.pi
local new_h = h + m_shadow.custom.MISSILE_HEIGHT*math.sin(phi)
local rho = -math.atan(new_h - prev_h, missile_grad)
prev_h = new_h
point.missile:set_height(new_h)
point.missile:set_orient(point.missile:get_yaw(), rho, point.missile:get_roll())
return theta
end)
end
m_shadow.list:on_callback(function(unit)
m_shadow.alpha[unit] = m_shadow.alpha[unit] + 1
if m_shadow.alpha[unit] >= m_shadow.custom.ALPHA_TICKS then
local tb = m_shadow.table[unit]
SetUnitVertexColor(unit, 255, 255, 255, 255)
m_shadow.list:remove(unit)
m_shadow.ready[unit] = false
m_shadow.alpha[unit] = nil
ShowUnit(unit, false)
SetUnitInvulnerable(unit, true)
BlzPauseUnitEx(unit, true) -- This will likely fire an endcast event.
m_shadow.ready[unit] = nil
local point = Missile.Point:create(m_shadow.custom.PRELUDE_MISSILE, GetUnitX(unit), GetUnitY(unit))
point.missile:set_speed(m_shadow.custom.MISSILE_SPEED)
point:set_target_pos(tb.tx, tb.ty)
define_on_update(point, tb, unit)
define_on_land(point, unit, IsUnitSelected(unit, GetOwningPlayer(unit)))
point:launch()
else
local delta = m_shadow.alpha[unit]/m_shadow.custom.ALPHA_TICKS
SetUnitVertexColor(unit, 255, 255, 255, 255 - math.floor(255*m_shadow.custom.BEZIER[delta]))
end
end)
function m_shadow.turnInvisible(unit)
m_shadow.list:include(unit)
m_shadow.alpha[unit] = 0
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_CHANNEL, m_shadow.custom.ABILITY_ID, function()
local tb = {caster = GetTriggerUnit()}
local facing = GetUnitFacing(tb.caster)*math.pi/180.
tb.level = GetUnitAbilityLevel(tb.caster, m_shadow.custom.ABILITY_ID)
m_shadow.table[tb.caster] = tb
tb.tx, tb.ty = GetUnitX(tb.caster) + m_shadow.custom.POCKET_OFFSET*math.cos(facing),
GetUnitY(tb.caster) + m_shadow.custom.POCKET_OFFSET*math.sin(facing)
tb.timer = CreateTimer()
TimerStart(tb.timer, m_shadow.custom.ANIM_PRELUDE, false, function()
DestroyEffect(AddSpecialEffect(m_shadow.custom.PRELUDE_END_EFFECT, GetUnitX(tb.caster), GetUnitY(tb.caster)),
m_shadow.custom.ALPHA_DUR)
PauseTimer(tb.timer)
DestroyTimer(tb.timer)
m_shadow.ready[tb.caster] = true
m_shadow.turnInvisible(tb.caster)
end)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, m_shadow.custom.ABILITY_ID, function()
local tb = m_shadow.table[GetTriggerUnit()]
if (m_shadow.ready[tb.caster] == nil) or m_shadow.list:is_in(tb.caster) then
-- The unit was interrupted before transformation.
m_shadow.table[tb.caster] = nil
if not m_shadow.ready[tb.caster] then
PauseTimer(tb.timer)
DestroyTimer(tb.timer)
else
m_shadow.ready[tb.caster] = nil
m_shadow.alpha[tb.caster] = nil
SetUnitVertexColor(tb.caster, 255, 255, 255, 255)
end
end
end)
UnitState.register("DEATH_EVENT", function()
local dummy = UnitState.eventUnit
if m_shadow.dum_tb[dummy] then
local unit = m_shadow.dum_tb[dummy]; m_shadow.dum_tb[dummy] = nil
local tb = m_shadow.table[unit]; m_shadow.table[unit] = nil
local tx, ty = GroundCoordinates(GetUnitX(dummy), GetUnitY(dummy))
SetUnitX(unit, tx); SetUnitY(unit, ty);
SetUnitInvulnerable(unit, false)
BlzPauseUnitEx(unit, false)
ShowUnit(unit, true)
m_shadow.addArmor(unit, tb.level)
SetWidgetLife(unit, GetWidgetLife(unit) + GetUnitMaxHP(unit)*m_shadow.custom.MAX_HP_HEAL[tb.level])
DestroyEffect( AddSpecialEffectTarget(m_shadow.custom.RELEASE_MODEL, unit, "origin"))
if PlayerUtils.player == GetOwningPlayer(dummy) and IsUnitSelected(dummy, GetOwningPlayer(dummy)) then
SelectUnit(unit, true)
end
end
end)
end
do
local arcane = setmetatable({}, protected_t())
m_arcane = getmetatable(arcane)
PillarSkeleton.ArcaneDissension = arcane
m_arcane.custom = {
ABILITY_ID = FourCC("A01P"),
BENEFACTOR_ABIL = LinkedList(FourCC("A01K")),
NOTIFIER_ABIL_ID = FourCC("A01Q"),
NOTIFIER_BUFF_ID = FourCC("B00P"),
BONUS_OBTAIN_EFFECT = "war3mapImported\\Call of Dread Green.mdx",
BAT_PER_STACK = {0.02, 0.03, 0.04},
DAMAGE_RED_PER_STACK = {0.04, 0.06, 0.08},
BAT_MAX = {0.1, 0.21, 0.4},
DAMAGE_RED_MAX = {0.2, 0.3, 0.4},
BONUS_DURATION = {7, 10, 13},
SILENCE_RESIST = {1, 1, 1},
}
m_arcane.resist_count = {}
m_arcane.dmg_resist = {}
m_arcane.bat_mod = {}
SpellEvent.register_spell(EVENT_PLAYER_HERO_SKILL, m_arcane.custom.ABILITY_ID, function()
local unit, level = GetTriggerUnit(), GetLearnedSkillLevel()
if not m_arcane.resist_count[unit] then
m_arcane.resist_count[unit] = 0
end
while m_arcane.custom.SILENCE_RESIST[level] > m_arcane.resist_count[unit] do
m_arcane.resist_count[unit] = m_arcane.resist_count[unit] + 1
BlzUnitDisableAbility(unit, m_arcane.custom.ABILITY_ID, false, false)
end
while m_arcane.custom.SILENCE_RESIST[level] < m_arcane.resist_count[unit] do
m_arcane.resist_count[unit] = m_arcane.resist_count[unit] - 1
BlzUnitDisableAbility(unit, m_arcane.custom.ABILITY_ID, true, false)
end
end)
function m_arcane.getBuffCount(unit)
local buffs = 0
-- UnitCountBuffsEx(whichUnit, removePositive, removeNegative, magic, physical, timedLife, aura, autoDispel)
buffs = buffs + UnitCountBuffsEx(unit, false, true, true, false, false, false, false)
return buffs
end
function m_arcane.refreshAbilities(unit)
UnitRemoveAbility(unit, m_arcane.custom.NOTIFIER_ABIL_ID)
for abilId in m_arcane.custom.BENEFACTOR_ABIL:iterator() do
BlzEndUnitAbilityCooldown(unit, abilId)
end
UnitAddAbility(unit, m_arcane.custom.NOTIFIER_ABIL_ID)
UnitMakeAbilityPermanent(unit, true, m_arcane.custom.NOTIFIER_ABIL_ID)
end
do
local custom = m_arcane.custom
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local level = GetUnitAbilityLevel(unit, custom.ABILITY_ID)
local buffs = m_arcane.getBuffCount(unit)
local dmg_red = math.min(buffs*custom.DAMAGE_RED_PER_STACK[level], custom.DAMAGE_RED_MAX[level])
local bat_incr = math.min(buffs*custom.BAT_PER_STACK[level], custom.BAT_MAX[level])
UnitRemoveBuffsEx(unit, false, true, true, false, false, false, false)
m_arcane.refreshAbilities(unit)
DestroyEffect( AddSpecialEffectTarget(custom.BONUS_OBTAIN_EFFECT, unit, "overhead"), 0.5)
if not m_arcane.dmg_resist[unit] then
m_arcane.dmg_resist[unit] = 0
m_arcane.bat_mod[unit] = LinkedList:create()
end
local bat_mod = BonusBaseAtkSpeed:multAtkSpeed(unit, 0, 1 + bat_incr)
local bat_point = select(3, m_arcane.bat_mod[unit]:insert(bat_mod))
local bonus = m_arcane.dmg_resist[unit]
bonus = bonus + (1 - bonus)*dmg_red
m_arcane.dmg_resist[unit] = bonus
doAfter(custom.BONUS_DURATION[level], function()
if m_arcane.bat_mod[unit] then
local bonus = m_arcane.dmg_resist[unit]
bonus = bonus - (1 - bonus)/(1 - dmg_red)
m_arcane.dmg_resist[unit] = bonus
m_arcane.bat_mod[unit]:erase(bat_point)
bat_mod:destroy()
if m_arcane.bat_mod[unit].size <= 0 then
m_arcane.bat_mod[unit]:destroy()
m_arcane.dmg_resist[unit] = nil
m_arcane.bat_mod[unit] = nil
UnitRemoveAbility(unit, m_arcane.custom.NOTIFIER_ABIL_ID)
UnitRemoveAbility(unit, m_arcane.custom.NOTIFIER_BUFF_ID)
end
end
end)
end)
DamageEvent.register_modifier("MODIFIER_EVENT_ALPHA", function()
local unit = DamageEvent.current.target
if m_arcane.dmg_resist[unit] then
DamageEvent.current.dmg = DamageEvent.current.dmg*(1 - m_arcane.dmg_resist[unit])
end
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
if m_arcane.bat_mod[unit] then
for bat_mod, bat_point in m_arcane.bat_mod[unit]:iterator() do
m_arcane.bat_mod[unit]:erase(bat_point)
bat_mod:destroy()
end
m_arcane.bat_mod[unit]:destroy()
m_arcane.dmg_resist[unit] = nil
m_arcane.bat_mod[unit] = nil
end
m_arcane.resist_count[unit] = nil
end)
end
end
do
local pits = setmetatable({}, protected_t())
local armpits = getmetatable(pits)
PillarSkeleton.PitsOfTheAbyss = pits
armpits.custom = {
ABILITY_ID = FourCC("A01R"),
SLOW_ABIL_ID = FourCC("A01S"),
STUN_ABIL_ID = FourCC("A01T"),
ROOT_ABIL_ID = FourCC("A01U"),
SLOW_ORDER = "slow",
STUN_ORDER = "firebolt",
ROOT_ORDER = "entanglingroots",
ABYSS_MAX_JUMPS = {3},
ABYSS_DAMAGE = {150},
ABYSS_JUMP_DISTANCE = {125},
ABYSS_JUMP_INTERVAL = {0.50},
ABYSS_RADIUS = {},
ABYSS_MODEL = "war3mapImported\\Conflagrate.mdx",
ATTACK_TYPE = ATTACK_TYPE_HERO,
DAMAGE_TYPE = DAMAGE_TYPE_ENHANCED,
}
Initializer.register(function()
local levels = tonumber(ObjectReader.read(armpits.custom.ABILITY_ID, "levels"))
for j = 1, levels do
armpits.custom.ABYSS_RADIUS[j] = tonumber(ObjectReader.read(armpits.custom.ABILITY_ID, "Area1", ",."))
end
armpits.DUMMY_UNIT = DummyUtils.request()
armpits._GROUP = Group()
UnitAddAbility(armpits.DUMMY_UNIT, armpits.custom.SLOW_ABIL_ID)
UnitAddAbility(armpits.DUMMY_UNIT, armpits.custom.STUN_ABIL_ID)
UnitAddAbility(armpits.DUMMY_UNIT, armpits.custom.ROOT_ABIL_ID)
end)
armpits.custom.ABIL_CAST = {armpits.custom.SLOW_ABIL_ID, armpits.custom.STUN_ABIL_ID, armpits.custom.ROOT_ABIL_ID}
armpits.custom.ORDER_CAST = {armpits.custom.SLOW_ORDER, armpits.custom.STUN_ORDER, armpits.custom.ROOT_ORDER}
function armpits.filterTarget(target, caster)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster))
and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
and not IsUnitType(target, UNIT_TYPE_FLYING)
end
function armpits.castTarget(target, i, level)
SetUnitX(armpits.DUMMY_UNIT, GetUnitX(target)); SetUnitY(armpits.DUMMY_UNIT, GetUnitY(target));
DisableUnitMovement(armpits.DUMMY_UNIT)
SetUnitAbilityLevel(armpits.DUMMY_UNIT, armpits.custom.ABIL_CAST[i], level)
IssueTargetOrder(armpits.DUMMY_UNIT, armpits.custom.ORDER_CAST[i], target)
EnableUnitMovement(armpits.DUMMY_UNIT)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, armpits.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local level = GetUnitAbilityLevel(unit, armpits.custom.ABILITY_ID)
local tx, ty = GetSpellTargetX(), GetSpellTargetY()
local theta = math.atan(ty - GetUnitY(unit), tx - GetUnitX(unit))
local i = 1
local func = function()
DestroyEffect( AddSpecialEffect(armpits.custom.ABYSS_MODEL, tx, ty), 0.75)
GroupEnumUnitsInRange(armpits._GROUP, tx, ty, armpits.custom.ABYSS_RADIUS[level], nil)
ForGroup(armpits._GROUP, function()
local uu = GetEnumUnit()
if armpits.filterTarget(uu, unit) then
armpits.castTarget(uu, i, level)
UnitDamageTarget(unit, uu, armpits.custom.ABYSS_DAMAGE[level], false, true,
armpits.custom.ATTACK_TYPE, armpits.custom.DAMAGE_TYPE, nil)
end
end)
tx, ty = tx + armpits.custom.ABYSS_JUMP_DISTANCE[level]*math.cos(theta), ty + armpits.custom.ABYSS_JUMP_DISTANCE[level]*math.sin(theta)
i = i + 1
end
func()
doRepeat(armpits.custom.ABYSS_JUMP_INTERVAL[level], function()
if i > armpits.custom.ABYSS_MAX_JUMPS[level] then
PauseTimer(GetExpiredTimer())
else
func()
end
end)
end)
end
VoidAssassin = {}
do
local m_chain = protected_t()
VoidAssassin.ChainThrust = setmetatable({}, m_chain)
m_chain.custom = {
ABILITY_ID = FourCC("A01V"),
ROPE_ID = "WHCH",
ROPE_MAX_COUNT = {3, 3, 3},
ROPE_VELOCITY = {800, 900, 1000},
ROPE_SEARCH_VELOCITY = {2500, 2500, 2500},
ROPE_SEARCH_DIST = {2500, 2500, 2500},
ROPE_SEARCH_FLY_TIME = 0.5,
ROPE_MOVE_INTERVAL = 1/32.,
ROPE_MAX_FLY_TIME = 1.25,
ROPE_RETRACT_DIST_RATIO = 1.25,
ROPE_END_HEIGHT = 0.,
ROPE_COLLISION_SIZE = 192.0,
SWING_ENUM_RADIUS = 150.0,
HOOK_MODEL = "war3mapImported\\TimberChainHead.mdx",
HOOK_OFFSET = -10.0,
}
m_chain.custom.ROPE_COLLISION_SIZE = m_chain.custom.ROPE_COLLISION_SIZE/2
m_chain._LIST = ClassTimer:create(m_chain.custom.ROPE_MOVE_INTERVAL)
m_chain.rope_list = {}
m_chain.coordinate = {x = {}, y = {}}
m_chain.level = {}
m_chain.cooling = {}
Initializer.register(function()
local levels = tonumber(ObjectReader.read(m_chain.custom.ABILITY_ID, "levels"))
for i = 1, levels do
m_chain.custom.ROPE_VELOCITY[i] = m_chain.custom.ROPE_VELOCITY[i]*m_chain.custom.ROPE_MOVE_INTERVAL
m_chain.custom.ROPE_VELOCITY[i] = m_chain.custom.ROPE_VELOCITY[i]/m_chain.custom.ROPE_SEARCH_FLY_TIME
m_chain.custom.ROPE_SEARCH_VELOCITY[i] = m_chain.custom.ROPE_SEARCH_VELOCITY[i]*m_chain.custom.ROPE_MOVE_INTERVAL
m_chain.custom.ROPE_SEARCH_VELOCITY[i] = m_chain.custom.ROPE_SEARCH_VELOCITY[i]/m_chain.custom.ROPE_SEARCH_FLY_TIME
end
m_chain.cleave = VoidAssassin.AerialCleave
m_chain.cleave_base = VoidAssassin.AerialCleaveBase
m_chain._GROUP = Group()
end)
function m_chain.filter_swing_target(target, caster)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster))
and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
and not IsUnitType(target, UNIT_TYPE_FLYING)
end
function m_chain.get_rect(tx, ty)
return Rect(tx - m_chain.custom.ROPE_COLLISION_SIZE, ty - m_chain.custom.ROPE_COLLISION_SIZE,
tx + m_chain.custom.ROPE_COLLISION_SIZE, ty + m_chain.custom.ROPE_COLLISION_SIZE)
end
function m_chain.filterTargetForPhase(target)
return ((not IsUnitType(target, UNIT_TYPE_STRUCTURE)) and UnitAlive(target)) or
(not UnitAlive(target))
end
function m_chain.disconnect(rope)
m_chain.rope_list[rope.fanny]:remove(rope)
rope.chain:destroy()
BlzSetSpecialEffectTimeScale(rope.hook, 1.00)
DestroyEffect(rope.hook)
EnableUnitMovement(rope.fanny)
end
function m_chain.disconnect_all(unit, exclude_unready)
if not m_chain.rope_list[unit] then return end
for rope in m_chain.rope_list[unit]:iterator() do
if (exclude_unready and rope.hit) or (not exclude_unready) then
m_chain.disconnect(rope)
end
end
if m_chain.rope_list[unit].size == 0 then
m_chain.rope_list[unit]:destroy()
m_chain.rope_list[unit] = nil
m_chain._LIST:remove(unit)
end
end
function m_chain.reset_ropes(unit)
for rope in m_chain.rope_list[unit]:iterator() do
if rope.hit then
rope.time = 0
end
end
end
function m_chain.update_hook(rope, tx, ty, hook_yaw)
SetEffectX(rope.hook, tx + m_chain.custom.HOOK_OFFSET*math.cos(rope.theta))
SetEffectY(rope.hook, ty + m_chain.custom.HOOK_OFFSET*math.sin(rope.theta))
SetEffectHeight(rope.hook, m_chain.custom.ROPE_END_HEIGHT)
BlzSetSpecialEffectOrientation(rope.hook, hook_yaw, 0, 0)
end
function m_chain.create_hook(rope, cx, cy)
rope.hook = AddSpecialEffect(m_chain.custom.HOOK_MODEL, cx, cy)
BlzSetSpecialEffectTime(rope.hook, 2.00)
BlzSetSpecialEffectTimeScale(rope.hook, 0.00)
SetEffectHeight(rope.hook, m_chain.custom.ROPE_END_HEIGHT)
SetEffectScale(rope.hook, 3.5)
end
function m_chain.is_terrain_safe(tx, ty)
if not IsTerrainWalkable(tx, ty) then
return IsTerrainWalkableEx(tx, ty)
end
return true
end
function m_chain.check_for_hit(rope, tx, ty, cx, cy)
if not IsTerrainWalkable(tx, ty) and not IsTerrainWalkableEx(tx, ty) then
-- Check if there are any destructables present
-- Move this later into an independent function.
if IsTerrainPathable(tx, ty, PATHING_TYPE_WALKABILITY) then
if IsTerrainPathable(tx, ty, PATHING_TYPE_AMPHIBIOUSPATHING) then
rope.hit = true
if rope.hit then
rope.returning = nil
rope.tx, rope.ty = tx, ty
rope.theta = ModuloReal(math.atan(ty - cy, tx - cx), 2*math.pi)
m_chain.reset_ropes(rope.fanny)
end
end
return
end
local rec = m_chain.get_rect(tx, ty)
local obs_count = 0
EnumDestructablesInRect(rec, nil, function()
obs_count = obs_count + 1
end)
GroupEnumUnitsInRect(m_chain._GROUP, rec, Filter(function()
if not m_chain.filterTargetForPhase(GetFilterUnit()) then
obs_count = obs_count + 1
end
return false
end))
RemoveRect(rec)
rope.hit = (obs_count > 0)
if rope.hit then
rope.returning = nil
rope.tx, rope.ty = tx, ty
rope.theta = ModuloReal(math.atan(ty - cy, tx - cx), 2*math.pi)
m_chain.reset_ropes(rope.fanny)
end
end
end
-- This ability forces the movement of the unit.
m_chain._LIST:on_callback(function(unit)
local avg_x = 0
local avg_y = 0
local n = m_chain.rope_list[unit].size
local cx, cy = GetUnitX(unit), GetUnitY(unit)
if not UnitAlive(unit) then
m_chain.disconnect_all(unit)
return
end
for rope in m_chain.rope_list[unit]:iterator() do
local hook_yaw
if not rope.hit then
-- The rope is still moving towards an anchor, ignore it.
n = n - 1
local tx, ty
if not rope.returning then
rope.dist = rope.dist + m_chain.custom.ROPE_SEARCH_VELOCITY[m_chain.level[unit]]
tx, ty = rope.cx + rope.dist*math.cos(rope.theta), rope.cy + rope.dist*math.sin(rope.theta)
if rope.dist > m_chain.custom.ROPE_SEARCH_DIST[m_chain.level[unit]] then
rope.returning = true
rope.dist = ((cx-tx)^(2) + (cy-ty)^(2))^(0.5)
rope.ref_x = tx
rope.ref_y = ty
end
else
rope.dist = rope.dist - m_chain.custom.ROPE_SEARCH_VELOCITY[m_chain.level[unit]]
rope.theta = ModuloReal(math.atan(rope.ref_y-cy, rope.ref_x-cx), 2*math.pi)
tx, ty = cx + rope.dist*math.cos(rope.theta), cy + rope.dist*math.sin(rope.theta)
if rope.dist <= 0 then
rope.returning = nil
end
end
hook_yaw = rope.theta
if rope.returning ~= nil then
m_chain.update_hook(rope, tx, ty, hook_yaw)
rope.chain:move(IsUnitVisible(unit, PlayerUtils.player), cx, cy, tx, ty)
if not rope.returning then
m_chain.check_for_hit(rope, tx, ty, cx, cy)
end
else
m_chain.disconnect(rope)
end
else
-- The rope has now anchored itself.
avg_x = avg_x + math.cos(rope.theta)
avg_y = avg_y + math.sin(rope.theta)
rope.time = rope.time + m_chain.custom.ROPE_MOVE_INTERVAL
hook_yaw = ModuloReal(math.atan(rope.ty-cy, rope.tx-cx), 2*math.pi)
BlzSetSpecialEffectOrientation(rope.hook, hook_yaw, 0, 0)
if rope.time >= m_chain.custom.ROPE_MAX_FLY_TIME then
m_chain.disconnect(rope)
elseif IsUnitInRangeXY(unit, rope.tx, rope.ty, m_chain.custom.ROPE_RETRACT_DIST_RATIO*m_chain.custom.ROPE_VELOCITY[m_chain.level[unit]]) then
m_chain.disconnect(rope)
else
rope.chain:move(IsUnitVisible(unit, PlayerUtils.player), cx, cy, rope.tx, rope.ty)
end
end
end
if n == 0 then return end
local magnitude = ((avg_x)*(avg_x) + (avg_y)*(avg_y))^(0.5)
local avg_theta
avg_x, avg_y = avg_x/n, avg_y/n; avg_theta = math.atan(avg_y, avg_x);
m_chain.coordinate.x[unit] = m_chain.coordinate.x[unit] + magnitude*m_chain.custom.ROPE_VELOCITY[m_chain.level[unit]]*math.cos(avg_theta)
m_chain.coordinate.y[unit] = m_chain.coordinate.y[unit] + magnitude*m_chain.custom.ROPE_VELOCITY[m_chain.level[unit]]*math.sin(avg_theta)
if m_chain.rope_list[unit].size == 0 then
m_chain.disconnect_all(unit)
else
local tx, ty = m_chain.coordinate.x[unit], m_chain.coordinate.y[unit]
if m_chain.is_terrain_safe(tx, ty) then
SetUnitX(unit, tx); SetUnitY(unit, ty)
if m_chain.cleave_base then
local cleave_level = GetUnitAbilityLevel(unit, m_chain.cleave.custom.ABILITY_ID)
if cleave_level <= 0 then return end
if not m_chain.cooling[unit] then
GroupEnumUnitsInRange(m_chain._GROUP, tx, ty, m_chain.custom.SWING_ENUM_RADIUS, nil)
local has_enemy = false
while true do
local enum_unit = FirstOfGroup(m_chain._GROUP)
GroupRemoveUnit(m_chain._GROUP, enum_unit)
if not enum_unit then break end
if m_chain.filter_swing_target(enum_unit, unit) then
has_enemy = true
break
end
end
if has_enemy then
m_chain.cooling[unit] = true
m_chain.cleave_base.damage_aoe(unit, cleave_level)
doAfter(m_chain.cleave.custom.AUTO_COOLDOWN[cleave_level], function(unit)
m_chain.cooling[unit] = nil
end, unit)
end
end
end
else
m_chain.disconnect_all(unit, true)
end
end
end)
-- Disables movement for the unit
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_chain.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local tx, ty = GetSpellTargetX(), GetSpellTargetY()
local cx, cy = GetUnitX(unit), GetUnitY(unit)
local theta = ModuloReal(math.atan(ty - cy, tx - cx), 2*math.pi)
local level = GetUnitAbilityLevel(unit, m_chain.custom.ABILITY_ID)
local rope = {fanny = unit, theta = theta, cx = cx, cy = cy}
rope.chain = LightningUtils:add(m_chain.custom.ROPE_ID, IsUnitVisible(unit, PlayerUtils.player), cx, cy, cx, cy)
rope.hit = false
rope.returning = false
rope.dist = 0.
rope.time = 0.
m_chain.create_hook(rope, cx, cy)
DisableUnitMovement(unit)
m_chain.coordinate.x[unit] = GetUnitX(unit)
m_chain.coordinate.y[unit] = GetUnitY(unit)
m_chain.level[unit] = m_chain.level[unit] or level
if not m_chain.rope_list[unit] then
m_chain.rope_list[unit] = LinkedList:create()
end
m_chain.rope_list[unit]:insert(rope)
m_chain._LIST:include(unit)
if m_chain.rope_list[unit].size > m_chain.custom.ROPE_MAX_COUNT[level] then
m_chain.disconnect(m_chain.rope_list[unit]:first())
end
end)
end
do
local m_cleave = protected_t()
VoidAssassin.AerialCleaveBase = setmetatable({}, m_cleave)
m_cleave.custom = {
ABILITY_ID = FourCC("A01W"),
ABILITY_ORDER = "windwalk",
ABILITY_EFFECT = "war3mapImported\\Culling Slash Purple.mdx",
STRIKE_EFFECT = "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl",
DAMAGE_BASE = {5, 10, 15},
DAMAGE_FACTOR = {0.6, 0.9, 1.2},
DAMAGE_AOE = {200, 200, 200},
MANA_COST = {},
ATTACK_INDEX = 5,
ATTACK_TYPE = ATTACK_TYPE_HERO,
DAMAGE_TYPE = DAMAGE_TYPE_NORMAL,
WEAPON_TYPE = WEAPON_TYPE_METAL_MEDIUM_SLICE,
ANIM_SPEED = 2.00,
ANIM_SPEED_DELAY = 0.50,
}
function m_cleave.filterTarget(target, caster)
return UnitAlive(target) and IsUnitEnemy(target, GetOwningPlayer(caster))
and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
end
do
local max_mana = 0.
Initializer.registerBJ("SYSTEM", function()
m_cleave._DUMMY = DummyUtils.request()
m_cleave._GROUP = Group()
UnitAddAbility(m_cleave._DUMMY, m_cleave.custom.ABILITY_ID)
do
local abil = BlzGetUnitAbility(m_cleave._DUMMY, m_cleave.custom.ABILITY_ID)
local levels = BlzGetAbilityIntegerField(abil, ABILITY_IF_LEVELS)
for i = 1, levels do
max_mana = math.max(max_mana, BlzGetAbilityIntegerLevelField(abil, ABILITY_ILF_MANA_COST, i-1))
m_cleave.custom.MANA_COST[i] = BlzGetAbilityIntegerLevelField(abil, ABILITY_ILF_MANA_COST, i-1)
end
max_mana = max_mana + 1
end
BlzSetUnitMaxMana(m_cleave._DUMMY, max_mana)
end)
local animCount = {}
local function do_swing(unit)
animCount[unit] = animCount[unit] or 0
animCount[unit] = animCount[unit] + 1
DestroyEffect( AddSpecialEffectTarget(m_cleave.custom.ABILITY_EFFECT, unit, "chest"))
SetUnitAnimationByIndex(unit, m_cleave.custom.ATTACK_INDEX)
if animCount[unit] == 1 then
SetUnitTimeScale(unit, m_cleave.custom.ANIM_SPEED)
end
doAfter(m_cleave.custom.ANIM_SPEED_DELAY, function()
animCount[unit] = animCount[unit] - 1
if animCount[unit] == 0 then
animCount[unit] = nil
SetUnitTimeScale(unit, 1)
end
end)
end
function m_cleave.attempt_cast(caster, level)
local prev_mana = GetUnitState(caster, UNIT_STATE_MANA)
BlzSetUnitMaxMana(m_cleave._DUMMY, GetUnitMaxMana(caster))
SetUnitState(m_cleave._DUMMY, UNIT_STATE_MANA, prev_mana)
SetUnitAbilityLevel(m_cleave._DUMMY, m_cleave.custom.ABILITY_ID, level)
local result = IssueImmediateOrder(m_cleave._DUMMY, m_cleave.custom.ABILITY_ORDER)
local manacost = prev_mana - GetUnitState(m_cleave._DUMMY, UNIT_STATE_MANA)
if result then
SetUnitState(caster, UNIT_STATE_MANA, prev_mana - manacost)
end
return result
end
function m_cleave.deal_damage(caster, level)
local dice,side = math.max(math.random(1, BlzGetUnitDiceNumber(caster, 0)), 1), math.max(math.random(1, BlzGetUnitDiceSides(caster, 0)), 1)
local atk_dmg = BlzGetUnitBaseDamage(caster, 0) + dice*side
local dmg = m_cleave.custom.DAMAGE_BASE[level] + atk_dmg*m_cleave.custom.DAMAGE_FACTOR[level]
do_swing(caster)
GroupEnumUnitsInRange(m_cleave._GROUP, GetUnitX(caster), GetUnitY(caster), m_cleave.custom.DAMAGE_AOE[level], nil)
ForGroup(m_cleave._GROUP, function()
local uu = GetEnumUnit()
if m_cleave.filterTarget(uu, caster) then
UnitDamageTarget(caster, uu, dmg, true, false, m_cleave.custom.ATTACK_TYPE,
m_cleave.custom.DAMAGE_TYPE, m_cleave.custom.WEAPON_TYPE)
DestroyEffect( AddSpecialEffect(m_cleave.custom.STRIKE_EFFECT, GetUnitX(uu), GetUnitY(uu)))
end
end)
end
function m_cleave.damage_aoe(caster, level)
level = level or 0
if level == 0 then return end
if m_cleave.attempt_cast(caster, level) then
m_cleave.deal_damage(caster, level)
end
end
end
end
do
local m_cleave = protected_t()
VoidAssassin.AerialCleave = setmetatable({}, m_cleave)
m_cleave.custom = {
ABILITY_ID = FourCC("A01X"),
BUFF_ID = FourCC("B00U"),
AUTO_COOLDOWN = {0.1, 0.1, 0.1}
}
m_cleave.spell_mod = {}
Initializer.register(function()
m_cleave.base = VoidAssassin.AerialCleaveBase
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_cleave.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
doAfter(0.00, function()
UnitRemoveAbility(unit, m_cleave.custom.BUFF_ID)
end)
if m_cleave.base then
m_cleave.base.damage_aoe(unit, GetUnitAbilityLevel(unit, m_cleave.custom.ABILITY_ID))
end
end)
end
do
local m_sweep = protected_t()
VoidAssassin.SweepingBlow = setmetatable({}, m_sweep)
m_sweep.custom = {
ABILITY_ID = FourCC("A01Y"),
ATTACHMENT_FX = "Abilities\\Weapons\\ZigguratMissile\\ZigguratMissile.mdl",
BLEED_EFFECT = "Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl",
MOVE_OFFSET = {100, 100, 100}
}
Initializer.register(function()
m_sweep.chain = VoidAssassin.ChainThrust
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, m_sweep.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local fx = AddSpecialEffectTarget(m_sweep.custom.ATTACHMENT_FX, unit, "left hand")
if m_sweep.chain then
m_sweep.chain.disconnect_all(unit)
end
doAfter(0.00, function(targ, caster)
local theta = math.atan(-GetUnitY(targ) + GetUnitY(caster), -GetUnitX(targ) + GetUnitX(caster))
local level = GetUnitAbilityLevel(caster, m_sweep.custom.ABILITY_ID)
local tx, ty = GroundCoordinates(GetUnitX(targ) - m_sweep.custom.MOVE_OFFSET[level]*math.cos(theta),
GetUnitY(targ) - m_sweep.custom.MOVE_OFFSET[level]*math.sin(theta))
DestroyEffect(fx, 0.5)
DestroyEffect( AddSpecialEffectTarget(m_sweep.custom.BLEED_EFFECT, targ, "chest"))
SetUnitX(caster, tx); SetUnitY(caster, ty)
end, GetSpellTargetUnit(), unit)
end)
end
do
local dup = protected_t()
VoidAssassin.DuplicityOfTheVoid = setmetatable({}, dup)
dup.custom = {
ABILITY_ID = FourCC("A028"),
ASSOCIATED_IDS = LinkedList(),
DUPLICATE_COUNT = {1},
TEMP_OWNER = DummyUtils.PLAYER
}
dup.create_flag = false
dup.clone_flag = false
dup.custom.ASSOCIATED_IDS:insert(dup.custom.ABILITY_ID, VoidAssassin.ChainThrust.custom.ABILITY_ID)
dup.custom.ASSOCIATED_IDS:insert(VoidAssassin.AerialCleave.custom.ABILITY_ID, VoidAssassin.SweepingBlow.custom.ABILITY_ID)
dup.meepo_table = {}
dup.meepo_point = {}
function dup.clone_inherit(clone, unit)
for abilId in dup.custom.ASSOCIATED_IDS:iterator() do
local level = GetUnitAbilityLevel(unit, abilId)
while level > GetUnitAbilityLevel(clone, abilId) do
IssueImmediateOrderById(clone, abilId)
end
end
UnitRemoveAbility(clone, FourCC('AInv'))
end
SpellEvent.register(EVENT_PLAYER_HERO_SKILL, function()
local abilId = GetLearnedSkill()
local level = GetLearnedSkillLevel()
local unit = GetTriggerUnit()
if dup.create_flag then return end
if abilId == dup.custom.ABILITY_ID then
-- Check if unit has an entry
-- Prevent nested callbacks from proceeding
if not dup.meepo_table[unit] and not dup.meepo_point[unit] then
dup.meepo_table[unit] = {group=CreateGroup(), count=0}
end
local unitId = GetUnitTypeId(unit)
dup.create_flag = true
if dup.meepo_table[unit] then
-- It is the main unit who learned this ability
ForGroup(dup.meepo_table[unit].group, function()
IssueImmediateOrderById(GetEnumUnit(), abilId)
end)
while true do
if dup.meepo_table[unit].count < dup.custom.DUPLICATE_COUNT[level] then
local clone = CreateUnit(GetOwningPlayer(unit), unitId, GetUnitX(unit), GetUnitY(unit), GetUnitFacing(unit))
SetHeroLevel(clone, GetHeroLevel(unit), false)
GroupAddUnit(dup.meepo_table[unit].group, clone)
dup.clone_inherit(clone, unit)
dup.meepo_point[clone] = unit
dup.meepo_table[unit].count = dup.meepo_table[unit].count + 1
else break
end
end
elseif dup.meepo_point[unit] then
local main = dup.meepo_point[unit]
IssueImmediateOrderById(main, abilId)
ForGroup(dup.meepo_point[main].group, function()
if GetEnumUnit() ~= unit then
IssueImmediateOrderById(GetEnumUnit(), abilId)
end
end)
while true do
if dup.meepo_table[main].count < dup.custom.DUPLICATE_COUNT[level] then
local clone = CreateUnit(GetOwningPlayer(main), unitId, GetUnitX(main), GetUnitY(main), GetUnitFacing(main))
SetHeroLevel(clone, GetHeroLevel(main), false)
GroupAddUnit(dup.meepo_table[main].group, clone)
dup.clone_inherit(clone, main)
dup.meepo_point[clone] = main
dup.meepo_table[main].count = dup.meepo_table[main].count + 1
else break
end
end
end
dup.create_flag = false
else
dup.create_flag = true
if dup.meepo_point[unit] then
-- The learning unit is just a clone
IssueImmediateOrderById(dup.meepo_point[unit], abilId)
ForGroup(dup.meepo_table[dup.meepo_point[unit] ].group, function()
if GetEnumUnit() ~= unit then
IssueImmediateOrderById(GetEnumUnit(), abilId)
end
end)
elseif dup.meepo_table[unit] then
ForGroup(dup.meepo_table[unit].group, function()
IssueImmediateOrderById(GetEnumUnit(), abilId)
end)
end
dup.create_flag = false
end
end)
UnitState.register("DEATH_EVENT", function()
local unit = UnitState.eventUnit
if dup.clone_flag then return end
if dup.meepo_table[unit] then
-- Kill all associated meepos.
dup.clone_flag = true
ForGroup(dup.meepo_table[unit].group, function()
local uu = GetEnumUnit()
SetUnitOwner(uu, dup.custom.TEMP_OWNER, false)
if UnitAlive(uu) then
KillUnit(uu)
end
end)
dup.clone_flag = false
elseif dup.meepo_point[unit] then
-- A clone died, kill the original
KillUnit(dup.meepo_point[unit])
end
end)
UnitState.register("RESURRECT_EVENT", function()
local unit = UnitState.eventUnit
local owner = GetOwningPlayer(unit)
if dup.clone_flag then return end
if dup.meepo_table[unit] then
-- Revive all associated meepos.
local cx, cy = GetUnitX(unit), GetUnitY(unit)
dup.clone_flag = true
ForGroup(dup.meepo_table[unit].group, function()
local uu = GetEnumUnit()
SetUnitOwner(uu, owner, true)
if not UnitAlive(uu) then
ReviveHero(uu, cx, cy, true)
else
SetUnitPosition(uu, cx, cy)
end
end)
dup.clone_flag = false
elseif dup.meepo_point[unit] then
-- A clone was revived, revive the original
local cx, cy = GetUnitX(unit), GetUnitY(unit)
SetUnitOwner(unit, owner, true)
ReviveHero(dup.meepo_point[unit], cx, cy, true)
end
end)
ExperienceEvent.register(function()
local unit = ExperienceEvent.eventUnit
if dup.clone_flag then return end
if dup.meepo_table[unit] then
dup.clone_flag = true
local cur_xp = ExperienceEvent.xpAmount
ForGroup(dup.meepo_table[unit].group, function()
SetHeroXP(GetEnumUnit(), cur_xp, true)
end)
dup.clone_flag = false
elseif dup.meepo_point[unit] then
GroupRemoveUnit(dup.meepo_table[dup.meepo_point[unit]].group, unit)
SetHeroXP(dup.meepo_point[unit], cur_xp, true)
GroupAddUnit(dup.meepo_table[dup.meepo_point[unit]].group, unit)
end
end)
end