Name | Type | is_array | initial_value |
Test_Dialog | dialog | No |
Adjusted base hitpoints of the following units:
- Moon Deaconess (375 -> 275)
- Druid Acolyte (450 -> 350)
- Druid of the Harpy (300 -> 250)
- Druid of the Harpy (Flying) (400 -> 375)
- Treant - Level 2 (400 -> 325)
- Treant - Level 3 (550 -> 400)
Modified armor type of Druid of the Harpy (Medium -> Small)
Added Earthen Druidess
Adjusted food consumption of the following units:
- Druid of the Harpy (2 -> 3) (Flying mode adjusted similarly)
- Druid Acolyte (3 -> 4)
- Moon Deaconess (3 -> 4)
- Druid Villager (1 -> 2) (Savage Beast Form adjusted similarly)
Adjusted food production of the following buildings:
- Increased food production of Night Hall (10 -> 15)
- Increased food production of Druid Farm (10 -> 12)
- Decreased food production of Archdruid Sanctuary (20 -> 15)
do
local meta = {}
function meta:__call(cc)
if not meta[cc] then
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
meta[cc] = str
return str
end
return meta[cc]
end
CC2Four = setmetatable({}, meta)
end
do
local m_class = {}
local table_class = {}
local flags = {
READONLY = 1,
PROTECTED = 2,
}
m_class.__index = m_class
m_class.__newindex = function() end
ClassTable = setmetatable({}, m_class)
function m_class.is_readonly(o)
return table_class[o] == flags.READONLY
end
function m_class.is_protected(o)
return table_class[o] == flags.PROTECTED
end
function readonly_table(o)
o = o or {}
o.__index = o
o.__newindex = function(t, k, v)
if o[k] then return
end
rawset(t, k, v)
end
table_class[o] = flags.READONLY
return o
end
function protected_table(o)
o = o or {}
o.__index = function(t, k)
if tostring(k):sub(1,1) == '_' then return;
end
return o[k]
end
o.__newindex = function() end
table_class[o] = flags.PROTECTED
return o
end
end
do
local old_func = {}
old_func.print = print
function print_after(delay, ...)
if not delay or delay < 0 then old_func.print(...) return;
end
local t = {...}
TimerStart(CreateTimer(), delay, false, function()
old_func.print(table.unpack(t))
DestroyTimer(GetExpiredTimer())
end)
end
function is_function(func)
if func == nil then return true;
elseif type(func) == 'function' then return true;
end
if type(func) == 'table' then
return is_function(getmetatable(func).__call)
end
end
end
do
local m_init = protected_table()
local list = {
SYSTEM = {},
TRIGGER = {},
USER = {},
TIMER = {},
}
local results = {
SYSTEM = {},
TRIGGER = {},
USER = {},
TIMER = {},
}
local flags = {}
Initializer = setmetatable({}, m_init)
m_init.__metatable = Initializer
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.initialized(prio)
prio = ((not list[prio]) and "USER") or prio
return flags[prio]
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
local function exec(prio)
local i = 1
while true do
results[prio][i] = select(1, pcall(list[prio][i]))
if not results[prio][i] then
results[prio][i] = "Initializer.result {" .. prio .. "} >> " .. tostring(list[prio][i]) ..
" failed to initialize. Index position (" .. tostring(i) .. ")"
else
results[prio][i] = nil
end
i = i + 1
if i > #list[prio] then break end
end
flags[prio] = true
end
-- Initialize the functions
local _SetMapMusic = SetMapMusic
function SetMapMusic(musicname, random, index)
SetMapMusic = _SetMapMusic
_SetMapMusic(musicname, random, index)
TimerStart(CreateTimer(), 0.00, false, function()
exec("TIMER")
PauseTimer(GetExpiredTimer())
DestroyTimer(GetExpiredTimer())
end)
exec("SYSTEM")
local hasCustomInit = RunInitializationTriggers ~= nil
local tempVar = ""
local tempCustomTriggers
if hasCustomInit then
tempCustomTriggers = RunInitializationTriggers
tempVar = "RunInitializationTriggers"
else
tempCustomTriggers = InitGlobals
tempVar = "InitGlobals"
end
_G[tempVar] = function()
exec("TRIGGER")
tempCustomTriggers()
_G[tempVar] = tempCustomTriggers
exec("USER")
end
end
end
do
local _SetMapName = SetMapName
local _SetMapDescription = SetMapDescription
local mapname
local desc
function SetMapName(name)
mapname = GetLocalizedString(name)
_SetMapName(name)
end
function SetMapDescription(description)
desc = GetLocalizedString(description)
_SetMapDescription(description)
end
function GetMapName(name)
return mapname
end
function GetMapDescription(description)
return desc
end
end
do
local m_vers = protected_table()
m_vers.patch = (BlzStartUnitAbilityCooldown and "1.32") or "1.31"
VersionCheck = setmetatable({}, m_vers)
end
do
local function foo()
if VersionCheck.patch ~= "1.31" then return;
end
local timer = CreateTimer()
local PERIOD = 30.00
Initializer("SYSTEM", function()
TimerStart(timer, PERIOD, true, function()
collectgarbage()
collectgarbage()
end)
end)
end
foo()
end
do
local m_rect = protected_table()
WorldRect = setmetatable({}, m_rect)
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
do
local tb = protected_table()
tb.PRELOAD_SIZE = 1024
local data = {}
local pointer = {}
local next = {}
local prev = {}
local size = {}
local database = {}
local tstack = {}
List = setmetatable({}, tb)
LinkedList = List
data.__index = function(t, k)
if k == 'data' then
return data[t]
end
return nil
end
data.__newindex = function(t, k, v)
end
for i = 1, tb.PRELOAD_SIZE do
tstack[i] = setmetatable({}, data)
end
local function remove_from_class(self)
local meta = tb.__metatable
tb.__metatable = nil
setmetatable(self, nil)
tb.__metatable = meta
end
local function stack_request()
if tstack[#tstack] then
local t = tstack[#tstack]
tstack[#tstack] = nil
return t
end
return {}
end
local function stack_restore(t)
tstack[#tstack + 1] = t
end
local function generic_insert(self, func, ...)
local n = select('#', ...)
if n == 0 then return;
elseif n == 1 then generic_insert(self, func, 1, select(1, ...)); return;
end
local j = math.floor(select(1, ...) + 0.5)
local start = self
local ptb = {}
while j > 1 do
start = next[self][start]
j = j - 1
end
for k = 2, n do
local t = stack_request()
data[t] = select(k, ...)
pointer[t] = self
ptb[#ptb + 1] = t
if not database[self][data[t]] then
database[self][data[t]] = 1
else
database[self][data[t]] = database[self][data[t]] + 1
end
func(self, t, start)
next[self][prev[self][t]] = t
prev[self][next[self][t]] = t
size[self] = size[self] + 1
end
return table.unpack(ptb)
end
local function rout_insert(self, t, start)
next[self][t] = start
prev[self][t] = prev[self][start]
end
local function rout_unshift(self, t, start)
prev[self][t] = start
next[self][t] = next[self][start]
end
-- Constructor
function tb:__call(...)
local o = {}
next[o] = {}
prev[o] = {}
database[o] = {}
size[o] = 0
next[o][o] = o
prev[o][o] = o
tb.insert(o, ...)
setmetatable(o, tb)
return o
end
function tb:insert(...)
return generic_insert(self, rout_insert, ...)
end
function tb:unshift(...)
return generic_insert(self, rout_unshift, ...)
end
function tb:remove(t)
if pointer[t] ~= self then return;
elseif t == self then return;
end
database[self][data[t]] = database[self][data[t]] - 1
if database[self][data[t]] <= 0 then
database[self][data[t]] = nil
end
data[t] = nil
pointer[t] = nil
next[self][prev[self][t]] = next[self][t]
prev[self][next[self][t]] = prev[self][t]
next[self][t] = nil
prev[self][t] = nil
size[self] = size[self] - 1
stack_restore(t)
end
function tb:first()
return data[next[self][self]], next[self][self]
end
function tb:last()
return data[prev[self][self]], prev[self][self]
end
function tb:next(t)
return next[self][t]
end
function tb:prev(t)
return next[self][t]
end
function tb:unshift()
self:remove(select(2, self:first()))
end
function tb:pop()
self:remove(select(2, self:last()))
end
function tb:is_node_in(t)
return pointer[t] == self
end
function tb:is_elem_in(elem)
return database[self][elem] ~= nil
end
-- Destructors
function tb:clear()
while size[self] > 0 do
self:remove(prev[self][self])
end
end
function tb:destroy()
if not size[self] then return;
end
self:clear()
next[self][self] = nil
prev[self][self] = nil
database[self] = nil
next[self] = nil
prev[self] = nil
size[self] = nil
end
-- Size getters
function tb:size()
return size[self]
end
function tb:__len()
return size[self]
end
end
do
local tb = getmetatable(List)
local ftb = {flag={}}
local temp = {}
local lock = {}
local rmvcall = {}
local rmvstack = {}
local destcall = {}
local curnode = {}
local curftb = {depth=0}
local _insert = tb.insert
local _unshift = tb.unshift
local function lock_object(self)
if not lock[self] then
lock[self] = 0
destcall[self] = 0
rawset(self, 'remove', temp.remove)
rawset(self, 'clear', temp.clear)
rawset(self, 'destroy', temp.destroy)
end
lock[self] = lock[self] + 1
end
local function unlock_object(self)
lock[self] = lock[self] - 1
if lock[self] <= 0 then
lock[self] = nil
curnode[self] = nil
rawset(self, 'remove', nil)
rawset(self, 'clear', nil)
rawset(self, 'destroy', nil)
if destcall[self] > 0 then
destcall[self] = nil
self:destroy()
end
end
end
local function iterator_unlock(obj, self)
curftb[obj.depth][self] = nil
end
local function iterator_lock(obj, self)
while curftb.depth < lock[self] do
curftb.depth = curftb.depth + 1
curftb[curftb.depth] = {}
end
curftb[lock[self] ][self] = obj
end
local function iterator_restore(obj)
if ftb.flag[obj] then return;
end
ftb[#ftb + 1] = obj
ftb.flag[obj] = true
iterator_unlock(obj, obj.list)
unlock_object(obj.list)
obj.debug = nil
obj.depth = nil
obj.list = nil
obj.node = nil
end
local function iterator_request()
local obj
if ftb[#ftb] then
obj = ftb[#ftb]
ftb[#ftb] = nil
ftb.flag[obj] = false
return obj
end
obj = {}
obj.timer = CreateTimer()
obj.callback = function()
local node = obj.node
obj.node = obj.list:next(obj.node)
if rmvstack[node] then
rmvstack[node] = rmvstack[node] - 1
if rmvstack[node] < 0 then
tb.remove(obj.list, node)
rmvstack[node] = nil
rmvcall[node] = nil
end
end
if obj.node == obj.list then
PauseTimer(obj.timer)
iterator_restore(obj)
return nil;
end
node = obj.node
curnode[obj.list] = node
rmvstack[node] = rmvstack[node] or 0
rmvstack[node] = rmvstack[node] + 1
if obj.debug then
print("Current rmvstack[node]", rmvstack[node])
end
return node.data, node, lock[obj.list]
end
TimerStart(obj.timer, 0.00, false, function()
PauseTimer(obj.timer)
iterator_restore(obj)
end)
PauseTimer(obj.timer)
return obj
end
function temp:remove(node)
if curnode[self] == node then
if rmvcall[node] then return;
end
rmvcall[node] = 1
rmvstack[node] = rmvstack[node] - 1
return
end
if rmvstack[node] then
if rmvcall[node] then return;
end
rmvcall[node] = 1
rmvstack[node] = rmvstack[node] - 1
return
end
tb.remove(self, node)
end
function temp:clear()
local i = 1
local j = #self
local node = select(2, self:first())
while i <= j do
local prev_node = node
node = self:next(node)
self:remove(prev_node)
i = i + 1
end
end
function temp:destroy()
destcall[self] = 1
end
function tb:insert(...)
local n = select('#', ...)
if n == 0 then
return self
end
return self, _insert(self, 1, ...)
end
function tb:unshift(...)
local n = select('#', ...)
if n == 0 then
return self
end
return self, _unshift(self, 1, ...)
end
function tb:is_head(t)
return t == self
end
function tb:new(...)
return self(...)
end
function tb:create(...)
return self(...)
end
function tb:random()
if #self <= 0 then return nil, nil;
end
local i = math.random(1, #self)
local iter = select(2, self:first())
while i > 1 do
iter = self:next(iter)
i = i - 1
end
return iter.data, iter
end
function tb:iterator(node, debug)
if (not node) or (not self:is_node_in(node)) then
node = self
end
local obj = iterator_request()
lock_object(self)
iterator_lock(obj, self)
obj.list = self
obj.node = node
obj.depth = lock[self]
obj.debug = debug
ResumeTimer(obj.timer)
return obj.callback
end
function tb:remove_elem(elem)
if not self:is_elem_in(elem) then return;
end
for comp_elem, pointer in self:iterator() do
if elem == comp_elem then
tb.remove(self, pointer)
break
end
end
end
end
do
local tb = protected_table()
AllocTableEx = setmetatable({}, tb)
tb._DEF_SIZE = 0
tb._ALLOC = {}
function tb:__call(size)
size = size or 50
local o = protected_table()
local co = {pointer={}}
tb._ALLOC[o] = co
if size > 0 then
for i = 1, size do
co[i] = {}
co.pointer[co[i]] = i
end
end
o.request = function()
if co[#co] then
local t = co[#co]
co.pointer[t] = nil
co[#co] = nil
return t
end
return {}
end
o.release = function(t)
if co.pointer[t] then return;
end
co[#co + 1] = t
co.pointer[t] = #co
end
o.restore = o.release
return o
end
end
do
local tb = protected_table()
SimpleList = setmetatable({}, tb)
function tb:__call(...)
local o = setmetatable({list = LinkedList(), pointer = {}}, tb)
local j = select('#', ...)
for i = 1, j do
o:insert(select(i, ...))
end
return o
end
function tb:create()
return self()
end
function tb:new()
return self()
end
function tb:destroy()
self:clear()
self.pointer = nil
end
function tb:insert(elem)
if self.pointer[elem] then return false;
end
self.pointer[elem] = select(2, self.list:insert(elem))
return true
end
function tb:remove(elem)
if not self.pointer[elem] then return false;
end
self.list:remove(self.pointer[elem])
self.pointer[elem] = nil
return true
end
function tb:is_in(elem)
return self.pointer[elem] ~= nil
end
function tb:iterator(node, debug)
return self.list:iterator(node, debug)
end
function tb:__len()
return #self.list
end
function tb:random()
return self.list:random()
end
function tb:clear()
local node = select(2, self.list:first())
local j = #self.list
while j > 0 do
local prev_node = node
node = self.list:next(node)
self:remove(prev_node.data)
j = j - 1
end
end
end
do
local tb = protected_table()
AllocTable = setmetatable({}, tb)
tb._alloc = {}
tb.__metatable = AllocTable
tb._DEF_ALLOC = 32
function tb:__call(o, size, const, dest)
local o = protected_table()
local co = {}
tb._alloc[o] = co
size = size or tb._DEF_ALLOC
local i = 1
while i <= size do
co[i] = {}
i = i + 1
end
o.__call = function(t, ...)
local oo
if #co > 0 then
oo = co[#co]
co[#co] = nil
else
oo = {}
end
if o.__constructor then
o.__constructor(oo, ...)
end
setmetatable(oo, o)
return oo
end
o.__destroy = function(t)
if not getmetatable(t) then return;
end
local mt = o.__metatable
if o.__destructor then
o.__destructor(t)
end
o.__metatable = nil
setmetatable(t, nil)
o.__metatable = mt
co[#co + 1] = t
end
o.create = o.__call
o.new = o.__call
o.destroy = o.__destroy
return o, co
end
end
do
local tb = {__index = function(t, k) return tb[k] end, __newindex = function() end}
local wt = {__mode = 'kv'}
tb.__DEF = 16
WeakObject = setmetatable({}, tb)
function tb:__call(size)
local mo = {}
local o = {}
local co = {}
size = size or tb.__DEF
local i = 1
while i <= size do
co[i] = {}
i = i + 1
end
o.__index = function(t, k)
if o[k] then return o[k];
elseif not mo[k] then return nil;
end
return mo[k][t]
end
o.__newindex = function(t, k, v)
if o[k] then return;
end
if not mo[k] then
mo[k] = setmetatable({}, wt)
end
mo[k][t] = v
end
o.__call = function(t, ...)
local oo
if #co > 0 then
oo = co[#co]
co[#co] = nil
else
oo = {}
end
if o.__constructor then
o.__constructor(oo, ...)
end
setmetatable(oo, o)
return oo
end
o.__destroy = function(t)
if not getmetatable(t) then return;
end
local mt = o.__metatable
if o.__destructor then
o.__destructor(t)
end
o.__metatable = nil
setmetatable(t, nil)
o.__metatable = mt
co[#co + 1] = t
end
o.destroy = o.__destroy
o.__gc = function(self)
self:__destroy()
end
return o, mo, co
end
end
--[[
-- Originally made by Almia, this was ported over to LUA.
-- Link to the JASS library: https://www.hiveworkshop.com/threads/beziereasing.310514/
]]
do
local m_bez = {
_EPSILON = 0.00001
}
BezierEasing = setmetatable({}, m_bez)
m_bez.__metatable = BezierEasing
m_bez.__newindex = function() 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 math.abs(a - b) <= m_bez._EPSILON*math.max(1., math.max(math.abs(a), math.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 k < 0 then
return 0
elseif k > 1 then
return 1
end
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.
end
return m_bez[k]
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
local m_cache = {}
m_cache.__newindex = function() end
m_cache._results = {}
m_cache._interval = {}
m_cache._list = {}
function m_cache:__index(k)
if type(k) == 'number' then return m_cache._results[self][k];
end
if tostring(k):sub(1,1) == '_' then return nil;
end
return m_cache[k]
end
function m_cache:next(i)
if m_cache._list[self][i] then return m_cache._list[self][i];
end
return -1
end
function math.cache(a, b, interval, func)
interval = interval or 1
local i = 1
local tb = setmetatable({}, m_cache)
m_cache._results[tb] = {}
m_cache._list[tb] = {}
m_cache._interval[tb] = interval
while a < b do
m_cache._results[tb][i] = func(a)
m_cache._list[tb][i] = i + 1
a = a + interval
i = i + 1
end
m_cache._list[tb][i - 1] = 1
return tb
end
end
do
local vect = WeakObject(32)
Vector2D = setmetatable({}, vect)
function vect:__constructor(x, y)
self.x = x or 0
self.y = y or 0
end
function vect:__destructor()
self.x = nil
self.y = nil
end
function vect:__len()
return (self.x*self.x + self.y*self.y)^(1/2)
end
function vect:dot(other)
return self.x*other.x + self.y*other.y
end
function vect:norm()
local len = #self
return Vector2D(self.x/len, self.y/len)
end
function vect:__add(other)
return Vector2D(self.x + other.x, self.y + other.y)
end
function vect:__sub(other)
return Vector2D(self.x - other.x, self.y - other.y)
end
function vect:__mul(other)
return self:dot(other)
end
function vect:coords()
return "(" .. tostring(self.x) .. ", " .. tostring(self.y) .. ")"
end
function vect:arg()
return math.atan(self.y, self.x)
end
vect.ORIGIN = Vector2D(0, 0)
end
do
local m_listener = protected_table()
EventListener = setmetatable({}, m_listener)
local function clean_object(self)
local meta = m_listener.__metatable
m_listener.__metatable = nil
setmetatable(self, nil)
m_listener.__metatable = meta
end
function m_listener:new(o)
o = o or {}
o.list = {}
o.list_count = {}
o.func_point = {}
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)
if not is_function(func) or (self == EventListener) then return false;
end
rep = rep or 1
if not self.func_point[func] then
self.list[#self.list + 1] = func
self.list_count[#self.list] = 0
self.func_point[func] = #self.list
end
local index = self.func_point[func]
self.list_count[index] = self.list_count[index] + rep
return true
end
function m_listener:deregister(func, rep)
if not is_function(func) or (self == EventListener) or (not self.func_point[func]) then return false;
end
rep = rep or 1
local index = self.func_point[func]
self.list_count[index] = self.list_count[index] - rep
if self.list_count[index] <= 0 then
local i, j = index, #self.list
while i < j do
local func2 = self.list_count[i + 1]
self.list_count[i] = func2
self.list[i] = self.list[i + 1]
self.func_point[func2] = i
i = i + 1
end
self.list_count[j] = nil
self.list[j] = nil
self.func_point[func] = nil
end
return true
end
function m_listener:destroy()
while true do
self:deregister(self.list[#self.list], self.list_count[#self.list])
if #self.list <= 0 then break end
end
clean_object(self)
end
end
do
local m_listener = getmetatable(EventListener)
local event_tb = {
cur_event = 0,
cur_function = 0,
}
m_listener.__metatable = EventListener
local old_method = {
new = m_listener.new,
dest = m_listener.destroy,
reg = m_listener.register,
dereg = m_listener.deregister
}
local temp_method = {}
function m_listener:new(o)
o = o or {}
o.recr = {}
o.remove_count = {}
o.active_count = {}
o.dest_count = 0
o.mut_count = 0
o.max_recr = 0
return old_method.new(self, o)
end
function m_listener:destroy()
self.recr = nil
self.remove_count = nil
self.active_count = nil
old_method.dest(self)
end
function m_listener:register(func, rep)
local flag = old_method.reg(self, func, rep)
if flag and not self.recr[func] then
self.recr[func] = 0
self.active_count[func] = 0
end
return flag
end
function m_listener:deregister(func, rep)
local flag = old_method.dereg(self, func, rep)
if flag and not self.func_point[func] then
self.recr[func] = nil
self.active_count[func] = nil
end
return flag
end
function m_listener:enable(func)
if not self.func_point[func] then return;
end
self.active_count[func] = self.active_count[func] + 1
end
function m_listener:disable(func)
if not self.func_point[func] then return;
end
self.active_count[func] = self.active_count[func] - 1
end
function m_listener:is_enabled(func)
if not self.func_point[func] then return false;
end
return self.active_count[func] >= 0
end
function m_listener:is_registered(func)
return self.func_point[func] ~= nil
end
function m_listener:get_depth()
return self.mut_count
end
function m_listener.get_event()
return event_tb.cur_event
end
function m_listener.get_cur_function()
return event_tb.cur_function
end
do
function temp_method:register(func, rep)
rep = rep or 1
if not self.func_point[func] then
return m_listener.register(self, func, rep)
else
if self.remove_count[func] then
self.remove_count[func] = self.remove_count[func] - rep
else
self.remove_count[func] = -rep
end
end
end
function temp_method:deregister(func, rep)
rep = rep or 1
if not self.func_point[func] then
return m_listener.deregister(self, func, rep)
else
if self.remove_count[func] then
self.remove_count[func] = self.remove_count[func] + rep
else
self.remove_count[func] = rep
end
end
end
function temp_method:destroy()
self.dest_count = self.dest_count + 1
end
function temp_method:restore()
self.dest_count = self.dest_count - 1
end
local function evaluate_cond(self, cond)
if not is_function(cond) then return cond;
end
return cond();
end
local function pass_recr(self, i)
if self.max_recr <= 0 then return true end
local func = self.list[i]
return self.recr[func] < self.max_recr and
self.list_count[self.func_point[func]] > self.remove_count[func]
end
local is_enabled = m_listener.is_enabled
local function override_setters(self)
-- Temporarily overwrite self:register, self:deregister, and self:destroy
rawset(self, 'register', temp_method.register)
rawset(self, 'deregister', temp_method.deregister)
rawset(self, 'destroy', temp_method.destroy)
rawset(self, 'restore', temp_method.restore)
end
local function reset_setters(self)
rawset(self, 'register', nil)
rawset(self, 'deregister', nil)
rawset(self, 'destroy', nil)
rawset(self, 'restore', nil)
end
function m_listener:_restore()
local i = 1
while i <= #self.list do
local func = self.list[i]
if self.remove_count[func] then
if self.remove_count[func] > 0 then
self:deregister(func, self.remove_count[func])
else
self:register(func, -self.remove_count[func])
end
self.remove_count[func] = nil
-- If the instance was removed at any point, reset index
if not self.func_point[func] then
i = i - 1
end
end
i = i + 1
end
end
function m_listener:conditional_exec(cond, ...)
local tbs = {
cur_event = event_tb.cur_event,
cur_function = event_tb.cur_function
}
event_tb.cur_event = self
self.mut_count = self.mut_count + 1
if self.mut_count == 1 then
override_setters(self)
end
local i = 1
while i <= #self.list do
local func = self.list[i]
if not self.remove_count[func] then self.remove_count[func] = 0;
end
if is_enabled(self, func) and evaluate_cond(self, cond) and pass_recr(self, i) then
event_tb.cur_function = func
self.recr[func] = self.recr[func] + 1
local k = 1
while k <= (self.list_count[i] - self.remove_count[func]) do
pcall(func, ...)
k = k + 1
end
self.recr[func] = self.recr[func] - 1
end
i = i + 1
end
event_tb.cur_event = tbs.cur_event
event_tb.cur_function = tbs.cur_function
self.mut_count = self.mut_count - 1
if self.mut_count <= 0 then
reset_setters(self)
if self.dest_count > 0 then
self:destroy()
else
m_listener._restore(self)
end
end
end
end
function m_listener:execute(...)
self:conditional_exec(true, ...)
end
function m_listener:set_recursion_count(value)
value = ((type(value) ~= 'number') and 0) or value
self.max_recr = value
end
function m_listener:set_recursion_depth(value)
self:set_recursion_count(value)
end
end
do
local m_prio = protected_table()
PriorityEvent = setmetatable({}, m_prio)
m_prio.__metatable = PriorityEvent
local function DoNothing()
end
function m_prio:new(o)
o = o or {}
o.list = {
prio = {[0]=0},
event = {[0]=EventListener:create()},
}
o.max = 0
o.min = 0
o.size = 1
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
-- is_prio_in should return a flag, and an appropriate index
local function is_prio_in(self, prior)
if (prior >= self.max) then
if prior == self.max then
return true, self.size - 1
end
return false, self.size
end
if (prior <= self.min) then
if prior == self.min then
return true, 0
end
return false, 0
end
local pos = self.size - 1
while pos >= 0 do
if prior >= self.list.prio[pos] then break;
end
pos = pos - 1
end
if prior == self.list.prio[pos] then
return true, pos
end
return false, pos + 1
end
local function insert(self, prior, index)
local i = self.size - 1
if index == 0 then
self.min = prior
elseif index >= self.size then
self.max = prior
end
while i >= index do
self.list.prio[i + 1] = self.list.prio[i]
self.list.event[i + 1] = self.list.event[i]
i = i - 1
end
self.size = self.size + 1
self.list.prio[index] = prior
self.list.event[index] = EventListener:create()
end
-- prior will only accept integers.
-- floats will be rounded to the nearest integer.
-- If no priority is provided via nil, prior will instead default to self.min
function m_prio:register(prior, func)
prior = prior or self.min
prior = math.floor(prior + 0.5)
local has, pos = is_prio_in(self, prior)
if not has then insert(self, prior, pos);
end
self.list.event[pos]:register(func)
end
function m_prio:deregister(prior, func)
prior = prior or self.min
prior = math.floor(prior + 0.5)
local has, pos = is_prio_in(self, prior)
if not has then return;
end
self.list.event[pos]:deregister(func)
end
function m_prio:conditional_fire(prior, cond, ...)
prior = prior or self.min
prior = math.floor(prior + 0.5)
local has, pos = is_prio_in(self, prior)
if not has then return;
end
self.list.event[pos]:conditional_exec(cond, ...)
end
function m_prio:fire(prior, ...)
self:conditional_fire(prior, true, ...)
end
function m_prio:conditional_fire_to(upper, lower, cond, ...)
upper, lower = math.floor(upper + 0.5), math.floor(lower + 0.5)
local i, j = select(2, is_prio_in(self, upper)), select(2, is_prio_in(self, lower))
if i <= j then i, j = j, i;
end
while i >= j do
self.list.event[i]:conditional_exec(cond, ...)
i = i - 1
end
end
function m_prio:fire_to(upper, lower, ...)
self:conditional_fire_to(upper, lower, true, ...)
end
function m_prio:fire_all(...)
self:conditional_fire_to(self.max, self.min, true, ...)
end
function m_prio:conditional_fire_all(cond, ...)
self:conditional_fire_to(self.max, self.min, cond, ...)
end
function m_prio:set_prio_recursion(prior, value)
prior = prior or self.min
prior = math.floor(prior + 0.5)
local has, pos = is_prio_in(self, prior)
if not has then
return
end
self.listeners[prior]:set_recursion_count(value)
end
function m_prio:preload_registry(min, max, incr)
min = min or self.min
max = max or self.max
incr = incr or 1
while min <= max do
self:register(min, DoNothing)
self:deregister(min, DoNothing)
min = min + incr
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
do
local m_reg = protected_table()
RegisterAnyPlayerUnitEvent = setmetatable({}, m_reg)
m_reg.__metatable = RegisterAnyPlayerUnitEvent
do
local trig
local tb = {}
local function is_player_event(event)
return tostring(event):sub(1,15) == 'playerunitevent'
end
Initializer("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() self(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 m_spell = protected_table()
SpellEvent = setmetatable({}, m_spell)
m_spell.__listeners = PriorityEvent:create()
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
if not m_spell.__spellId[abilId][i] then
m_spell.__spellId[abilId][i] = EventListener:create()
end
m_spell.__spellId[abilId][i]:register(func)
end
Initializer("SYSTEM", function()
local resp_func = function()
local unit = GetTriggerUnit()
local id, abilId = event2Id(GetTriggerPlayerUnitEventId()), GetSpellAbilityId()
if id == 6 then abilId = GetLearnedSkill() end
m_spell.__listeners:fire(id, unit, abilId)
if m_spell.__spellId[abilId] and m_spell.__spellId[abilId][id] then
m_spell.__spellId[abilId][id]:execute(unit, abilId)
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
do
local m_dex = protected_table()
local info = {
preplaced_flag = true,
preplace_table = {},
register_table = {},
index_table = {},
alloc = {[0]=0},
}
UnitDex = setmetatable({}, m_dex)
m_dex.__metatable = UnitDex
m_dex._UNDEFEND = 852056
m_dex._DETECT_LEAVE = FourCC("uDex") -- Ability must be based on footman's defend ability
m_dex._OVERRIDE = false
m_dex._REMOVE = RemoveUnit
local param = {
ENTER_EVENT = EventListener:create(),
LEAVE_EVENT = EventListener:create()
}
if m_dex._OVERRIDE then
info.setdata = SetUnitUserData
function SetUnitUserData(whichUnit, data)
end
end
local function checkparams(ev)
return param[ev] ~= nil
end
function m_dex.register(ev, func)
if not checkparams(ev) then
return
end
param[ev]:register(func)
end
function m_dex.deregister(ev, func)
if not checkparams(ev) then
return
end
param[ev]:deregister(func)
end
Initializer("TRIGGER", function()
m_dex._GROUP = CreateGroup()
local function new_index()
local i = info.alloc[0]
if info.alloc[i] == 0 then
i = i + 1
info.alloc[0] = i
else
info.alloc[0] = info.alloc[i]
end
info.alloc[i] = 0
return i
end
local function recycle_index(i)
if not info.alloc[i] then
return
end
if info.alloc[i] ~= 0 then
return
end
info.alloc[i] = info.alloc[0]
info.alloc[0] = i
end
local function throw_event(ev, whichunit)
local prev_unit = m_dex.eventUnit
local prev_type = m_dex.eventType
m_dex.eventUnit = whichunit
m_dex.eventType = ev
param[ev]:execute(whichunit)
m_dex.eventUnit = prev_unit
m_dex.eventType = prev_type
end
local function reg(whichunit)
if info.register_table[whichunit] then return;
end
-- Proceed with registration
info.register_table[whichunit] = true
info.preplace_table[whichunit] = info.preplaced_flag
if info.setdata then
local i = new_index()
info.index_table[i] = whichunit
info.setdata(whichunit, i)
end
-- Add detection ability
UnitAddAbility(whichunit, m_dex._DETECT_LEAVE)
UnitMakeAbilityPermanent(whichunit, true, m_dex._DETECT_LEAVE)
BlzUnitDisableAbility(whichunit, m_dex._DETECT_LEAVE, true, true)
-- Add Unit to the global group
GroupAddUnit(m_dex._GROUP, whichunit)
-- Throw event
throw_event("ENTER_EVENT", whichunit)
end
local function dereg(whichunit)
if not info.register_table[whichunit] then return;
end
-- Proceed with removal
info.register_table[whichunit] = nil
info.preplace_table[whichunit] = nil
if info.setdata then
local i = GetUnitUserData(whichunit)
recycle_index(i)
info.index_table[i] = nil
info.setdata(whichunit, 0)
end
-- Remove the unit from the global group
GroupRemoveUnit(m_dex._GROUP, whichunit)
-- Throw event
throw_event("LEAVE_EVENT", whichunit)
end
-- RemoveUnit is overwritten to instantly deregister the unit
-- instead of anticipating the undefend order
function RemoveUnit(whichunit)
dereg(whichunit)
m_dex._REMOVE(whichunit)
end
local trig = CreateTrigger()
TriggerRegisterEnterRegion(trig, WorldRect.reg, nil)
TriggerAddCondition(trig, Filter(function()
reg(GetTriggerUnit())
end))
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function()
if GetIssuedOrderId() ~= m_dex._UNDEFEND then return;
elseif GetUnitAbilityLevel(GetTriggerUnit(), m_dex._DETECT_LEAVE) ~= 0 then return;
end
dereg(GetTriggerUnit())
end)
-- Enumerate all pre-placed units
local tempgrp = CreateGroup()
GroupEnumUnitsInRect(tempgrp, WorldRect.rect, nil)
ForGroup(tempgrp, function()
reg(GetEnumUnit())
end)
DestroyGroup(tempgrp)
info.preplaced_flag = false
end)
function m_dex.is_preplaced(whichunit)
return info.preplace_table[whichunit]
end
function EnumAllUnits(whichgroup, filter)
if whichgroup == m_dex._GROUP then return;
end
GroupClear(whichgroup)
for i = 0, BlzGroupGetSize(m_dex._GROUP) - 1 do
local uu = BlzGroupUnitAt(m_dex._GROUP, i)
if filter(uu) then
GroupAddUnit(whichgroup, uu)
end
end
end
IsUnitPreplaced = m_dex.is_preplaced
do
local temp = {index={}, max={}}
local function index_pop()
local index = #temp.index
temp.index[index] = nil
temp.max[index] = nil
if index == 1 then
PauseTimer(temp.timer)
end
end
local function index_clear()
while #temp.index > 0 do
index_pop()
end
end
local function index_push()
local index = #temp.index + 1
temp.index[index] = 0
temp.max[index] = BlzGroupGetSize(m_dex._GROUP)
if index == 1 then
if not temp.timer then
temp.timer = CreateTimer()
end
TimerStart(temp.timer, 0.00, false, index_clear)
end
end
local function unit_iterator()
local unit
while (not unit) and (temp.index[#temp.index] < temp.max[#temp.index]) do
unit = BlzGroupUnitAt(m_dex._GROUP, temp.index[#temp.index])
temp.index[#temp.index] = temp.index[#temp.index] + 1
end
if unit then return unit;
end
if temp.index[#temp.index] >= temp.max[#temp.index] then
index_pop()
end
end
function UnitIterator()
index_push()
return unit_iterator
end
end
if info.setdata then
function m_dex.unit_from_id(i)
return info.index_table[i]
end
function m_dex.unit_id(whichunit)
return GetUnitUserData(whichunit)
end
function GetUnitById(i)
return m_dex.unit_from_id(i)
end
function GetUnitId(whichunit)
return m_dex.unit_id(whichunit)
end
end
end
do
local m_state = protected_table()
UnitState = setmetatable({}, m_state)
m_state.__metatable = UnitState
m_state._DETECT_ABIL = FourCC("uDey")
m_state._UNDEFENSE = "magicundefense"
m_state._EVENTS = {
DEATH_EVENT = 1,
RESURRECT_EVENT = 2,
TRANSFORM_EVENT = 3,
LOAD_EVENT = 4,
UNLOAD_EVENT = 5,
}
m_state._properties = {
registered = {},
death_status = {
PHYSICAL_DEAD = {},
EVENT_DEAD = {},
},
unit_type = {},
transporter = {},
transport_grp = {},
}
m_state._listener = PriorityEvent:create()
m_state._listener:preload_registry(
m_state._EVENTS.DEATH_EVENT,
m_state._EVENTS.UNLOAD_EVENT
)
do
local death_tb = m_state._properties.death_status
local transport_grp = m_state._properties.transport_grp
local transporter = m_state._properties.transporter
local event_tb = {count=0}
local zero_timer
Initializer("SYSTEM", function()
zero_timer = CreateTimer()
end)
local function do_throw(tb)
if tb.event == "DEATH_EVENT" then
m_state.eventKiller = tb.killer
m_state._listener:fire(m_state._EVENTS[tb.event], m_state.eventUnit,
m_state.eventKiller, tb.killed)
elseif tb.event == "LOAD_EVENT" or tb.event == "UNLOAD_EVENT" then
m_state.eventTransport = tb.transport
m_state._listener:fire(m_state._EVENTS[tb.event], m_state.eventUnit,
m_state.eventTransport)
elseif tb.event == "TRANSFORM_EVENT" then
m_state.prevUnitType = tb.prev_type
UnitAddAbility(tb.unit, m_state._DETECT_ABIL)
BlzUnitHideAbility(tb.unit, m_state._DETECT_ABIL, true)
m_state._listener:fire(m_state._EVENTS[tb.event], m_state.eventUnit,
m_state.prevUnitType, GetUnitTypeId(m_state.eventUnit))
else
m_state._listener:fire(m_state._EVENTS[tb.event], m_state.eventUnit)
end
m_state.eventKiller = nil
m_state.eventTransport = nil
m_state.prevUnitType = nil
end
local function iterate_events()
local i = 1
local ev_unit = m_state.eventUnit
local ev_type = m_state.eventType
while i <= event_tb.count do
m_state.eventUnit = event_tb[i].unit
m_state.eventType = event_tb[i].event
do_throw(event_tb[i])
event_tb[i] = nil
i = i + 1
end
m_state.eventUnit = ev_unit
m_state.eventType = ev_type
event_tb.count = 0
end
local function throw_event(event, unit)
local tb = {
unit = unit,
event = event,
}
event_tb.count = event_tb.count + 1
event_tb[event_tb.count] = tb
PauseTimer(zero_timer)
TimerStart(zero_timer, 0, false, iterate_events)
return tb
end
local function throw_unload_event(unit, transport)
GroupRemoveUnit(transport_grp[transport], unit)
transporter[unit] = nil
throw_event("UNLOAD_EVENT", unit).transport = transport
end
Initializer("SYSTEM", function()
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function()
local order = GetIssuedOrderId()
if OrderId2String(order) ~= m_state._UNDEFENSE then return;
end
local unit = GetTriggerUnit()
local is_dead = not UnitAlive(unit)
-- Ability was removed, somehow
-- Check if the unit was removed at any point in the game
local cur_type = GetUnitTypeId(unit)
if cur_type == 0 then return;
end
-- Check for death or resurrect event
if (is_dead ~= death_tb.PHYSICAL_DEAD[unit]) then
local event = (is_dead and "DEATH_EVENT") or "RESURRECT_EVENT"
death_tb.PHYSICAL_DEAD[unit] = is_dead
if not is_dead then
death_tb.EVENT_DEAD[unit] = false
end
throw_event(event, unit)
end
if GetUnitAbilityLevel(unit, m_state._DETECT_ABIL) ~= 0 then return;
elseif cur_type == m_state._properties.unit_type[unit] then return;
end
throw_event("TRANSFORM_EVENT", unit).prev_type = m_state._properties.unit_type[unit]
m_state._properties.unit_type[unit] = cur_type
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function()
local unit = GetTriggerUnit()
death_tb.EVENT_DEAD[unit] = true
for i = 1, event_tb.count do
local tb = event_tb[i]
if tb.event == "DEATH_EVENT" and tb.unit == unit then
tb.killer = GetKillingUnit()
tb.killed = true
break
end
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_LOADED, function()
local transport, loadee = GetTransportUnit(), GetTriggerUnit()
if not transport_grp[transport] then
transport_grp[transport] = CreateGroup()
end
GroupAddUnit(transport_grp[transport], loadee)
transporter[loadee] = transport
SetUnitX(loadee, WorldRect.rectMaxX)
SetUnitY(loadee, WorldRect.rectMaxY)
throw_event("LOAD_EVENT", loadee).transport = transport
end)
local trig = CreateTrigger()
TriggerRegisterEnterRegion(trig, WorldRect.reg, nil)
TriggerAddCondition(trig, Filter(function()
local unit = GetTriggerUnit()
if not transporter[unit] or IsUnitLoaded(unit) then return;
end
throw_unload_event(unit, transporter[unit])
end))
end)
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
UnitAddAbility(unit, m_state._DETECT_ABIL)
BlzUnitHideAbility(unit, m_state._DETECT_ABIL, true)
m_state._properties.registered[unit] = true
m_state._properties.unit_type[unit] = GetUnitTypeId(unit)
death_tb.PHYSICAL_DEAD[unit] = not UnitAlive(unit)
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
m_state._properties.registered[unit] = nil
m_state._properties.unit_type[unit] = nil
death_tb[unit] = nil
death_tb.PHYSICAL_DEAD[unit] = nil
death_tb.EVENT_DEAD[unit] = nil
if transport_grp[unit] then
ForGroup(transport_grp[unit], function()
throw_unload_event(GetEnumUnit(), unit)
end)
iterate_events()
DestroyGroup(transport_grp[transport])
transport_grp[transport] = nil
end
end)
function m_state.register(event, func)
if not m_state._EVENTS[event] then return;
end
m_state._listener:register(m_state._EVENTS[event], func)
end
function m_state.deregister(event, func)
if not m_state._EVENTS[event] then return;
end
m_state._listener:deregister(m_state._EVENTS[event], func)
end
local _GetTransportUnit = GetTransportUnit
function GetTransportUnit(whichunit)
if not whichunit then
return _GetTransportUnit()
end
return transporter[whichunit]
end
function GetTransportedGroup(whichunit)
return transport_grp[whichunit]
end
function UnitNotDead(whichunit)
return death_tb.EVENT_DEAD[whichunit]
end
end
local _KillUnit = KillUnit
function KillUnit(whichunit)
if UnitIsSleeping(whichunit) then
BlzUnitDisableAbility(whichunit, m_state._DETECT_ABIL, false, true)
end
_KillUnit(whichunit)
end
end
do
local tb = protected_table()
local show = ShowUnit
tb._monitor = {lizard = SimpleList(), timer = CreateTimer(), INTERVAL = 0.25}
tb._vis = {}
tb._handler = EventListener:create()
UnitVisibility = setmetatable({}, tb)
function tb.register(func)
tb._handler:register(func)
end
function tb._check_visibility(whichunit)
local visible = not IsUnitHidden(whichunit)
if (tb._vis[whichunit] == nil) or (tb._vis[whichunit] ~= visible) then
tb._vis[whichunit] = visible
tb._handler:execute(whichunit, visible)
end
end
function ShowUnit(whichunit, flag)
show(whichunit, flag)
tb._check_visibility(whichunit)
end
function tb._monitor_visibility()
for unit in tb._monitor.lizard:iterator() do
tb._check_visibility(unit)
end
end
function tb._monitor_unit(whichunit)
tb._monitor.lizard:insert(whichunit)
if #tb._monitor.lizard == 1 then
TimerStart(tb._monitor.timer, tb._monitor.INTERVAL, true, tb._monitor_visibility)
end
end
function tb._remove_unit(whichunit)
tb._monitor.lizard:remove(whichunit)
if #tb._monitor.lizard == 0 then
PauseTimer(tb._monitor.timer)
end
end
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
tb._monitor_unit(unit)
tb._check_visibility(unit)
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
tb._remove_unit(unit)
end)
end
do
local m_reg = protected_table()
GenericUnitEvent = setmetatable({}, m_reg)
m_reg._MAX_SIZE = 80
m_reg._COLLECT = 120
m_reg._CUR_COLLECT = 0
m_reg._COLLECT_MIN = 10
m_reg._MAP = {}
m_reg._LIST = LinkedList()
local function is_unitevent(whichevent)
return tostring(whichevent):sub(1,9) == 'unitevent'
end
local function new(whichevent)
local o = o or {}
o.detector = {}
o.container = {}
o.size = {
container = {[0]=0},
events = {[0]=0},
cur_index = 0,
}
o.pointer = {}
o.listener = EventListener:create()
o.unitevent = whichevent
setmetatable(o, m_reg)
return o
end
local function prepare_trigger(self, i)
self.detector[i] = CreateTrigger()
TriggerAddCondition(self.detector[i], Filter(function()
self.listener:execute()
end))
end
function m_reg:register_unit(whichunit)
-- Filter out already-registered units
if self.pointer[whichunit] then return;
end
-- Prepare the instance
local index = self.size.cur_index
if (index == 0) or (self.size.container[index] >= m_reg._MAX_SIZE) then
index = index + 1
self.size.cur_index = index
self.size.container[index] = 0
self.size.events[index] = 0
self.container[index] = CreateGroup()
prepare_trigger(self, index)
end
-- Use an O(n) algorithm search to determine the index for the
-- unit
local i = 1
while i <= index do
if self.size.container[i] < m_reg._MAX_SIZE then break;
end
i = i + 1
end
-- Index determined, register the unit
GroupAddUnit(self.container[i], whichunit)
TriggerRegisterUnitEvent(self.detector[i], whichunit, self.unitevent)
self.pointer[whichunit] = i
self.size.container[i] = self.size.container[i] + 1
self.size.events[i] = self.size.events[i] + 1
end
function m_reg:deregister_unit(whichunit)
-- Filter out deregistered units
if not self.pointer[whichunit] then return;
end
-- Get the index containing the relevant group and
-- number of registered unitevents
local i = self.pointer[whichunit]
GroupRemoveUnit(self.container[i], whichunit)
self.size.events[i] = self.size.events[i] - 1
self.pointer[whichunit] = nil
end
local function clean_event(whichevent)
local self = m_reg._MAP[whichevent]
local index = self.size.cur_index
for i = 1, index do
local delta = self.size.container[i] - self.size.events[i]
-- If there are more than _COLLECT_MIN missing instances
-- perform a collection
if delta > m_reg._COLLECT_MIN then
self.size.container[i] = self.size.events[i]
DestroyTrigger(self.detector[i])
-- Iterate through each member, and re-register them
-- to the trigger
prepare_trigger(self, i)
ForGroup(self.container[i], function()
TriggerRegisterUnitEvent(self.detector[i], GetEnumUnit(), self.unitevent)
end)
end
end
end
local function register_all(whichevent)
local t = m_reg._MAP[whichevent]
for unit in UnitIterator() do
t:register_unit(unit)
end
end
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
for whichevent in m_reg._LIST:iterator() do
m_reg._MAP[whichevent]:register_unit(unit)
end
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
m_reg._CUR_COLLECT = math.floor(math.fmod(m_reg._CUR_COLLECT + 1, m_reg._COLLECT))
local reset = (m_reg._CUR_COLLECT < 1)
for whichevent in m_reg._LIST:iterator() do
m_reg._MAP[whichevent]:deregister_unit(unit)
if reset then
clean_event(whichevent)
end
end
end)
function m_reg.register(whichevent, func)
-- Filter out non-unitevents
if not is_unitevent(whichevent) then return;
end
-- Create an instance if not already available
if not m_reg._MAP[whichevent] then
m_reg._MAP[whichevent] = new(whichevent)
m_reg._LIST:insert(whichevent)
register_all(whichevent)
end
m_reg._MAP[whichevent].listener:register(func)
end
RegisterAnyUnitEvent = m_reg.register
end
do
local exp = protected_table({
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, exp.xpAmount, exp.xpGained)
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
local function enum_update()
local uu = GetEnumUnit()
if exp.HERO_EXP[uu] then
update_exp(uu)
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, enum_update)
end)
end)
function exp.register(func)
evt:register(func)
end
ExperienceEvent = setmetatable({}, exp)
end
-- Thanks to Bribe for the REPO thread about damage event interactions
do
local tb = protected_table()
local dtb = {dt=AllocTableEx(5), mt=AllocTableEx(5)}
DamageEvent = setmetatable({}, tb)
local interactions = {
dmg_type = {
magic = 1,
universal = 2,
normal = 4
},
atk_type = {
normal = 8,
spells = 16,
magic = 32
},
}
interactions.atk_tb = {
[ATTACK_TYPE_MELEE] = interactions.atk_type.normal,
[ATTACK_TYPE_PIERCE] = interactions.atk_type.normal,
[ATTACK_TYPE_SIEGE] = interactions.atk_type.normal,
[ATTACK_TYPE_CHAOS] = interactions.atk_type.normal,
[ATTACK_TYPE_HERO] = interactions.atk_type.normal,
[ATTACK_TYPE_NORMAL] = interactions.atk_type.spells,
[ATTACK_TYPE_MAGIC] = interactions.atk_type.magic,
}
interactions.dmg_tb = {
[DAMAGE_TYPE_UNKNOWN] = interactions.dmg_type.universal,
[DAMAGE_TYPE_UNIVERSAL] = interactions.dmg_type.universal,
[DAMAGE_TYPE_NORMAL] = interactions.dmg_type.normal,
[DAMAGE_TYPE_ENHANCED] = interactions.dmg_type.normal,
[DAMAGE_TYPE_POISON] = interactions.dmg_type.normal,
[DAMAGE_TYPE_DISEASE] = interactions.dmg_type.normal,
[DAMAGE_TYPE_ACID] = interactions.dmg_type.normal,
[DAMAGE_TYPE_DEFENSIVE] = interactions.dmg_type.normal,
[DAMAGE_TYPE_SLOW_POISON] = interactions.dmg_type.normal,
[DAMAGE_TYPE_FIRE] = interactions.dmg_type.magic,
[DAMAGE_TYPE_COLD] = interactions.dmg_type.magic,
[DAMAGE_TYPE_LIGHTNING] = interactions.dmg_type.magic,
[DAMAGE_TYPE_DIVINE] = interactions.dmg_type.magic,
[DAMAGE_TYPE_MAGIC] = interactions.dmg_type.magic,
[DAMAGE_TYPE_SONIC] = interactions.dmg_type.magic,
[DAMAGE_TYPE_FORCE] = interactions.dmg_type.magic,
[DAMAGE_TYPE_DEATH] = interactions.dmg_type.magic,
[DAMAGE_TYPE_MIND] = interactions.dmg_type.magic,
[DAMAGE_TYPE_PLANT] = interactions.dmg_type.magic,
[DAMAGE_TYPE_DEMOLITION] = interactions.dmg_type.magic,
[DAMAGE_TYPE_SPIRIT_LINK] = interactions.dmg_type.magic,
[DAMAGE_TYPE_SHADOW_STRIKE] = interactions.dmg_type.magic,
}
tb._modifier = PriorityEvent:create()
tb._damage = EventListener:create()
tb._after_dmg = EventListener:create()
tb._LIFE_CHANGE_DELTA = 0.00001
tb._MODIFIER_EVENT = {
MODIFIER_EVENT_SYSTEM = 5,
MODIFIER_EVENT_ALPHA = 4,
MODIFIER_EVENT_BETA = 3,
MODIFIER_EVENT_GAMMA = 2,
MODIFIER_EVENT_DELTA = 1,
}
tb._modifier:preload_registry(
tb._MODIFIER_EVENT.MODIFIER_EVENT_DELTA,
tb._MODIFIER_EVENT.MODIFIER_EVENT_SYSTEM
)
tb._pure_flag = false
local function mindex(t, k, v)
return getmetatable(t)[k]
end
local function newindex(t, k, v)
if getmetatable(t)[k] then return;
end
rawset(t, k, v)
end
local function clear_metatable(mt)
mt.source = nil
mt.target = nil
mt.original_dmg = nil
mt.orig_atktype = nil
mt.orig_dmgtype = nil
mt.orig_wpntype = nil
mt.pure = nil
mt.block_dmg = nil
mt.wc3_dmg = nil
mt.__index = nil
mt.__newindex = nil
end
local function clear_dmgtable(t)
t.dmg = nil
t.atktype = nil
t.dmgtype = nil
t.wpntype = nil
t.suspend = nil
end
local function restore_dmgtable(t)
local mt = getmetatable(t)
clear_dmgtable(t)
clear_metatable(mt)
dtb.mt.restore(mt)
dtb.dt.restore(t)
end
local function new_damage_info()
local mt = dtb.mt.request()
mt.source = GetEventDamageSource()
mt.target = GetTriggerUnit()
mt.original_dmg = GetEventDamage()
mt.orig_atktype = BlzGetEventAttackType()
mt.orig_dmgtype = BlzGetEventDamageType()
mt.orig_wpntype = BlzGetEventWeaponType()
mt.pure = tb._pure_flag
mt.block_dmg = 0
local t = dtb.dt.request()
t.dmg = mt.original_dmg
t.atktype = mt.orig_atktype
t.dmgtype = mt.orig_dmgtype
t.wpntype = mt.orig_wpntype
t.suspend = 0
setmetatable(t, mt)
mt.__index = mindex
mt.__newindex = newindex
return t
end
function SuspendDamageEvent(flag)
if not tb.current then return;
end
if flag then
tb.current.suspend = tb.current.suspend + 1
else
tb.current.suspend = tb.current.suspend - 1
end
end
Initializer("SYSTEM", function()
local zero_timer = CreateTimer()
local trigdata = {}
local function do_flush(i)
if i > #dtb then return;
end
restore_dmgtable(dtb[i])
while i < #dtb do
dtb[i] = dtb[i + 1]
i = i + 1
end
dtb[#dtb] = nil
tb.current = dtb[#dtb]
end
-- A clearing callback function that removes dangling events
local function clear_info()
while #dtb > 0 do
do_flush(#dtb)
end
end
local function can_run_events()
return tb.current.suspend <= 0
end
TimerStart(zero_timer, 0.00, false, clear_info)
PauseTimer(zero_timer)
local after_dmg_callback
local function after_dmg_trigcall()
local trig = GetTriggeringTrigger()
local index = trigdata[trig]
DisableTrigger(trig)
DestroyTrigger(trig)
after_dmg_callback(index)
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DAMAGING, function()
local t = new_damage_info()
local mt = getmetatable(t)
dtb[#dtb + 1] = t
tb.current = t
tb._modifier:fire(tb._MODIFIER_EVENT.MODIFIER_EVENT_SYSTEM, t.target, t.source, t.dmg)
tb._modifier:conditional_fire_to(tb._MODIFIER_EVENT.MODIFIER_EVENT_ALPHA,
tb._MODIFIER_EVENT.MODIFIER_EVENT_GAMMA,
can_run_events, t.target, t.source, t.dmg)
if not t.pure then
BlzSetEventAttackType(t.atktype)
BlzSetEventDamageType(t.dmgtype)
BlzSetEventWeaponType(t.wpntype)
BlzSetEventDamage(t.dmg)
else
t.atktype = t.orig_atktype
t.dmgtype = t.orig_dmgtype
t.wpntype = t.orig_wpntype
t.dmg = t.original_dmg
end
if BlzIsUnitInvulnerable(t.target) or not UnitAlive(t.target) then
do_flush(#dtb)
return
end
local is_ethereal = IsUnitType(t.target, UNIT_TYPE_ETHEREAL)
local is_immune = IsUnitType(t.target, UNIT_TYPE_MAGIC_IMMUNE)
local atk_map, dmg_map = interactions.atk_tb[t.atktype], interactions.dmg_tb[t.dmgtype]
if is_ethereal and
(atk_map == interactions.atk_type.normal or
(atk_map == interactions.atk_type.spells and
dmg_map == interactions.dmg_type.normal)) then
do_flush(#dtb)
return
end
if is_immune and
(atk_map == interactions.attackType.magic or
dmg_map == interactions.damageType.magic) then
do_flush(#dtb)
return
end
if #dtb == 1 then
ResumeTimer(zero_timer)
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DAMAGED, function()
local t = dtb[#dtb]
local mt = getmetatable(t)
local index = #dtb
if not t then return;
end
mt.wc3_dmg = GetEventDamage()
t.dmg = mt.wc3_dmg
tb.current = t
-- Lock the ability to modify atktype, dmgtype and wpntype
mt.atktype = t.atktype
mt.dmgtype = t.dmgtype
mt.wpntype = t.wpntype
t.atktype, t.dmgtype, t.wpntype = nil
tb._modifier:conditional_fire(tb._MODIFIER_EVENT.MODIFIER_EVENT_DELTA,
can_run_events, t.target, t.source, t.dmg)
if not mt.pure then
BlzSetEventDamage(t.dmg)
mt.dmg = t.dmg
else
BlzSetEventDamage(mt.original_dmg)
mt.dmg = mt.original_dmg
end
t.dmg = nil
-- This event can modify the amount of damage blocked
-- but not manipulate damage dealt directly
mt.block = nil
t.block = 0
tb._damage:conditional_exec(can_run_events, t.target, t.source, t.dmg)
BlzSetEventDamage(t.dmg - t.block)
-- Check for unit-state changes here.
if BlzIsUnitInvulnerable(t.target) or not UnitAlive(t.target) then
do_flush(index)
return
end
local curHP = GetWidgetLife(t.target)
SetWidgetLife(t.target, math.max(curHP - mt.dmg, 0.406))
local nextHP = GetWidgetLife(t.target)
SetWidgetLife(t.target, curHP)
if mt.dmg ~= 0 and math.max(curHP - nextHP) > tb._LIFE_CHANGE_DELTA then
local detector = CreateTrigger()
TriggerRegisterUnitStateEvent(detector, t.target, UNIT_STATE_LIFE, GREATER_THAN, nextHP)
TriggerRegisterUnitStateEvent(detector, t.target, UNIT_STATE_LIFE, LESS_THAN, nextHP)
TriggerAddCondition(detector, Filter(after_dmg_trigcall))
trigdata[detector] = index
else
after_dmg_callback(index)
end
end)
after_dmg_callback = function(index)
local t = dtb[index]
tb.current = t
tb._after_dmg:conditional_exec(can_run_events, t.target, t.source, t.dmg)
do_flush(index)
end
end)
function tb.register_modifier(event, func)
if not tb._MODIFIER_EVENT[event] then return;
end
tb._modifier:register(tb._MODIFIER_EVENT[event], func)
end
function tb.register_damage(func)
tb._damage:register(func)
end
function tb.register_after_damage(func)
tb._after_dmg:register(func)
end
function IsPhysicalDamage()
return interactions.atk_tb[tb.current.atktype] == interactions.atk_type.normal
end
function IsSpellDamage()
return interactions.atk_tb[tb.current.atktype] == interactions.atk_type.spell
end
function IsMagicDamage()
return interactions.atk_tb[tb.current.atktype] == interactions.atk_type.magic
end
function IsPureDamage()
return tb.current.pure
end
end
do
local m_dmg = getmetatable(DamageEvent)
m_dmg.__metatable = DamageEvent
function UnitDamageTargetPure(source, target, amount)
local old_pure = m_dmg._pure_flag
local result
m_dmg._pure_flag = true
result = UnitDamageTarget(source, target, amount, false, false,
ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL,
nil)
m_dmg._pure_flag = old_pure
return result
end
end
do
local tb = protected_table()
local native = {}
local ltb = {stack = AllocTableEx(2)}
ResearchEvent = setmetatable({}, tb)
tb._data = {}
tb._tech = {list = LinkedList(), pointer = {}}
tb._player_tech = {}
tb._construct = {}
tb._handler = EventListener:create()
-- All elements in the variable argument list
-- are assumed to be integers via FourCC
function tb:add_unittype(...)
local t = {...}
if #t == 0 then return;
end
for i = 1, #t do
local elem = t[i]
if not self.unittype.pointer[elem] then
self.unittype.pointer[elem] = select(2,self.unittype.list:insert(elem))
end
end
end
function tb:remove_unittype(...)
local t = {...}
if #t == 0 then return;
end
for i = 1, #t do
local elem = t[i]
if self.unittype.pointer[elem] then
self.unittype.list:remove(self.unittype.pointer[elem])
self.unittype.pointer[elem] = nil
end
end
end
-- Function arguments
-- (techid, curunit, curlevel[, prevlevel])
function tb.register(techid, func, ...)
if type(techid) ~= 'number' then return;
elseif not is_function(func) then return;
end
if not tb._data[techid] then
tb._data[techid] = {
unittype = {list = LinkedList(), pointer = {}},
callbacks = EventListener:create()
}
setmetatable(tb._data[techid], tb)
tb._tech.pointer[techid] = select(2, tb._tech.list:insert(techid))
end
tb._data[techid]:add_unittype(...)
tb._data[techid].callbacks:register(func)
return tb._data[techid]
end
function tb.subscribe(func)
tb._handler:register(func)
end
function tb._run_callback()
local enum, enumtype; enum = GetEnumUnit(); enumtype = GetUnitTypeId(enum);
if ltb.self.unittype.pointer[enumtype] then
ltb.self.callbacks:execute(ltb.techid, enum, ltb.cur_level, ltb.prev_level)
end
end
function tb:_do_callback(player, techid, cur_level, prev_level)
local grp = CreateGroup()
local prev = ltb.stack.request()
prev.self = ltb.self
prev.prev_level = ltb.prev_level
prev.cur_level = ltb.cur_level
prev.techid = ltb.techid
ltb.techid = techid
ltb.cur_level = cur_level
ltb.prev_level = prev_level
ltb.self = self
GroupEnumUnitsOfPlayer(grp, player, nil)
ForGroup(grp, tb._run_callback)
DestroyGroup(grp)
ltb.self = prev.self
ltb.prev_level = prev.prev_level
ltb.cur_level = prev.cur_level
ltb.techid = prev.techid
ltb.stack.restore(prev)
end
function tb._do_generic_callback(player, techid, eventtype)
eventtype = eventtype or ""
tb._handler:execute(player, techid, eventtype)
end
function tb:_internal_check(player, techid)
local cur_level = GetPlayerTechCount(player, techid, true)
local player_id = GetPlayerId(player)
local prev_level = tb._player_tech[techid][player_id]
if prev_level == cur_level then return;
end
tb._player_tech[techid][player_id] = cur_level
tb._do_callback(self, player, techid, cur_level, prev_level)
end
function tb._check_techid(player, techid)
if not tb._data[techid] then return;
end
tb._internal_check(tb._data[techid], player, techid)
end
native.dec = BlzDecPlayerTechResearched
native.inc = AddPlayerTechResearched
native.set = SetPlayerTechResearched
function BlzDecPlayerTechResearched(player, techid, levels)
native.dec(player, techid, levels)
tb._do_generic_callback(player, techid)
tb._check_techid(player, techid)
end
function AddPlayerTechResearched(player, techid, levels)
native.inc(player, techid, levels)
tb._do_generic_callback(player, techid)
tb._check_techid(player, techid)
end
function SetPlayerTechResearched(player, techid, level)
native.set(player, techid, level)
tb._do_generic_callback(player, techid)
tb._check_techid(player, techid)
end
Initializer("SYSTEM", function()
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_RESEARCH_FINISH, function()
local player, techid = GetTriggerPlayer(), GetResearched()
tb._do_generic_callback(player, techid, "finish")
tb._check_techid(player, techid)
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_RESEARCH_START, function()
local player, techid = GetTriggerPlayer(), GetResearched()
tb._do_generic_callback(player, techid, "start")
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_RESEARCH_CANCEL, function()
local player, techid = GetTriggerPlayer(), GetResearched()
tb._do_generic_callback(player, techid, "cancel")
end)
for techid in tb._tech.list:iterator() do
tb._player_tech[techid] = {}
end
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
local p = Player(i)
for techid in tb._tech.list:iterator() do
tb._player_tech[techid][i] = GetPlayerTechCount(p, techid, true)
if tb._player_tech[techid][i] ~= 0 then
tb._do_callback(tb._data[techid], p, techid, tb._player_tech[techid][i], 0)
end
end
end
end)
function tb._attempt_callback(whichunit, reverse_arg)
local unittype = GetUnitTypeId(whichunit)
local i = GetPlayerId(GetOwningPlayer(whichunit))
for techid in tb._tech.list:iterator() do
local self = tb._data[techid]
local cur_level = tb._player_tech[techid][i]
if self.unittype.pointer[unittype] and (cur_level ~= 0) then
if reverse_arg then
self.callbacks:execute(techid, whichunit, 0, cur_level)
else
self.callbacks:execute(techid, whichunit, cur_level, 0)
end
end
end
end
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
if tb._construct[unit] then return;
end
tb._attempt_callback(unit, false)
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
if tb._construct[unit] then
tb._construct[unit] = nil
return
end
tb._attempt_callback(unit, true)
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_START, function()
tb._construct[GetConstructingStructure()] = true
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, function()
local unit = GetConstructedStructure()
tb._construct[unit] = nil
tb._attempt_callback(unit, false)
end)
end
do
local tb = protected_table({
_THRESHOLD = 50,
_CUR_INDEX = 0,
})
DebugTable = setmetatable({}, tb)
function tb:__call(o, thresh, name)
tb._CUR_INDEX = tb._CUR_INDEX + 1
o = o or protected_table()
o._THRESHOLD = thresh or tb._THRESHOLD
o._COUNTER = {}
o._CMAX = {}
o._NAME = name or "DebugTable[" .. tostring(tb._CUR_INDEX) .. "]"
o.__call = function(self, name, crit)
if type(name) ~= 'string' then return;
end
if not o._COUNTER[name] then
o._COUNTER[name] = 0
o._CMAX[name] = crit or o._THRESHOLD
end
end
o.refresh = function(self, name)
if type(name) ~= 'string' then return;
elseif not o._COUNTER[name] then return;
end
o._COUNTER[name] = 0
end
o.update = function(self, name)
if type(name) ~= 'string' then return;
elseif not o._COUNTER[name] then return;
end
if o._COUNTER[name] == 0 then
doAfter(0.00, o.refresh, o, name)
end
o._COUNTER[name] = o._COUNTER[name] + 1
if o._COUNTER[name] >= o._CMAX[name] then
PauseGame(true)
print(o._NAME .. " >> Game Paused! Recursion without a breakpoint detected.")
print(o._NAME .. " >> Associated name of function: " .. name)
end
end
local mo = setmetatable({}, o)
return mo, o
end
end
do
--[[
IsUnitMoving -> based on Bribe's GUI IsUnitMoving
]]
local tb = protected_table()
tb._INTERVAL = 1/32
tb._MIN_DIST = 1
tb._handler = EventListener:create()
tb._moving = {cx = {}, cy = {}}
tb._list = SimpleList()
Initializer("SYSTEM", function()
tb._timer = CreateTimer()
tb._MIN_DIST = tb._MIN_DIST*tb._MIN_DIST*tb._INTERVAL*tb._INTERVAL
end)
MovementEvent = setmetatable({}, tb)
function tb._check_pos(unit)
local cx, cy = GetUnitX(unit), GetUnitY(unit)
local tx, ty = tb._moving.cx[unit], tb._moving.cy[unit]
local dist = (tx-cx)*(tx-cx) + (ty-cy)*(ty-cy)
local flag = dist >= tb._MIN_DIST
if flag then
tb._moving.cx[unit] = GetUnitX(unit)
tb._moving.cy[unit] = GetUnitY(unit)
end
if tb._moving[unit] ~= flag then
tb._moving[unit] = flag
tb._handler:execute(unit, flag)
end
end
function tb._check_movement()
for unit in tb._list:iterator() do
tb._check_pos(unit)
end
end
function tb._reinstate_unit(unit)
tb._moving[unit] = false
tb._moving.cx[unit] = GetUnitX(unit)
tb._moving.cy[unit] = GetUnitY(unit)
tb._list:insert(unit)
if #tb._list == 1 then
TimerStart(tb._timer, tb._INTERVAL, true, tb._check_movement)
end
end
function tb._suspend_unit(unit)
tb._moving[unit] = nil
tb._moving.cx[unit] = nil
tb._moving.cy[unit] = nil
tb._list:remove(unit)
if #tb._list == 0 then
PauseTimer(tb._timer)
end
end
function tb.register(func)
tb._handler:register(func)
end
function tb.deregister(func)
tb._handler:deregister(func)
end
function IsUnitMoving(whichunit)
return tb._moving[whichunit]
end
RegisterUnitMoveState = tb.register
DeregisterUnitMoveState = tb.deregister
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
tb._reinstate_unit(unit)
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
tb._suspend_unit(unit)
end)
end
do
local tb = protected_table()
tb._DAY = 6
tb._NIGHT = 18
tb._IS_DAY = false
tb._handler = EventListener:create()
DNCycle = setmetatable({}, tb)
function tb.register(func)
tb._handler:register(func)
end
function tb.deregister(func)
tb._handler:deregister(func)
end
function tb.is_daytime()
return tb._IS_DAY
end
function tb.is_nighttime()
return not tb._IS_DAY
end
Initializer("SYSTEM", function()
local trig = CreateTrigger()
TriggerRegisterGameStateEvent(trig, GAME_STATE_TIME_OF_DAY, LESS_THAN, tb._DAY)
TriggerRegisterGameStateEvent(trig, GAME_STATE_TIME_OF_DAY, GREATER_THAN, tb._DAY)
TriggerRegisterGameStateEvent(trig, GAME_STATE_TIME_OF_DAY, LESS_THAN, tb._NIGHT)
TriggerRegisterGameStateEvent(trig, GAME_STATE_TIME_OF_DAY, GREATER_THAN, tb._NIGHT)
TriggerAddCondition(trig, Condition(function()
local time = math.floor(GetFloatGameState(GAME_STATE_TIME_OF_DAY) + 0.5)
local old_status = tb._IS_DAY
local flag
-- Night time
if (time < tb._DAY) or (time >= tb._NIGHT) then
flag = false
else
flag = true
end
if old_status ~= flag then
tb._IS_DAY = flag
-- Throw event
tb._handler:execute(flag)
end
end))
end)
end
OrderMatrix = setmetatable({}, protected_table())
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 function is_unit(whichunit)
return select(1, pcall(GetUnitTypeId, whichunit))
end
local old_index = m_order.__index
function m_order.__index(t, k)
if 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("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()
else
lt.point_x, lt.point_y = 0, 0
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)
function m_order.reorder(unit, index)
local orderInfo = OrderMatrix[unit]
if not orderInfo.orderId[index] then
return false
end
if orderInfo.orderType[index] == OrderMatrix.IMMEDIATE then
IssueImmediateOrderById(unit, orderInfo.orderId[index])
elseif orderInfo.orderType[index] == OrderMatrix.POINT then
IssuePointOrderById(unit, orderInfo.orderId[index], orderInfo.point.x[index], orderInfo.point.y[index])
else
IssueTargetOrderById(unit, orderInfo.orderId[index], orderInfo.target[index])
end
return true
end
end
do
local tb = {}
function IsUnitUnderConstruction(whichunit)
return tb[whichunit] ~= nil
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_START, function()
local unit = GetConstructingStructure()
tb[unit] = true
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, function()
local unit = GetConstructedStructure()
tb[unit] = nil
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, function()
local unit = GetConstructedStructure()
tb[unit] = nil
end)
UnitDex.register("LEAVE_EVENT", function()
tb[UnitDex.eventUnit] = nil
end)
end
do
local _DUMMY
Initializer("SYSTEM", function()
_DUMMY = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), FourCC('uloc'), 0, 0, 0)
ShowUnit(_DUMMY, false)
end)
function GetZ(x, y)
SetUnitX(_DUMMY, x)
SetUnitY(_DUMMY, y)
return BlzGetUnitZ(_DUMMY)
end
GetCoordinateZ = GetZ
GetPointZ = GetZ
end
IsDestructableTree = setmetatable({}, protected_table())
--[[
*************************************************************************************
*
* 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.
*/
]]
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
do
local m_obj = protected_table()
ObjectReader = setmetatable({}, m_obj)
m_obj.BASE_ABIL_ID = FourCC("ANfd")
m_obj._database = {}
m_obj._natives = {
abilityrealfield = BlzGetAbilityRealField,
abilityintegerfield = BlzGetAbilityIntegerField,
abilitystringfield = BlzGetAbilityStringField,
abilitybooleanfield = BlzGetAbilityBooleanField,
abilityreallevelfield = BlzGetAbilityRealLevelField,
abilityintegerlevelfield = BlzGetAbilityIntegerLevelField,
abilitystringlevelfield = BlzGetAbilityStringLevelField,
abilitybooleanlevelfield = BlzGetAbilityBooleanLevelField,
abilityreallevelarrayfield = BlzGetAbilityRealLevelArrayField,
abilityintegerlevelarrayfield = BlzGetAbilityIntegerLevelArrayField,
abilitystringlevelarrayfield = BlzGetAbilityStringLevelArrayField,
abilitybooleanlevelarrayfield = BlzGetAbilityBooleanLevelArrayField,
}
-- ObjectReader.read returns a string value.
function m_obj.read(obj_id, field, format)
obj_id = ((type(obj_id) == 'number') and CC2Four(obj_id)) or obj_id
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[obj_id] then
m_obj._database[obj_id] = {}
end
if not m_obj._database[obj_id][field] then
local str = "<" .. obj_id .. "," .. 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[obj_id][field] = result
return result
end
return m_obj._database[obj_id][field]
end
function m_obj.extract(obj_id, field)
end
Initializer("SYSTEM", function()
local dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), FourCC("uloc"),
WorldRect.rectMinX, WorldRect.rectMinY, 0)
PauseUnit(dummy, false)
ShowUnit(dummy, false)
function m_obj.extract(obj_id, field, lvl, index)
local result
lvl = lvl or 0
lvl = lvl - 1
index = index or 0
local added = UnitAddAbility(dummy, obj_id)
local abil = BlzGetUnitAbility(dummy, obj_id)
local colon_pos = tostring(field):find(':')
local str = tostring(field):sub(1, colon_pos - 1)
if not m_obj._natives[str] then
print("ObjectReader.extract >> Field entry is invalid.")
print("ObjectReader.extract >> type of entry:", str)
return nil
end
if not str:find('level') then
result = m_obj._natives[str](abil, field)
elseif str:find('array') then
result = m_obj._natives[str](abil, field, lvl, index)
else
result = m_obj._natives[str](abil, field, lvl)
end
if added then
UnitRemoveAbility(dummy, obj_id)
end
return result
end
end)
end
do
local m_obj = getmetatable(ObjectReader)
Initializer("SYSTEM", function()
function m_obj.extract_icon(obj_id)
return m_obj.extract(obj_id, ABILITY_SLF_ICON_NORMAL, 1)
end
end)
end
local tb = {[0]=0}
tb._destroy = DestroyTimer
tb._fields = {}
tb._data = {}
-- For doAfter, doUntil, and doRepeat
-- tb[1], tb[2] ... tb[n] = {}
tb._callback = {}
tb._condition = {}
tb._entrycount = {}
tb._fieldcount = 0
function GetTimerData(whichtimer)
return tb._data[whichtimer]
end
function SetTimerData(whichtimer, data)
tb._data[whichtimer] = data
end
local function alloc_entries(whichtimer, ...)
local j = select('#', ...)
while tb._fieldcount < j do
tb._fieldcount = tb._fieldcount + 1
tb[tb._fieldcount] = {}
end
tb._entrycount[whichtimer] = j
if j < 1 then return;
end
local i = 1
while i <= j do
tb[i][whichtimer] = select(i, ...)
i = i + 1
end
end
local function pop_entries(whichtimer)
local j = #tb._fields
while j > 0 do
if tb[j][whichtimer] then
tb._fields[j] = tb[j][whichtimer]
else
tb._fields[j] = nil
end
j = j - 1
end
while j < tb._entrycount[whichtimer] do
j = j + 1
tb._fields[j] = tb[j][whichtimer]
end
end
local function clear_entries(whichtimer)
while tb._entrycount[whichtimer] > 0 do
local j = tb._entrycount[whichtimer]
tb[j][whichtimer] = nil
tb._entrycount[whichtimer] = tb._entrycount[whichtimer] - 1
end
tb._entrycount[whichtimer] = nil
tb._callback[whichtimer] = nil
tb._condition[whichtimer] = nil
end
local function assign_callback(whichtimer, func, cond)
tb._callback[whichtimer] = func
tb._condition[whichtimer] = cond
end
function DestroyTimer(whichtimer)
tb._destroy(whichtimer)
tb._data[whichtimer] = nil
if tb._callback[whichtimer] then
clear_entries(whichtimer)
end
end
function tb.singular_callback()
local t = GetExpiredTimer()
pop_entries(t)
if tb._entrycount[t] > 0 then
tb._callback[t](table.unpack(tb._fields))
else
tb._callback[t]()
end
clear_entries(t)
DestroyTimer(t)
end
function tb.repeated_callback()
local t = GetExpiredTimer()
local cond = tb._condition[t]
local is_func = is_function(cond)
pop_entries(t)
if (is_func and not cond()) or ((not is_func) and cond) then
if tb._entrycount[t] > 0 then
tb._callback[t](table.unpack(tb._fields))
else
tb._callback[t]()
end
return
end
PauseTimer(t)
clear_entries(t)
DestroyTimer(t)
end
function doAfter(dur, func, ...)
if dur < 0. then
func(...)
return
end
local t = CreateTimer()
alloc_entries(t, ...)
assign_callback(t, func)
TimerStart(t, dur, false, tb.singular_callback)
return t
end
function doUntil(intv, cond, func, ...)
local is_func = is_function(cond)
if intv < 0. then
if (is_func and not cond()) or ((not is_func) and cond) then
func(...)
end
return
end
local t = CreateTimer()
alloc_entries(t, ...)
assign_callback(t, func, cond)
TimerStart(t, intv, true, tb.repeated_callback)
return t
end
function doRepeat(intv, func, ...)
return doUntil(intv, true, func, ...)
end
local tb = protected_table({
_stamp = {}
})
TimerEvent = setmetatable({}, tb)
-- iters refers to number of iterations per second.
-- iters will always be coerced to an integer
local function toint(iters)
return (type(iters) ~= 'number' and 1) or math.floor(iters + 0.5)
end
function tb.register(iters, func)
iters = toint(iters)
-- Initialize tb._stamp[iters] if not initialized already
if not tb._stamp[iters] then
tb._stamp[iters] = {
event = EventListener:create(),
timer = CreateTimer(),
running = false,
active_context = 0,
interval = 1/iters,
}
SetTimerData(tb._stamp[iters].timer, iters)
end
tb._stamp[iters].event:register(func)
tb._stamp[iters].event:disable(func)
end
function tb._check(iters, func)
if not tb._stamp[iters] then return false;
elseif not tb._stamp[iters].event:is_registered(func) then return false;
end
return true
end
function tb._on_callback_execute()
local iters = GetTimerData(GetExpiredTimer())
tb._stamp[iters].event:execute()
end
function tb.activate(iters, func)
iters = toint(iters)
if not tb._check(iters, func) then return;
elseif tb._stamp[iters].event:is_enabled(func) then return;
end
tb._stamp[iters].active_context = tb._stamp[iters].active_context + 1
tb._stamp[iters].event:enable(func)
-- Start the timer if it hasn't started yet.
if tb._stamp[iters].running then return;
end
tb._stamp[iters].running = true
TimerStart(tb._stamp[iters].timer, tb._stamp[iters].interval, true, tb._on_callback_execute)
end
function tb.deactivate(iters, func)
iters = toint(iters)
if not tb._check(iters, func) then return;
elseif not tb._stamp[iters].event:is_enabled(func) then return;
elseif not tb._stamp[iters].running then return;
end
tb._stamp[iters].active_context = tb._stamp[iters].active_context - 1
tb._stamp[iters].event:disable(func)
-- Pause the timer if there are no more active instances.
if tb._stamp[iters].active_context > 0 then return;
end
tb._stamp[iters].running = false
PauseTimer(tb._stamp[iters].timer)
end
function tb.is_active(iters)
iters = toint(iters)
if not tb._stamp[iters] then return false;
end
return tb._stamp[iters].running
end
local tb = protected_table()
tb.curList = 0
tb.curFunc = 0
TimerIterator = setmetatable({}, tb)
function tb:create(iters, func)
local o = {}
o.list = SimpleList()
o.iters = iters
o.func = func
o._call = function()
local prev_list, prev_func = tb.curList, tb.curFunc
tb.curList, tb.curFunc = o.list, func
for elem in o.list:iterator() do
func(elem)
end
tb.curList, tb.curFunc = prev_list, prev_fun
if #o.list == 0 then
TimerEvent.deactivate(iters, o._call)
end
end
o.list.debug = true
TimerEvent.register(iters, o._call)
setmetatable(o, tb)
return o
end
function tb:insert(elem)
if self.list:insert(elem) and #self.list == 1 then
TimerEvent.activate(self.iters, self._call)
end
end
function tb:remove(elem)
if self.list:remove(elem) and #self.list <= 0 then
TimerEvent.deactivate(self.iters, self._call)
end
end
function tb:is_elem_in(elem)
return self.list:is_in(elem)
end
--[[
This system was inspired by Spellbound's Socket System,
and was written from scratch.
]]
do
local ftb = {stack={}}
local tb = protected_table({
cur_event = {},
cur_unit = {},
cur_socket = {},
cur_socket_instance = {},
cur_socketer = {},
_cur_context = {},
DEF_RANGE = 300,
DEF_SOCKETS = 2,
_CROW_ABIL = FourCC("Amrf"),
})
local plug = {}
local socket = {}
local info = {socket={}, pos={}, trig_pointer={}}
SocketSystem = setmetatable({}, tb)
tb._events = {
ENTER_EVENT = 1,
ON_SOCKET_EVENT = 2,
LEAVE_EVENT = 3,
}
tb._plug = plug
tb._socket = socket
tb._handler = PriorityEvent:create()
tb._handler:preload_registry(tb._events.ENTER_EVENT, tb._events.LEAVE_EVENT)
function ftb.is_unit(whichunit)
if pcall(GetUnitTypeId, whichunit) then return true;
end
return false
end
function ftb.restore_table(t)
ftb.stack[#ftb.stack + 1] = t
end
function ftb.request_table()
if not ftb.stack[#ftb.stack] then
return {}
end
local t = ftb.stack[#ftb.stack]
ftb.stack[#ftb.stack] = nil
return t
end
function ftb.throw_event(eventtype, observer, occupant, pos, socket)
local index = #tb.cur_event + 1
tb.cur_event[index] = eventtype
tb.cur_unit[index] = occupant
tb.cur_socketer[index] = observer
tb.cur_socket[index] = pos
tb.cur_socket_instance[index] = socket
tb._cur_context[index] = 0
tb._handler:fire(tb._events[eventtype], observer, occupant, pos, socket)
local result = tb._cur_context[index]
tb.cur_event[index] = nil
tb.cur_unit[index] = nil
tb.cur_socketer[index] = nil
tb.cur_socket[index] = nil
tb.cur_socket_instance[index] = nil
tb._cur_context[index] = nil
return result
end
function ftb.set_occupant_pos(occupant, x, y, z, checkpathing)
if UnitAddAbility(occupant, tb._CROW_ABIL) then
UnitRemoveAbility(occupant, tb._CROW_ABIL)
end
if checkpathing then
SetUnitPosition(occupant, x, y)
else
SetUnitX(occupant, x)
SetUnitY(occupant, y)
end
SetUnitFlyHeight(occupant, z, 0)
end
function ftb.add_plug_info(whichunit)
if not plug[whichunit] then
plug[whichunit] = ftb.request_table()
plug[whichunit].list = LinkedList()
plug[whichunit].unit_map = ftb.request_table()
plug[whichunit].pointer = ftb.request_table()
end
end
function ftb.remove_plug_info(whichunit)
if plug[whichunit] then
plug[whichunit].list:destroy()
ftb.restore_table(plug[whichunit].pointer)
ftb.restore_table(plug[whichunit].unit_map)
ftb.restore_table(plug[whichunit])
plug[whichunit].unit_map = nil
plug[whichunit].pointer = nil
plug[whichunit].list = nil
plug[whichunit] = nil
end
end
function ftb.clear_plug_info(whichunit)
while #plug[whichunit].list > 0 do
local self = plug[whichunit].list:first()
self:destroy()
end
end
function ftb.add_instance(observer, self)
local p = plug[observer]
p.pointer[self] = select(2, p.list:insert(self))
end
function ftb.remove_instance(observer, self)
local p = plug[observer]
p.list:remove(p.pointer[self])
p.pointer[self] = nil
end
function ftb.restore_instance(self)
local mt = tb.__metatable
tb.__metatable = nil
setmetatable(self, nil)
tb.__metatable = mt
ftb.restore_table(self)
end
function ftb.clear_list(self)
for pos, pointer in self.socket.free:iterator() do
self.socket.pointer[pos] = nil
self.socket.free:remove(pointer)
end
self.socket.used:destroy()
self.socket.free:destroy()
ftb.restore_table(self.socket.pointer)
end
-- Callback function encased in a filter
Initializer("SYSTEM", function()
ftb.detector_callback = Filter(function()
local self = info.trig_pointer[GetTriggeringTrigger()]
local occupant = GetTriggerUnit()
if occupant == self.observer then return;
elseif #self.socket.free == 0 then return;
elseif socket[occupant] then return;
end
local pos = self:get_nearest_socket(GetUnitX(occupant), GetUnitY(occupant))
local context = ftb.throw_event("ENTER_EVENT", self.observer, occupant, pos, self)
local enterID = GetUnitTypeId(occupant)
if not UnitAlive(self.observer) then
context = context - 1
end
if not self.unit_map:is_elem_in(enterID) then
context = context - 1
end
if context < 0 then return;
end
ftb.add_unit(self, occupant, pos)
end)
end)
-- Socket occupancy functions
function ftb.add_unit(self, occupant, pos)
-- Some assumptions are made here, such as
-- the socket being free, the occupant not
-- being socketed yet.
self.socket.free:remove(self.socket.pointer[pos])
self.socket.pointer[pos] = select(2, self.socket.used:insert(pos))
self.socket[pos].occupant = occupant
socket[occupant] = self.observer
info.socket[occupant] = self
info.pos[occupant] = pos
local tx, ty, tz = self:get_socket_pos(pos)
ftb.set_occupant_pos(occupant, tx, ty, tz)
ftb.throw_event("ON_SOCKET_EVENT", self.observer, occupant, pos, self)
end
function ftb.remove_unit(self, occupant, ignorecontext)
-- Some assumptions are made here, such as
-- the socket being used, the occupant being
-- socketed to the same instance.
local pos = info.pos[occupant]
local tx, ty, tz = self:get_socket_pos(pos)
ftb.set_occupant_pos(occupant, tx, ty, GetUnitDefaultFlyHeight(occupant), true)
local context = ftb.throw_event("LEAVE_EVENT", self.observer, occupant, pos, self)
if not UnitAlive(self.observer) then
context = context + 1
end
if (context < 0) and (not ignorecontext) then
ftb.set_occupant_pos(occupant, tx, ty, tz)
return
end
self.socket.used:remove(self.socket.pointer[pos])
self.socket.pointer[pos] = select(2, self.socket.free:insert(pos))
self.socket[pos].occupant = occupant
socket[occupant] = nil
info.socket[occupant] = nil
info.pos[occupant] = nil
end
-- Socket instance-related functions
function tb:get_socket_pos(pos)
local tx, ty, tz = self.socket[pos].x, self.socket[pos].y, self.socket[pos].z
if not self.socket[pos].abs then
tx, ty = tx + GetUnitX(self.observer), ty + GetUnitY(self.observer)
end
return tx, ty, tz
end
function tb:set_socket_pos(pos, x, y, z, abs)
self.socket[pos].x = x or 0
self.socket[pos].y = y or 0
self.socket[pos].z = z or 0
self.socket[pos].abs = abs
if self.socket[pos].occupant then
local tx, ty, tz = self:get_socket_pos(pos)
ftb.set_occupant_pos(self.socket[pos].occupant, tx, ty, tz)
end
end
function tb:get_nearest_socket(cx, cy)
local pos = 0
local min = 99999999.0
-- Do not iterate if there's no more space.
if #self.socket.free == 0 then
return pos
end
for index in self.socket.free:iterator() do
local tx, ty = self:get_socket_pos(index)
local dist = (tx-cx)*(tx-cx) + (ty-cy)*(ty-cy)
if min >= dist then
min = dist
pos = index
end
end
return pos
end
-- Plug-related functions
function tb._new(whichunit, range, num)
local self = ftb.request_table()
self.observer = whichunit
self.range = range
self.count = num
self.socket = ftb.request_table()
self.unit_map = LinkedList()
self.detector = 0
self.socket.pointer = ftb.request_table()
self.socket.free = LinkedList()
self.socket.used = LinkedList()
for i = 1, num do
self.socket.pointer[i] = select(2, self.socket.free:insert(i))
self.socket[i] = ftb.request_table()
self.socket[i].x = 0
self.socket[i].y = 0
self.socket[i].z = 0
self.socket[i].abs = false
end
setmetatable(self, tb)
return self
end
function tb:destroy(ignorecontext)
DestroyTrigger(self.detector)
self:clear_unittype()
self:clear_occupants(ignorecontext)
-- Restore tables
for i = 1, self.count do
self.socket[i].x = nil
self.socket[i].y = nil
self.socket[i].z = nil
self.socket[i].abs = nil
ftb.restore_table(self.socket[i])
self.socket[i] = nil
end
ftb.clear_list(self)
self.socket.free = nil
self.socket.used = nil
self.socket.pointer = nil
ftb.restore_table(self.socket)
self.unit_map:destroy()
ftb.remove_instance(self.observer, self)
self.unit_map = nil
self.count = nil
self.detector = nil
self.range = nil
self.observer = nil
ftb.restore_instance(self)
end
-- Event-handler related functions
function tb.allow_socket()
if tb.cur_event[#tb.cur_event] == "ENTER_EVENT" then
tb._cur_context[#tb._cur_context] = tb._cur_context[#tb._cur_context] + 1
elseif tb.cur_event[#tb.cur_event] == "LEAVE_EVENT" then
tb._cur_context[#tb._cur_context] = tb._cur_context[#tb._cur_context] - 1
end
end
function tb.prevent_socket()
if tb.cur_event[#tb.cur_event] == "LEAVE_EVENT" then
tb._cur_context[#tb._cur_context] = tb._cur_context[#tb._cur_context] + 1
elseif tb.cur_event[#tb.cur_event] == "ENTER_EVENT" then
tb._cur_context[#tb._cur_context] = tb._cur_context[#tb._cur_context] - 1
end
end
function tb.will_socket()
if tb.cur_event[#tb.cur_event] == "ENTER_EVENT" then
return tb._cur_context[#tb._cur_context] >= 0
elseif tb.cur_event[#tb.cur_event] == "LEAVE_EVENT" then
return tb._cur_context[#tb._cur_context] < 0
end
return true
end
function tb.is_unit_plugged(whichunit)
return socket[whichunit] ~= nil
end
function tb.get_plug(whichunit)
return socket[whichunit]
end
function tb.get_plug_instance(whichunit)
return info.socket[whichunit]
end
function tb:in_socket(whichunit)
return info.socket[whichunit] == self
end
-- Sandboxed functions.
function tb:add_unit(occupant, pos)
-- Verify if occupant is a unit
if not ftb.is_unit(occupant) then return;
elseif not occupant then return;
elseif (socket[occupant] ~= nil) then return;
end
-- Verify if the position is vacant
pos = pos or self:get_nearest_socket(GetUnitX(occupant), GetUnitY(occupant))
if pos == 0 then return;
elseif self.socket.used:is_elem_in(pos) then return;
end
-- Proceed with inclusion
ftb.add_unit(self, occupant, pos)
end
function tb:remove_unit(occupant)
-- Verify if occupant is a unit
if not ftb.is_unit(occupant) then return;
elseif not occupant then return;
end
-- Verify if the socket occupant belongs
-- to this instance.
if info.socket[occupant] ~= self then return;
end
-- Proceed with removal
ftb.remove_unit(self, occupant)
end
function tb:add_unittype(unitid)
local observer = self.observer
-- Check if the unitid is already added to another list
if plug[observer].unit_map[unitid] then return;
end
plug[observer].unit_map[unitid] = select(2, self.unit_map:insert(unitid))
end
function tb:remove_unittype(unitid)
local observer = self.observer
-- Check if the unitid is in this list
if not plug[observer].unit_map[unitid] then return;
elseif not self.unit_map:is_node_in(plug[observer].unit_map[unitid]) then return;
end
self.unit_map:remove(plug[observer].unit_map[unitid])
plug[observer].unit_map[unitid] = nil
end
function tb:clear_unittype()
while #self.unit_map > 0 do
self:remove_unittype(self.unit_map:first())
end
end
function tb:clear_occupants(ignorecontext)
while #self.socket.used > 0 do
local pos = self.socket.used:first()
ftb.remove_unit(self, self.socket[pos].occupant, ignorecontext)
end
end
function tb:_init_detector()
if type(self.detector) ~= 'number' then
info.trig_pointer[self.detector] = nil
DestroyTrigger(self.detector)
end
self.detector = CreateTrigger()
info.trig_pointer[self.detector] = self
TriggerRegisterUnitInRange(self.detector, self.observer, self.range, nil)
TriggerAddCondition(self.detector, ftb.detector_callback)
end
function tb.register_unit(whichunit, range, num)
if not ftb.is_unit(whichunit) then return;
elseif not whichunit then return;
end
-- Apply safe values for range and num
range = range or tb.DEF_RANGE
num = num or tb.DEF_SOCKETS
num = math.max(1, num)
-- Initialize instance, and detector
local self = tb._new(whichunit, range, num)
tb._init_detector(self)
-- Add the instance to the list of plugs the unit has
ftb.add_plug_info(whichunit)
ftb.add_instance(whichunit, self)
return self
end
function tb.register_function(eventtype, func)
if tb._events[eventtype] then
tb._handler:register(tb._events[eventtype], func)
end
end
UnitDex.register("LEAVE_EVENT", function(unit)
if socket[unit] then
local self = info.socket[unit]
ftb.remove_unit(self, unit, true)
end
if plug[unit] then
-- Iterate through a whole list of instances
ftb.clear_plug_info(unit)
ftb.remove_plug_info(unit)
end
end)
UnitState.register("DEATH_EVENT", function(unit)
if socket[unit] then
local self = info.socket[unit]
ftb.remove_unit(self, unit)
end
if plug[unit] then
-- Iterate through a whole list of instances
for self in plug[unit].list:iterator() do
self:clear_occupants()
end
end
end)
tb.throw_event = ftb.throw_event
end
do
local tb = getmetatable(SocketSystem)
local mtb = {stack = {}}
local ftb = {}
local ignore = {list=SimpleList()}
local plug = tb._plug
local socket = tb._socket
ftb.register_unit = tb.register_unit
ftb.destroy = tb.destroy
Initializer("SYSTEM", function()
ignore.timer = CreateTimer()
TimerStart(ignore.timer, 0.00, false, function()
for unit in ignore.list:iterator() do
ignore[unit] = false
ignore.list:remove(unit)
end
end)
PauseTimer(ignore.timer)
end)
function mtb.restore(t)
mtb.stack[#mtb.stack + 1] = t
end
function mtb.request()
if mtb.stack[#mtb.stack] then
local t = mtb.stack[#mtb.stack]
mtb.stack[#mtb.stack] = nil
return t
end
return {}
end
function tb.register_unit(whichunit, range, num)
local self = ftb.register_unit(whichunit, range, num)
mtb[self] = mtb.request()
return self
end
function tb:destroy()
ftb.destroy(self)
mtb.restore(mtb[self])
mtb[self] = nil
end
function tb:order_watched(whichorder)
if #mtb[self] <= 0 then return false;
end
local flag = false
for i = 1, #mtb[self] do
if mtb[self][i] == whichorder then
flag = true
break
end
end
return flag
end
function tb:watch_order(whichorder)
if not self:order_watched(whichorder) then
mtb[self][#mtb[self] + 1] = whichorder
end
end
function tb:on_enter(func)
if is_function(func) then
mtb[self].enter_func = func
end
end
function tb:on_socket(func)
if is_function(func) then
mtb[self].socket_func = func
end
end
function tb:on_leave(func)
if is_function(func) then
mtb[self].leave_func = func
end
end
-- Check if the unit is being issued the appropriate order
tb.register_function("ENTER_EVENT", function(observer, occupant, pos, self)
if #mtb[self] > 0 then
local orderInfo = OrderMatrix[occupant]
if not self:order_watched(orderInfo.order[1]) then
tb.prevent_socket()
elseif orderInfo.target[1] ~= observer then
tb.prevent_socket()
end
end
if mtb[self].enter_func then
mtb[self].enter_func(observer, occupant, pos, self)
end
end)
tb.register_function("ON_SOCKET_EVENT", function(observer, occupant, pos, self)
if mtb[self].socket_func then
mtb[self].socket_func(observer, occupant, pos, self)
end
end)
tb.register_function("LEAVE_EVENT", function(observer, occupant, pos, self)
if #mtb[self] > 0 then
local orderInfo = OrderMatrix[occupant]
if not self:order_watched(orderInfo.order[1]) then
tb.prevent_socket()
elseif orderInfo.target[1] ~= observer then
tb.prevent_socket()
end
end
if mtb[self].leave_func then
mtb[self].leave_func(observer, occupant, pos, self)
end
end)
function ftb.unsocket(occupant)
local self = tb.get_plug_instance(occupant)
local event, func = EventListener.get_event(), EventListener.get_cur_function()
if #mtb[self] <= 0 then return;
end
event:disable(func)
tb.ignore_unit_orders(occupant)
self:remove_unit(occupant)
event:enable(func)
end
function ftb.on_order()
local unit = GetTriggerUnit()
if not socket[unit] then return;
elseif ignore[unit] then return;
end
ftb.unsocket(unit)
end
function tb.ignore_unit_orders(whichunit)
ignore[whichunit] = true
ignore.list:insert(whichunit)
ResumeTimer(ignore.timer)
end
local function check_func(self, order, unit, targ)
if #mtb[self]== 0 then return;
elseif not self:order_watched(order) then return;
elseif not IsUnitInRange(unit, targ, self.range) then return;
end
local pos = self:get_nearest_socket(GetUnitX(unit), GetUnitY(unit))
if pos == 0 then return;
end
local context = tb.throw_event("ENTER_EVENT", targ, unit, pos, self)
local enterID = GetUnitTypeId(unit)
if not UnitAlive(targ) then
context = context - 1
end
if not self.unit_map:is_elem_in(enterID) then
context = context - 1
end
if self:in_socket(unit) then
context = -1
end
if context < 0 then return;
end
self:add_unit(unit)
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, ftb.on_order)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, ftb.on_order)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function()
local unit, targ = GetTriggerUnit(), GetOrderTargetUnit()
local order = OrderId2String(GetIssuedOrderId())
if not plug[targ] then return;
end
tb.ignore_unit_orders(unit)
for self in plug[targ].list:iterator() do
check_func(self, order, unit, targ)
end
end)
end
do
local tb = protected_table()
BonusFactory = setmetatable({}, tb)
tb.SUM = {}
tb.PRODUCT = {}
tb.ADD = tb.SUM
tb.MUL = tb.PRODUCT
function tb:__call(o, func)
o = protected_table(o)
o._sum = {}
o._product = {}
o._base = {}
o._access = {[tb.SUM] = o._sum, [tb.PRODUCT] = o._product}
o._def = {[tb.SUM] = 0, [tb.PRODUCT] = 1}
o._access[tb.SUM] = o._sum
o._access[tb.PRODUCT] = o._product
if is_function(func) then
o._parse_modifier = func
end
function o._assign_bonus_value(whichunit)
o._sum[whichunit] = {list = SimpleList(), total = 0}
o._product[whichunit] = {list = SimpleList(), total = 1, zeros = 0}
end
function o._assign_base_value(whichunit)
o.set_base(whichunit, 1)
end
function o._apply_modifier(whichunit)
local result = o._base[whichunit]
if o._product[whichunit].zeros > 0 then
result = o._sum[whichunit].total
else
result = result*o._product[whichunit].total + o._sum[whichunit].total
end
if o._parse_modifier then
o._parse_modifier(whichunit, result)
end
end
function o:_set_modifier(prev_amt, amt, ignore)
if self.bonus_type == tb.SUM then
o._sum[self.unit].total = o._sum[self.unit].total - prev_amt + amt
elseif self.bonus_type == tb.PRODUCT then
if (amt == 0) then
if not self.zeroed then
o._product[self.unit].zeros = o._product[self.unit].zeros + 1
self.zeroed = true
end
return
end
if self.zeroed then
o._product[self.unit].zeros = o._product[self.unit].zeros - 1
self.zeroed = false
end
o._product[self.unit].total = o._product[self.unit].total/prev_amt*amt
end
self.amount = amt
if not ignore then
o._apply_modifier(self.unit)
end
end
function o._insert_bonus(whichunit, oo)
o._access[oo.bonus_type][whichunit].list:insert(oo)
o._set_modifier(oo, o._def[oo.bonus_type], oo.amount)
end
-- Never apply a modifier to the base directly.
function o:apply_bonus(whichunit, amount, bonustype)
bonustype = tb[bonustype] or tb.SUM
local oo = {
unit = whichunit,
amount = amount,
bonus_type = bonustype,
zeroed = false,
}
o._insert_bonus(whichunit, oo)
setmetatable(oo, o)
return oo
end
function o:set_bonus(amount, ignore)
o._set_modifier(self, self.amount, amount, ignore)
end
function o.set_base(whichunit, amount)
o._base[whichunit] = amount
o._apply_modifier(whichunit)
end
function o.get_base(whichunit)
return o._base[whichunit] or 0
end
function o.get_sum(whichunit)
return o._sum[whichunit].total
end
function o.get_product(whichunit)
if o._product[whichunit].zeros > 0 then
return 0
end
return o._product[whichunit].total
end
function o:remove_bonus(ignore)
if not self.amount then return;
end
self:set_bonus(o._def[self.bonus_type], ignore)
o._access[self.bonus_type][self.unit].list:remove(self)
self.bonus_type = nil
self.amount = nil
self.zeroed = nil
self.unit = nil
end
o.destroy = o.remove_bonus
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
o._assign_bonus_value(unit)
if o._assign_base_value then
o._assign_base_value(unit)
end
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
for modifier in o._sum[unit].list:iterator() do
modifier:remove_bonus(true)
end
for modifier in o._product[unit].list:iterator() do
modifier:remove_bonus(true)
end
o._sum[unit].list:destroy()
o._product[unit].list:destroy()
o._sum[unit] = nil
o._product[unit] = nil
o._base[unit] = nil
end)
return o
end
end
do
local tb = BonusFactory({
BASE_ABIL = FourCC("Z000")
})
BonusArmor = setmetatable({}, tb)
function tb._parse_modifier(whichunit, amount)
local abil = BlzGetUnitAbility(whichunit, tb.BASE_ABIL)
BlzSetAbilityIntegerLevelField(abil, ABILITY_ILF_DEFENSE_BONUS_IDEF, 0,
math.floor(amount + 0.5))
IncUnitAbilityLevel(whichunit, tb.BASE_ABIL)
DecUnitAbilityLevel(whichunit, tb.BASE_ABIL)
end
function tb._assign_base_value(whichunit)
UnitAddAbility(whichunit, tb.BASE_ABIL)
UnitMakeAbilityPermanent(whichunit, true, tb.BASE_ABIL)
tb.set_base(whichunit, 0)
end
end
do
local tb = BonusFactory()
local ptb = {}
tb.BONUS_ABIL = FourCC("Z001")
BonusHP = setmetatable({}, tb)
function tb._parse_modifier(whichunit, amount)
if not ptb[whichunit] then
ptb[whichunit] = 0
end
amount = math.floor(amount + 0.5)
UnitAddAbility(whichunit, tb.BONUS_ABIL)
local abil = BlzGetUnitAbility(whichunit, tb.BONUS_ABIL)
IncUnitAbilityLevel(whichunit, tb.BONUS_ABIL)
BlzSetAbilityIntegerLevelField(abil, ABILITY_ILF_MAX_LIFE_GAINED, 0, -amount + ptb[whichunit])
DecUnitAbilityLevel(whichunit, tb.BONUS_ABIL)
UnitRemoveAbility(whichunit, tb.BONUS_ABIL)
ptb[whichunit] = amount
end
function tb._assign_base_value(whichunit)
tb.set_base(whichunit, 0)
end
UnitDex.register("LEAVE_EVENT", function()
ptb[whichunit] = nil
end)
end
do
local tb = BonusFactory()
BonusDamageModifier = setmetatable({}, tb)
function tb._parse_modifier(whichunit, amount)
end
function tb._assign_base_value(whichunit)
tb.set_base(whichunit, 0)
end
DamageEvent.register_modifier("MODIFIER_EVENT_SYSTEM", function(targ)
if IsPureDamage() then return;
end
local ratio = tb.get_product(targ)
local amount = tb.get_sum(targ)
DamageEvent.current.dmg = math.max(DamageEvent.current.dmg*ratio - amount, 0)
end)
end
do
local tb = protected_table()
local btb = {stack={}}
BuffWatcher = setmetatable({}, tb)
tb._u_buff_list = btb -- This contains the list of instances of registered buffs per unit.
tb._TICKS = 10 -- This is on a per-second basis
tb.INTERVAL = 1/tb._TICKS
local function is_unit(whichunit)
return tostring(whichunit):sub(1, 5) == "unit:"
end
local function is_num(buffid)
return type(buffid) == 'number'
end
local function restore_table(t)
btb.stack[#btb.stack + 1] = t
end
local function request_table()
if btb.stack[#btb.stack] then
local t = btb.stack[#btb.stack]
btb.stack[#btb.stack] = nil
return t
end
return {}
end
local function restore_instance(self)
local mt = tb.__metatable
tb.__metatable = nil
setmetatable(self, nil)
tb.__metatable = mt
restore_table(self)
end
local function internal_dest(self)
if self.dest_flag or (self.dest_flag == nil) then return;
end
self.dest_flag = true
if rawget(self, 'check_func') then
self.check_func = nil
end
if rawget(self, 'dest_func') then
local func = self.dest_func
self.dest_func = nil
pcall(func, self.unit, self.buff)
end
tb._LIST:remove(self)
btb[self.unit].list:remove(btb[self.unit][self.buff])
btb[self.unit][self.buff] = nil
self.unit = nil
self.buff = nil
self.dest_flag = nil
restore_instance(self)
end
function tb._remove_info(whichunit)
if btb[whichunit] then
btb[whichunit].list:destroy()
restore_table(btb[whichunit])
btb[whichunit].list = nil
btb[whichunit] = nil
end
end
function tb._add_info(whichunit)
if not btb[whichunit] then
btb[whichunit] = request_table()
btb[whichunit].list = LinkedList()
end
end
tb._LIST = TimerIterator:create(tb._TICKS, function(self)
if GetUnitAbilityLevel(self.unit, self.buff) == 0 then
internal_dest(self)
return
end
if rawget(self, 'check_func') then
self.check_func(self.unit, self.buff)
end
end)
function tb._create(whichunit, buffid)
local o = request_table()
o.unit = whichunit
o.buff = buffid
o.dest_flag = false
setmetatable(o, tb)
return o
end
function tb:_destroy()
internal_dest(self)
end
function tb:check()
if GetUnitAbilityLevel(self.unit, self.buff) == 0 then
internal_dest(self)
end
end
function tb.watch(whichunit, buffid)
-- Check parameters
if (not is_unit(whichunit)) or (not is_num(buffid)) then return;
end
if btb[whichunit] and btb[whichunit][buffid] then
return btb[whichunit][buffid].data
end
-- Create a table for unit
tb._add_info(whichunit)
local list = btb[whichunit]
local self = tb._create(whichunit, buffid)
list[buffid] = select(2, list.list:insert(self))
tb._LIST:insert(self)
return self
end
function tb:on_buff_check(func)
if not is_function(func) then return;
end
rawset(self, 'check_func', func)
end
function tb:on_buff_remove(func)
if not is_function(func) then return;
end
rawset(self, 'dest_func', func)
end
end
do
local tb = getmetatable(BuffWatcher)
local list = LinkedList()
local buffs = SimpleList()
tb._aphandler = EventListener:create()
local add_unit
Initializer("SYSTEM", function()
local u_list = SimpleList()
local t = CreateTimer()
TimerStart(t, 0.00, false, function()
for unit, pointer in u_list:iterator() do
for buffid in buffs:iterator() do
tb._evaluate_buff(unit, buffid)
end
for buffid in list:iterator() do
tb._evaluate_buff(unit, buffid)
end
u_list:remove(pointer)
end
list:clear()
end)
PauseTimer(t)
add_unit = function(unit)
if u_list:is_in(unit) then return;
end
u_list:insert(unit)
ResumeTimer(t)
end
end)
function tb.register_buff(whichbuff)
whichbuff = (type(whichbuff) == 'string' and FourCC(whichbuff)) or whichbuff
if type(whichbuff) ~= 'number' then return;
elseif buffs:is_in(whichbuff) then return;
end
buffs:insert(whichbuff)
end
function tb._on_apply(unit, buff, buffer)
tb._aphandler:execute(unit, buff, buffer)
end
function tb._evaluate_buff(targ, buffid)
if (GetUnitAbilityLevel(targ, buffid) == 0) then return;
end
if (not tb._u_buff_list[targ]) or (not tb._u_buff_list[targ][buffid]) then
local buffer = tb.watch(targ, buffid)
tb._on_apply(targ, buffid, buffer)
end
end
function tb.register_function(func)
tb._aphandler:register(func)
end
-- Catch the moment the buffed unit is "damaged"
DamageEvent.register_modifier("MODIFIER_EVENT_SYSTEM", function(targ, src, dmg)
if dmg ~= 0.00 then return;
elseif DamageEvent.current.atktype ~= ATTACK_TYPE_NORMAL then return;
elseif (DamageEvent.current.dmgtype ~= DAMAGE_TYPE_UNKNOWN)
and (DamageEvent.current.dmgtype ~= DAMAGE_TYPE_NORMAL) then return;
end
-- Check the source and target for any lost or gained buffs.
if tb._u_buff_list[src] then
for self in tb._u_buff_list[src].list:iterator() do
if GetUnitAbilityLevel(src, self.buff) == 0 then
list:insert(self.buff)
tb._destroy(self)
end
end
end
add_unit(targ)
end)
end
do
local tb = getmetatable(BuffWatcher)
tb.__metatable = BuffWatcher
-- Does not sandbox the function at all,
-- but adds the ability for buffs to listen to its' removal
local _native = {
removeAbil = UnitRemoveAbility,
removeBuffs = UnitRemoveBuffs,
removeBuffsEx = UnitRemoveBuffsEx,
}
function UnitRemoveAbility(whichunit, abilId)
local result = _native.removeAbil(whichunit, abilId)
if not result then return result;
elseif not tb._u_buff_list[whichunit] then return result;
elseif not tb._u_buff_list[whichunit][abilId] then return result;
end
local self = tb._u_buff_list[whichunit][abilId]
tb._destroy(self)
return result
end
function UnitRemoveBuffs(whichunit, remove_pos, remove_neg)
_native.removeBuffs(whichunit, remove_pos, remove_neg)
if not tb._u_buff_list[whichunit] then return;
end
local list = tb._u_buff_list[unit]
for self in list.list:iterator() do
self:check()
end
end
-- UnitRemoveBuffsEx(whichUnit, removePositive, removeNegative, magic, physical, timedLife, aura, autoDispel)
function UnitRemoveBuffsEx(whichunit, remove_pos, remove_neg, ...)
_native.removeBuffsEx(whichunit, remove_pos, remove_neg, ...)
if not tb._u_buff_list[whichunit] then return;
end
local list = tb._u_buff_list[unit]
for self in list.list:iterator() do
self:check()
end
end
UnitState.register("DEATH_EVENT", function(unit)
if not tb._u_buff_list[unit] then return;
end
local list = tb._u_buff_list[unit]
for self in list.list:iterator() do
tb._destroy(self)
end
end)
UnitDex.register("LEAVE_EVENT", function(unit)
if not tb._u_buff_list[unit] then return;
end
local list = tb._u_buff_list[unit]
for self in list.list:iterator() do
tb._destroy(self)
end
list.list:destroy()
tb._u_buff_list[unit] = nil
end)
end
do
local tb = protected_table()
tb._race = {
[RACE_HUMAN] = {},
[RACE_ORC] = {},
[RACE_UNDEAD] = {},
[RACE_NIGHTELF] = {},
[RACE_DEMON] = {},
[RACE_OTHER] = {},
}
tb._hall = {pointer={}}
tb._hero = {pointer={}}
CustomMelee = setmetatable({}, tb)
function tb._new(o)
o = o or {}
o.race = 0
o.name = 0
o.setup = 0
o.ai_script = 0
o.hall_list = {pointer={}}
o.hero_list = {pointer={}}
setmetatable(o, tb)
return o
end
function tb:_get_random_hero(whichplayer, hero_x, hero_y)
local v = VersionGet()
local roll = math.random(1, #self.hero_list)
local owner = GetPlayerId(whichplayer)
-- Translate the roll into a unitid.
local pick = self.hero_list[roll]
local hero = CreateUnit(whichplayer, pick, hero_x, hero_y, bj_UNIT_FACING)
if bj_meleeGrantHeroItems then
if hero and (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 tb.add_faction(race, factionname)
local o = tb._new()
o.race = race
o.name = factionname
o.setup = 0
tb._race[race][#tb._race[race] + 1] = o
return o
end
function tb:add_heroID(...)
local t = {...}
if #t == 0 then return;
end
for i = 1, #t do
t[i] = ((type(t[i]) == 'string') and FourCC(t[i])) or t[i]
if not self.hero_list.pointer[t[i]] then
self.hero_list[#self.hero_list + 1] = t[i]
self.hero_list.pointer[t[i]] = #self.hero_list
end
if not tb._hero.pointer[t[i]] then
tb._hero[#tb._hero + 1] = t[i]
tb._hero.pointer[t[i]] = #tb._hero
end
end
end
function tb:add_hallID(...)
local t = {...}
if #t == 0 then return;
end
for i = 1, #t do
t[i] = ((type(t[i]) == 'string') and FourCC(t[i])) or t[i]
if not self.hall_list.pointer[t[i]] then
self.hall_list[#self.hall_list + 1] = t[i]
self.hall_list.pointer[t[i]] = #self.hall_list
end
if not tb._hall.pointer[t[i]] then
tb._hall[#tb._hall + 1] = t[i]
tb._hall.pointer[t[i]] = #tb._hall
end
end
end
function tb:remove_hallID(...)
local t = {...}
-- Assume that the parameters passed are raw codes.
if #t == 0 then return;
end
for i = 1, #t do
t[i] = ((type(t[i]) == 'string') and FourCC(t[i])) or t[i]
if self.hall_list.pointer[t[i]] then
local j = self.hall_list.pointer[t[i]]
while j < #self.hall_list do
self.hall_list[j] = self.hall_list[j + 1]
self.hall_list.pointer[self.hall_list[j + 1]] = j
j = j + 1
end
self.hall_list[#self.hall_list] = nil
end
if tb._hall.pointer[t[i]] then
local j = tb._hall.pointer[t[i]]
while j < #tb._hall do
tb._hall[j] = tb._hall[j + 1]
tb._hall.pointer[tb._hall[j + 1]] = j
j = j + 1
end
tb._hall[#tb._hall] = nil
end
end
end
function tb:remove_heroID(...)
local t = {...}
-- Assume that the parameters passed are raw codes.
if #t == 0 then return;
end
for i = 1, #t do
t[i] = ((type(t[i]) == 'string') and FourCC(t[i])) or t[i]
if self.hero_list.pointer[t[i]] then
local j = self.hero_list.pointer[t[i]]
while j < #self.hero_list do
self.hero_list[j] = self.hero_list[j + 1]
self.hero_list.pointer[self.hero_list[j + 1]] = j
j = j + 1
end
self.hero_list[#self.hero_list] = nil
end
if tb._hero.pointer[t[i]] then
local j = tb._hero.pointer[t[i]]
while j < #tb._hero do
tb._hero[j] = tb._hero[j + 1]
tb._hero.pointer[tb._hero[j + 1]] = j
j = j + 1
end
tb._hero[#tb._hero] = nil
end
end
end
function tb:config_setup(func)
if not is_function(func) then return;
end
self.setup = func
end
function tb:ai_setup(melee_ai)
if type(melee_ai) ~= 'string' then return;
end
self.ai_script = melee_ai
end
function tb:generate_setup(func)
local t = {}
return function(whichplayer, startloc, doheroes, docamera, dopreload)
t.random_flag = IsMapFlagSet(MAP_RANDOM_HERO)
t.peon_x, t.peon_y, t.hero_x, t.hero_y = func(whichplayer, startloc,
doheroes, docamera, dopreload)
if doheroes then
if t.random_flag then
tb._get_random_hero(self, whichplayer, t.hero_x, t.hero_y)
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, t.peon_x, t.peon_y)
SetCameraQuickPositionForPlayer(whichplayer, t.peon_x, t.peon_y)
end
end
end
end
do
CustomMeleeSetup = {
unitSpacing = 64.00,
minTreeDist = 3.50,
minWispDist = 1.75,
}
CustomMeleeSetup.minTreeDist = CustomMeleeSetup.minTreeDist * bj_CELLWIDTH
CustomMeleeSetup.minWispDist = CustomMeleeSetup.minWispDist * bj_CELLWIDTH
do
-- Starting Base Layouts.
local funcs = CustomMeleeSetup
funcs.human = CustomMelee.add_faction(RACE_HUMAN, "Alliance")
funcs.orc = CustomMelee.add_faction(RACE_ORC, "Horde")
funcs.undead = CustomMelee.add_faction(RACE_UNDEAD, "Scourge")
funcs.elf = CustomMelee.add_faction(RACE_NIGHTELF, "Sentinel")
funcs.demon = CustomMelee.add_faction(RACE_DEMON, "Other")
funcs.other = CustomMelee.add_faction(RACE_OTHER, "Other")
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
local heroX, heroY = GetLocationX(heroLoc), GetLocationY(heroLoc)
RemoveLocation(heroLoc)
return peonX, peonY, heroX, heroY, townHall, nearestMine
end
funcs.def_human = function(whichplayer, startloc, doheroes, docamera, dopreload)
if (dopreload) then
Preloader("scripts\\HumanMelee.pld")
end
local peonX, peonY, heroX, heroY, townHall = funcs.def(whichplayer, startloc, 'htow', 'hpea')
if (townHall ~= nil) then
UnitAddAbility(townHall, FourCC('Amic'))
UnitMakeAbilityPermanent(townHall, true, FourCC('Amic'))
end
return peonX, peonY, heroX, heroY
end
funcs.def_orc = function(whichplayer, startloc, doheroes, docamera, dopreload)
if (dopreload) then
Preloader("scripts\\OrcMelee.pld")
end
local peonX, peonY, heroX, heroY = funcs.def(whichplayer, startloc, 'ogre', 'opeo')
return peonX, peonY, heroX, heroY
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
local heroX, heroY = GetLocationX(heroLoc), GetLocationY(heroLoc)
RemoveLocation(heroLoc)
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
local heroX, heroY = GetLocationX(heroLoc), GetLocationY(heroLoc)
RemoveLocation(heroLoc)
return peonX, peonY, heroX, heroY
end
funcs.def_human = funcs.human:generate_setup(funcs.def_human)
funcs.def_orc = funcs.orc:generate_setup(funcs.def_orc)
funcs.def_undead = funcs.undead:generate_setup(funcs.def_undead)
funcs.def_nightelf = funcs.elf:generate_setup(funcs.def_nightelf)
funcs.def_demon = MeleeStartingUnitsUnknownRace
funcs.def_other = MeleeStartingUnitsUnknownRace
funcs.human:config_setup(funcs.def_human)
funcs.orc:config_setup(funcs.def_orc)
funcs.undead:config_setup(funcs.def_undead)
funcs.elf:config_setup(funcs.def_nightelf)
funcs.demon:config_setup(funcs.def_demon)
funcs.other:config_setup(funcs.def_other)
funcs.human:ai_setup("human.ai")
funcs.orc:ai_setup("orc.ai")
funcs.undead:ai_setup("undead.ai")
funcs.elf:ai_setup("elf.ai")
-- Registering Hero Ids.
funcs.human:add_heroID('Hamg', 'Hmkg', 'Hpal', 'Hblm')
funcs.orc:add_heroID('Obla', 'Ofar', 'Otch', 'Oshd')
funcs.elf:add_heroID('Edem', 'Ekee', 'Emoo', 'Ewar')
funcs.undead:add_heroID('Udea', 'Udre', 'Ulic', 'Ucrl')
funcs.other:add_heroID('Npbm', 'Nbrn', 'Nngs', 'Nplh',
'Nbst', 'Nalc', 'Ntin', 'Nfir')
funcs.human:add_hallID('htow', 'hkee', 'hcas')
funcs.orc:add_hallID('ogre', 'ostr', 'ofrt')
funcs.undead:add_hallID('unpl', 'unp1', 'unp2')
funcs.elf:add_hallID('etol', 'etoa', 'etoe')
end
end
do
local tb = getmetatable(CustomMelee)
local races = {
RACE_HUMAN,
RACE_ORC,
RACE_UNDEAD,
RACE_NIGHTELF,
RACE_DEMON,
RACE_OTHER
}
tb.dialog = {}
tb._faction = {}
tb.active_players = {
force = CreateForce(),
count = 0,
}
function tb.dialog.add_player(whichplayer)
ForceAddPlayer(tb.active_players.force, whichplayer)
end
function tb.dialog.remove_player(whichplayer)
ForceRemovePlayer(tb.active_players.force, whichplayer)
end
function tb.dialog.create_race(player, race, index)
local indexStartLoc = GetStartLocationLoc(GetPlayerStartLocation(player))
local self = tb._race[race][index]
tb._faction[GetPlayerId(player)] = self
self.setup(player, indexStartLoc, true, true, true)
RemoveLocation(indexStartLoc)
end
function tb.dialog.show()
local pause_status = 0
ForForce(tb.active_players.force, function()
pause_status = pause_status + 1
end)
if pause_status > 0 then
tb.dialog.pause_state = true
doAfter(0.00, function()
SuspendTimeOfDay(true)
ForForce(tb.active_players.force, function()
local p = GetEnumPlayer()
local race = GetPlayerRace(p)
doAfter(0.00, DialogDisplay, p, tb.dialog[race].main, true)
end)
for unit in UnitIterator() do
PauseUnit(unit, true)
end
end)
end
end
function tb.active_players.enum_players()
tb.active_players.count = tb.active_players.count + 1
end
function tb.dialog.check_unpause_all()
if not tb.dialog.pause_state then return;
end
local pause_status
tb.active_players.count = 0
ForForce(tb.active_players.force, tb.active_players.enum_players)
pause_status = tb.active_players.count
if pause_status == 0 then
DestroyForce(tb.active_players.force)
SuspendTimeOfDay(false)
tb.active_players.force = nil
tb.dialog.pause_state = nil
for unit in UnitIterator() do
PauseUnit(unit, false)
end
end
end
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
if tb.dialog.pause_state then
PauseUnit(unit, true)
end
end)
Initializer("SYSTEM", function()
local trig = CreateTrigger()
for i = 1, #races do
tb.dialog[races[i]] = {main = DialogCreate(), button={}}
TriggerRegisterDialogEvent(trig, tb.dialog[races[i]].main)
end
doAfter(0.00, function()
for i = 1, #races do
local main = tb.dialog[races[i]].main
DialogSetMessage(main, "Select your faction:")
for j = 1, #tb._race[races[i] ] do
local self = tb._race[races[i] ][j]
tb.dialog[races[i] ].button[j] = DialogAddButton(main, self.name, j - 1)
end
end
end)
TriggerAddCondition(trig, Condition(function()
local main = GetClickedDialog()
local button = GetClickedButton()
local player = GetTriggerPlayer()
local race = GetPlayerRace(player)
DialogDisplay(player, main, false)
tb.dialog.remove_player(player)
local i = 0
while i < #tb.dialog[race].button do
if button == tb.dialog[race].button[i + 1] then
i = i + 1
break
end
i = i + 1
end
tb.dialog.create_race(player, race, i)
tb.dialog.check_unpause_all()
end))
end)
end
local tb = getmetatable(CustomMelee)
local mtb = {
ENUM_GROUP = CreateGroup(),
TEST_GROUP = CreateGroup(),
}
local hall = tb._hall
mtb.get_ally_sc = MeleeGetAllyStructureCount
function mtb.init_tournament()
-- 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)
local 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)
end
function mtb.check_victors()
local players = CreateForce()
local gameOver = false
-- Check to see if any players have opponents remaining.
for i = 0, bj_MAX_PLAYERS - 1 do
if (not bj_meleeDefeated[i]) then
-- Determine whether or not this player has any remaining opponents.
for j = 0, bj_MAX_PLAYERS - 1 do
-- If anyone has an opponent, noone can be victorious yet.
if MeleePlayerIsOpponent(i, j) then
DestroyForce(players)
return CreateForce()
end
end
-- Keep track of each opponentless player so that we can give
-- them a victory later.
ForceAddPlayer(players, Player(i))
gameOver = true
end
end
-- Set the game over global flag
bj_meleeGameOver = gameOver
return players
end
function mtb.get_ally_kscallback()
local uu = GetEnumUnit()
local id = GetUnitTypeId(uu)
if (not hall.pointer[id]) or (not UnitAlive(uu)) then
GroupRemoveUnit(mtb.ENUM_GROUP, uu)
end
end
function mtb.get_ally_ksc(player)
local count = 0
-- Count the number of buildings controlled by all not-yet-defeated co-allies.
for i = 0, bj_MAX_PLAYERS - 1 do
local other = Player(i)
if (PlayersAreCoAllied(player, other)) then
GroupEnumUnitsOfPlayer(mtb.ENUM_GROUP, other, nil)
ForGroup(mtb.ENUM_GROUP, mtb.get_ally_kscallback)
count = count + BlzGroupGetSize(mtb.ENUM_GROUP)
end
end
return count
end
function mtb.is_crippled(player)
return (mtb.get_ally_sc(player) > 0) and (mtb.get_ally_ksc(player) <= 0)
end
function mtb.on_check_cripples(player, i)
local is_crap = mtb.is_crippled(player)
if bj_playerIsCrippled[i] == is_crap then return;
end
bj_playerIsCrippled[i] = is_crap
if is_crap then
-- Player became crippled; start their cripple timer.
TimerStart(bj_crippledTimer[i], bj_MELEE_CRIPPLE_TIMEOUT, false, MeleeCrippledPlayerTimeout)
if (GetLocalPlayer() == player) then
-- Show the timer window.
TimerDialogDisplay(bj_crippledTimerWindows[i], true)
-- Display a warning message.
DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, MeleeGetCrippledWarningMessage(player))
end
else
-- Player became uncrippled; stop their cripple timer.
PauseTimer(bj_crippledTimer[i])
if (GetLocalPlayer() == player) then
-- Hide the timer window for this player.
TimerDialogDisplay(bj_crippledTimerWindows[i], false)
-- Display a confirmation message if the player's team is still alive.
if (mtb.get_ally_sc(player) > 0) then
if (bj_playerIsExposed[i]) then
DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNREVEALED"))
else
DisplayTimedTextToPlayer(player, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNCRIPPLED"))
end
end
end
-- If the player granted shared vision, deny that vision now.
MeleeExposePlayer(player, false)
end
end
function mtb.check_cripples()
if bj_finishSoonAllExposed then return;
end
-- Check each player to see if he or she has been crippled or uncrippled.
for i = 0, bj_MAX_PLAYERS - 1 do
local player = Player(i)
mtb.on_check_cripples(player, i)
end
end
function mtb.check_losers_victors()
local d_players = CreateForce()
local v_players
-- If the game is already over, do nothing
if (bj_meleeGameOver) then
DestroyForce(d_players)
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
DestroyForce(d_players)
return
end
-- Check each player to see if he or she has been defeated yet.
for i = 0, bj_MAX_PLAYERS - 1 do
local player = Player(i)
if (not bj_meleeDefeated[i] and not bj_meleeVictoried[i]) then
-- DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "Player" .. tostring(i) .. " has " .. tostring(mtb.get_ally_sc(player)) .. " ally buildings.")
if (MeleeGetAllyStructureCount(player) <= 0) then
-- Keep track of each defeated player so that we can give
-- them a defeat later.
ForceAddPlayer(d_players, Player(i))
-- Set their defeated flag now so MeleeCheckForVictors
-- can detect victors.
bj_meleeDefeated[i] = true
end
end
end
-- Now that the defeated flags are set, check if there are any victors
v_players = mtb.check_victors()
-- Defeat all defeated players
ForForce(d_players, MeleeDoDefeatEnum)
-- Give victory to all victorious players
ForForce(v_players, MeleeDoVictoryEnum)
DestroyForce(d_players)
DestroyForce(v_players)
-- If the game is over we should remove all observers
if (bj_meleeGameOver) then
MeleeRemoveObservers()
end
end
function mtb.check_lose_unit(whichunit)
local owner = GetOwningPlayer(whichunit)
if GetPlayerStructureCount(owner, true) <= 0 then
mtb.check_losers_victors()
end
mtb.check_cripples()
end
function mtb.check_gain_unit(whichunit)
local owner = GetOwningPlayer(whichunit)
if (bj_playerIsCrippled[GetPlayerId(owner)]) then
mtb.check_cripples()
end
end
-- Trigger callback functions.
function mtb.on_building_cancel()
mtb.check_lose_unit(GetTriggerUnit())
end
function mtb.on_building_death()
local unit = GetTriggerUnit()
if not IsUnitType(unit, UNIT_TYPE_STRUCTURE) then return;
end
mtb.check_lose_unit(unit)
end
function mtb.on_building_start()
mtb.check_gain_unit(GetTriggerUnit())
end
function mtb.on_player_defeat()
local player = GetTriggerPlayer()
CachePlayerHeroData(player)
if (MeleeGetAllyCount(player) > 0) then
-- If at least one ally is still alive and kicking, share units with
-- them and proceed with death.
ShareEverythingWithTeam(player)
else
-- If no living allies remain, swap all units and buildings over to
-- neutral_passive and proceed with death.
MakeUnitsPassiveForTeam(player)
end
if (not bj_meleeDefeated[GetPlayerId(player)]) then
MeleeDoDefeat(player)
end
mtb.check_losers_victors()
end
function mtb.on_player_leave()
local player = GetTriggerPlayer()
-- Just show game over for observers when they leave
if (IsPlayerObserver(player)) then
RemovePlayerPreserveUnitsBJ(player, PLAYER_GAME_RESULT_NEUTRAL, false)
return
end
-- Inspect the player for leaving while in queue.
tb.dialog.remove_player(player)
tb.dialog.check_unpause_all()
CachePlayerHeroData(player)
-- This is the same as defeat except the player generates the message
-- "player left the game" as opposed to "player was defeated".
if (MeleeGetAllyCount(player) > 0) then
-- If at least one ally is still alive and kicking, share units with
-- them and proceed with death.
ShareEverythingWithTeam(player)
else
-- If no living allies remain, swap all units and buildings over to
-- neutral_passive and proceed with death.
MakeUnitsPassiveForTeam(player)
end
MeleeDoLeave(player)
if not tb.dialog.pause_state then
mtb.check_losers_victors()
else
doAfter(0.01, mtb.check_losers_victors)
end
end
function mtb.check_generic()
mtb.check_losers_victors()
mtb.check_cripples()
end
function mtb.on_player_alliance_change()
if tb.dialog.pause_state then
doAfter(0.01, mtb.check_generic)
return
end
mtb.check_generic()
end
function tb.init_victory_defeat()
mtb.init_tournament()
local trigs = {}
for i = 1, 6 do
trigs[i] = CreateTrigger()
end
do
TriggerAddCondition(trigs[1], Condition(mtb.on_building_cancel))
TriggerAddCondition(trigs[2], Condition(mtb.on_building_death))
TriggerAddCondition(trigs[3], Condition(mtb.on_building_start))
TriggerAddCondition(trigs[4], Condition(mtb.on_player_defeat))
TriggerAddCondition(trigs[5], Condition(mtb.on_player_leave))
TriggerAddCondition(trigs[6], Condition(mtb.on_player_alliance_change))
end
UnitDex.register("ENTER_EVENT", function()
if IsUnitType(UnitDex.eventUnit, UNIT_TYPE_STRUCTURE) then
mtb.check_gain_unit(UnitDex.eventUnit)
end
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
if not IsUnitType(unit, UNIT_TYPE_STRUCTURE) then return;
end
doAfter(0.01, mtb.check_lose_unit, unit)
end)
for i = 0, bj_MAX_PLAYERS - 1 do
local player = Player(i)
-- Make sure this player slot is playing.
if (GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING) then
bj_meleeDefeated[i] = false
bj_meleeVictoried[i] = false
-- Create a timer and timer window in case the player is crippled.
bj_playerIsCrippled[i] = false
bj_playerIsExposed[i] = false
bj_crippledTimer[i] = CreateTimer()
bj_crippledTimerWindows[i] = CreateTimerDialog(bj_crippledTimer[i])
TimerDialogSetTitle(bj_crippledTimerWindows[i], MeleeGetCrippledTimerMessage(player))
-- Set a trigger to fire whenever a building is cancelled for this player.
TriggerRegisterPlayerUnitEvent(trigs[1], player, EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, nil)
-- Set a trigger to fire whenever a unit dies for this player.
TriggerRegisterPlayerUnitEvent(trigs[2], player, EVENT_PLAYER_UNIT_DEATH, nil)
-- Set a trigger to fire whenever a unit begins construction for this player
TriggerRegisterPlayerUnitEvent(trigs[3], player, EVENT_PLAYER_UNIT_CONSTRUCT_START, nil)
-- Set a trigger to fire whenever this player defeats-out
TriggerRegisterPlayerEvent(trigs[4], player, EVENT_PLAYER_DEFEAT)
-- Set a trigger to fire whenever this player leaves
TriggerRegisterPlayerEvent(trigs[5], player, EVENT_PLAYER_LEAVE)
-- Set a trigger to fire whenever this player changes his/her alliances.
TriggerRegisterPlayerAllianceChange(trigs[6], player, ALLIANCE_PASSIVE)
TriggerRegisterPlayerStateEvent(trigs[6], player, PLAYER_STATE_ALLIED_VICTORY, EQUAL, 1)
else
bj_meleeDefeated[i] = true
bj_meleeVictoried[i] = false
-- Handle leave events for observers
if (IsPlayerObserver(player)) then
-- Set a trigger to fire whenever this player leaves
TriggerRegisterPlayerEvent(trigs[6], player, EVENT_PLAYER_LEAVE)
end
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, mtb.check_generic)
end
do
local tb = getmetatable(CustomMelee)
local hall = tb._hall
local hero = tb._hero
local p_faction = tb._faction
local funcs = {}
-- Special Dialog functions
-- 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)
SetPlayerTechMaxAllowed(p, FourCC('HERO'), bj_MELEE_HERO_LIMIT)
-- ReducePlayerTechMaxAllowed(p, <heroType>, bj_MELEE_HERO_TYPE_LIMIT)
for index = 1, #hero do
SetPlayerTechMaxAllowed(p, hero[index], bj_MELEE_HERO_TYPE_LIMIT)
end
end
end
function funcs.grant_hero_scroll(whichunit)
if not IsUnitType(whichunit, UNIT_TYPE_HERO) then return;
end
local owner = GetPlayerId(GetOwningPlayer(whichunit))
-- If we haven't twinked N heroes for this player yet, twink away.
if (bj_meleeTwinkedHeroes[owner] < bj_MELEE_MAX_TWINKED_HEROES) then
UnitAddItemById(whichunit, FourCC('stwp'))
bj_meleeTwinkedHeroes[owner] = bj_meleeTwinkedHeroes[owner] + 1
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()
funcs.grant_hero_scroll(GetTrainedUnit())
end))
t = CreateTrigger()
TriggerRegisterPlayerUnitEvent(t, Player(PLAYER_NEUTRAL_PASSIVE), EVENT_PLAYER_UNIT_SELL, nil)
TriggerAddCondition(t, Filter(function()
funcs.grant_hero_scroll(GetSoldUnit())
end))
bj_meleeGrantHeroItems = true
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.select_def_faction(whichplayer)
local race = GetPlayerRace(whichplayer)
if (#tb._race[race] == 1) or (GetPlayerController(whichplayer) == MAP_CONTROL_COMPUTER) then
tb.dialog.create_race(whichplayer, race, 1)
return
end
tb.dialog.add_player(whichplayer)
end
function funcs.starting_units()
for player_id = 0, bj_MAX_PLAYERS-1 do
local p = Player(player_id)
if (GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING) then
funcs.select_def_faction(p)
end
end
tb.dialog.show()
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, p_faction[player_id].ai_script)
if indexRace == RACE_UNDEAD then
RecycleGuardPosition(bj_ghoul[player_id])
end
ShareEverythingWithTeamAI(indexPlayer)
end
end
end
end
function funcs.victory_defeat()
tb.init_victory_defeat()
end
function tb.initialization()
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 tb = protected_table()
UnitRecycler = setmetatable({}, tb)
tb.PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
tb.ABILITY_ID = FourCC("uADA") -- This ability must be based on phoenixmorph
tb.ORDER_ID = "phoenixmorph"
tb._ids = {}
tb._pointer = {}
local function reset(unit)
UnitAddAbility(unit, tb.ABILITY_ID)
UnitMakeAbilityPermanent(unit, true, tb.ABILITY_ID)
end
local function animdeath_remove_aloc()
local timer = GetExpiredTimer()
local unit = GetTimerData(timer)
local t = tb._pointer[unit]
if not t.aloc_flag and (not t.recycled) then
UnitRemoveAbility(whichunit, FourCC("Aloc"))
end
PauseTimer(timer)
DestroyTimer(timer)
end
function tb:simulate_death(whichunit)
if not tb._pointer[whichunit] then return;
end
local t = tb._pointer[whichunit]
local time = BlzGetUnitRealField(whichunit, UNIT_RF_DEATH_TIME)
if not t.aloc_flag then
UnitAddAbility(whichunit, FourCC("Aloc"))
end
SetUnitAnimation(whichunit, "death")
QueueUnitAnimation(whichunit, "decay flesh")
QueueUnitAnimation(whichunit, "decay bone")
local timer = CreateTimer()
SetTimerData(timer, whichunit)
TimerStart(timer, time, false, animdeath_remove_aloc)
end
function tb:recycle(whichunit)
if not tb._pointer[whichunit] then return;
end
local t = tb._pointer[whichunit]
local unitid = t.unitid
t.recycled = true
tb._ids[unitid].restore(t)
SetUnitOwner(whichunit, tb.PLAYER, true)
ShowUnit(whichunit, false)
PauseUnit(whichunit, true)
if not t.aloc_flag then
UnitAddAbility(whichunit, FourCC('Aloc'))
end
SetUnitX(whichunit, WorldRect.rectMinX)
SetUnitY(whichunit, WorldRect.rectMinY)
SetWidgetLife(whichunit, BlzGetUnitMaxHP(whichunit)*10000)
end
function tb:request(player, unitid, x, y, face)
x, y, face = x or 0, y or 0, face or 0
if not tb._ids[unitid] then
tb._ids[unitid] = AllocTableEx(0)
end
local t = tb._ids[unitid].request()
t.recycled = false
if not t.unit then
t.unit = CreateUnit(player, unitid, x, y, face)
t.aloc_flag = (GetUnitAbilityLevel(t.unit, FourCC("Aloc")) ~= 0)
t.unitid = unitid
tb._pointer[t.unit] = t
UnitAddAbility(t.unit, tb.ABILITY_ID)
UnitMakeAbilityPermanent(t.unit, true, tb.ABILITY_ID)
else
SetUnitAnimation(t.unit, "stand")
SetUnitOwner(t.unit, player, true)
SetUnitFacing(t.unit, face)
ShowUnit(t.unit, true)
PauseUnit(t.unit, false)
UnitRemoveAbility(t.unit, FourCC('Aloc'))
if t.aloc_flag then
UnitAddAbility(t.unit, FourCC('Aloc'))
end
SetUnitPosition(t.unit, x, y)
end
return t.unit
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function()
local unit = GetTriggerUnit()
local order = OrderId2String(GetIssuedOrderId())
if order ~= tb.ORDER_ID then return;
elseif not tb._pointer[unit] then return;
end
UnitRemoveAbility(unit, tb.ABILITY_ID)
SetWidgetLife(unit, BlzGetUnitMaxHP(unit)*10000)
doAfter(0.00, reset, unit)
end)
-- Just in case a recycled unit is accidentally removed.
UnitDex.register("LEAVE_EVENT", function(unit)
if not tb._pointer[unit] then return;
end
local t = tb._pointer[unit]
local unitid = t.unitid
tb._pointer[unit] = nil
t.unit = nil
t.unitid = nil
t.aloc_flag = nil
if not t.recycled then
tb._ids[unitid].restore(t)
t.recycled = true
end
end)
end
--[[
DummyUtils
- Inspired by Nestharus' dummy allocation and deallocation algorithm.
an O(1) complexity algorithm.
- Also inspired by Flux's Dummy Recycler
]]
do
local m_utils = protected_table()
local list = {upper=LinkedList:create(), lower=LinkedList:create(), pointer={}, angle={}}
local dummy_flag = {}
local total_count = 0
--local SetUnitFacing = BlzSetUnitFacingEx or _G['SetUnitFacing']
DummyUtils = setmetatable({}, m_utils)
m_utils._operational = false
m_utils.DUMMY_TYPE = FourCC('dumi')
-- PRELOAD_AMOUNT/LIST_COUNT will always round up to an integer.
m_utils.PRELOAD_AMOUNT = 64
m_utils.LIST_COUNT = 8
m_utils.FACING_OFFSET = 360/m_utils.LIST_COUNT
m_utils.PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
for id = 1, m_utils.LIST_COUNT do
list[id] = LinkedList()--:create()
list.angle[id] = id * m_utils.FACING_OFFSET
list.pointer[id] = select(2, list.upper:insert(id))
list.pointer[list[id]] = list.upper
end
function m_utils._on_request(dummy, player, x, y, z, facing)
-- 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)
end
function m_utils._on_recycle(dummy, facing, tx, ty)
SetUnitX(dummy, tx); SetUnitY(dummy, ty);
SetUnitFacing(dummy, facing); SetUnitOwner(dummy, m_utils.PLAYER, true);
ShowUnit(dummy, false); PauseUnit(dummy, true);
BlzUnitDisableAbility(dummy, FourCC("Aatk"), true, false)
end
do
local t = {}
Initializer("SYSTEM", function()
t.rectX = WorldRect.rectMinX
t.rectY = WorldRect.rectMinY
t.temp = math.max(m_utils.PRELOAD_AMOUNT, 1)/m_utils.LIST_COUNT
t.temp = math.ceil(t.temp)
for i = 1, m_utils.LIST_COUNT do
local dummy
for j = 1, t.temp do
dummy = CreateUnit(m_utils.PLAYER, m_utils.DUMMY_TYPE, t.rectX, t.rectY, list.angle[i])
if not dummy then
print_after(0.00, "DummyUtils.initialization >> Dummy could not be created.")
break
end
PauseUnit(dummy, true); ShowUnit(dummy, false);
BlzUnitDisableAbility(dummy, FourCC("Aatk"), true, false)
list[i]:insert(dummy)
dummy_flag[dummy] = "free"
total_count = total_count + 1
end
if not dummy then break;
end
end
if total_count <= 0 then return;
end
m_utils._operational = true
end)
function m_utils._internal_req(player, x, y, z, facing)
if total_count <= 0 then return nil;
end
local j = math.floor(facing/m_utils.FACING_OFFSET + 0.5)
if j <= 0 then
j = m_utils.LIST_COUNT
end
local dummy, pointer = list[j]:first()
list[j]:remove(pointer)
total_count = total_count - 1
m_utils._on_request(dummy, player, x, y, z, facing)
dummy_flag[dummy] = "used"
if list.pointer[list[j]] == list.upper then
-- Located in the upper list.
list.upper:remove(list.pointer[j])
list.pointer[j] = select(2, 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]:remove(nextPointer)
list[j]:insert(nextDummy)
list.upper:remove(list.pointer[k])
list.pointer[k] = select(2, list.lower:insert(k))
list.pointer[list[k] ] = list.lower
end
if #list.upper <= 0 then
local swap = list.upper
list.upper, list.lower = list.lower, swap
end
return dummy
end
function m_utils.request(player, x, y, z, facing)
player = player or m_utils.PLAYER
x, y, z, facing = x or 0, y or 0, z or 0, facing or 0
if not m_utils._operational then
print("DummyUtils.request >> System is not operational.")
return nil
end
local dummy = m_utils._internal_req(player, x, y, z, facing)
if dummy then
bj_lastCreatedUnit, dummy = dummy, nil
return bj_lastCreatedUnit
end
dummy = CreateUnit(player, m_utils.DUMMY_TYPE, x, y, facing)
SetUnitFlyHeight(dummy, z, 0.0)
dummy_flag[dummy] = "used"
bj_lastCreatedUnit = dummy
return bj_lastCreatedUnit
end
function m_utils.recycle(dummy)
if not m_utils._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 <= 0 then
local swap = list.upper
list.upper, list.lower = list.lower, swap
end
local j, pointer = list.lower:first()
m_utils._on_recycle(dummy, list.angle[j], t.rectX, t.rectY)
list[j]:insert(dummy)
dummy_flag[dummy] = "free"
total_count = total_count + 1
list.lower:remove(pointer)
list.pointer[j] = select(2, list.upper:insert(j))
list.pointer[list[j] ] = list.upper
return true
end
end
end
do
local native = {
setHeight = BlzSetSpecialEffectHeight,
setAlpha = BlzSetSpecialEffectAlpha,
dest = DestroyEffect
}
local broken = (BlzStartUnitAbilityCooldown and true) or false
--local broken = VersionCheck.patch ~= '1.31'
local attr = {height = {}, alpha = {}, vis = {}}
local dest_flag = {}
local function const_factory(func)
return function(...)
local fx = func(...)
attr.height[fx] = 0.
attr.alpha[fx] = 255.
attr.vis[fx] = 0
return fx
end
end
function DestroyEffect(fx)
if dest_flag[fx] then return;
end
native.dest(fx)
attr.height[fx] = nil
attr.alpha[fx] = nil
attr.vis[fx] = nil
dest_flag[fx] = nil
end
local _dest = DestroyEffect
local function on_destroy_effect()
local t = GetExpiredTimer()
local fx = GetTimerData(t)
dest_flag[fx] = nil
_dest(fx)
DestroyTimer(t)
end
function DestroyEffect(fx, dur)
if (not dur) or (dur <= 0) then
_dest(fx)
return
end
if dest_flag[fx] then return;
end
dest_flag[fx] = true
local t = CreateTimer()
SetTimerData(t, fx)
TimerStart(t, dur, false, on_destroy_effect)
end
AddSpecialEffect = const_factory(AddSpecialEffect)
AddSpecialEffectLoc = const_factory(AddSpecialEffectLoc)
AddSpecialEffectTarget = const_factory(AddSpecialEffectTarget)
AddSpellEffect = const_factory(AddSpellEffect)
AddSpellEffectById = const_factory(AddSpellEffectById)
AddSpellEffectLoc = const_factory(AddSpellEffectLoc)
AddSpellEffectByIdLoc = const_factory(AddSpellEffectByIdLoc)
AddSpellEffectTarget = const_factory(AddSpellEffectTarget)
AddSpellEffectTargetById = const_factory(AddSpellEffectTargetById)
function BlzSetSpecialEffectHeight(fx, height)
attr.height[fx] = height
if broken then
height = height + GetPointZ(BlzGetLocalSpecialEffectX(fx), BlzGetLocalSpecialEffectY(fx))
end
native.setHeight(fx, height)
end
function BlzSetSpecialEffectAlpha(fx, alpha)
attr.alpha[fx] = alpha
if attr.vis[fx] >= 0 then
native.setAlpha(fx, alpha)
else
native.setAlpha(fx, 0)
end
end
if not BlzGetSpecialEffectHeight then
-- A function masquerading as a native
function BlzGetSpecialEffectHeight(fx)
return attr.height[fx] or 0
end
end
if not BlzGetSpecialEffectAlpha then
-- A function masquerading as a native
function BlzGetSpecialEffectAlpha(fx)
return attr.alpha[fx] or 255
end
end
function ShowEffect(fx, vis)
if vis then
attr.vis[fx] = attr.vis[fx] + 1
else
attr.vis[fx] = attr.vis[fx] - 1
end
BlzSetSpecialEffectAlpha(fx, attr.alpha[fx])
end
function IsEffectVisible(fx)
return attr.vis[fx] >= 0
end
SetEffectX = BlzSetSpecialEffectX
SetEffectY = BlzSetSpecialEffectY
SetEffectZ = BlzSetSpecialEffectZ
SetEffectHeight = BlzSetSpecialEffectHeight
SetEffectAlpha = BlzSetSpecialEffectAlpha
SetEffectColor = BlzSetSpecialEffectColor
SetEffectPColor = BlzSetSpecialEffectColorByPlayer
GetEffectX = BlzGetLocalSpecialEffectX
GetEffectY = BlzGetLocalSpecialEffectY
GetEffectZ = BlzGetLocalSpecialEffectZ
GetEffectHeight = BlzGetSpecialEffectHeight
GetEffectAlpha = BlzGetSpecialEffectAlpha
end
do
local tb = protected_table({
TICKS = 32,
})
tb.entry = {}
tb.loop = TimerIterator:create(tb.TICKS, function(self)
SetEffectX(self.effect, GetUnitX(self.attach))
SetEffectY(self.effect, GetUnitY(self.attach))
SetEffectHeight(self.effect, GetUnitFlyHeight(self.attach) + self.height)
if GetUnitTypeId(self.attach) == 0 then
DetachEffect(self.effect)
DestroyEffect(self.effect)
end
end)
function AttachEffect(fx, whichunit, h)
if not tb.entry[fx] then
tb.entry[fx] = {}
tb.entry[fx].effect = fx
tb.loop:insert(tb.entry[fx])
end
tb.entry[fx].attach = whichunit
tb.entry[fx].height = h or 0
SetEffectX(fx, GetUnitX(whichunit))
SetEffectY(fx, GetUnitY(whichunit))
SetEffectHeight(fx, GetUnitFlyHeight(whichunit) + (h or 0))
end
function DetachEffect(fx)
if not tb.entry[fx] then return;
end
tb.loop:remove(tb.entry[fx])
tb.entry[fx] = nil
end
end
do
-- Credits to JesusHipster for the sick hp-bar model
local tb = protected_table({
MODEL = "war3mapImported\\HPbar.mdx"
})
ProgressBar = setmetatable({}, tb)
end
do
local tb = AllocTable(200)
local t = {[1]={}, [2]={}}
local data = {}
tb._NORMAL = Vector2D.ORIGIN
tb.INTERVAL = 64
tb.GRADIENT = 1/tb.INTERVAL
Missile = setmetatable({}, tb)
tb._moving = TimerIterator:create(tb.INTERVAL, function(self)
if rawget(self, 'on_update') then
t[1].vect, t[1].dz = self.on_update(self)
else
t[1].vect, t[1].dz = tb._NORMAL, 0
end
if not self.effect then return;
elseif not self.running then return;
end
local tx, ty, h = self:get_x(), self:get_y(), self:get_height()
self:set_x(tx + t[1].vect.x)
self:set_y(ty + t[1].vect.y)
self:set_height(h + t[1].dz)
t[1], t[2] = t[2], t[1]
end)
-- Getters and setters
function tb:get_x()
return GetEffectX(self.effect)
end
function tb:get_y()
return GetEffectY(self.effect)
end
function tb:get_height()
return GetEffectHeight(self.effect)
end
function tb:set_x(val)
SetEffectX(self.effect, val)
end
function tb:set_y(val)
SetEffectY(self.effect, val)
end
function tb:set_height(val)
SetEffectHeight(self.effect, val)
end
-- Convenience functions
function tb:show(flag)
ShowEffect(self.effect, flag)
end
function tb:visible()
return IsEffectVisible(self.effect, flag)
end
function tb:set_data(value)
data[self] = value
end
function tb:get_data()
return data[self]
end
-- Constructor and destructor
function tb:__constructor(path, x, y)
self.effect = AddSpecialEffect(path, x, y)
self.running = false
end
function tb:__destructor()
self:stop()
if rawget(self, 'on_destroy') then
local func = self.on_destroy
rawset(self, 'on_destroy', nil)
func(self)
end
rawset(self, 'on_stop', nil)
rawset(self, 'on_launch', nil)
rawset(self, 'on_update', nil)
DestroyEffect(self.effect)
data[self] = nil
self.effect = nil
self.running = nil
end
-- Generic launch and stop methods
function tb:is_moving()
return self.running
end
function tb:launch(cx, cy, h)
if self.running then return;
end
self.running = true
tb._moving:insert(self)
if rawget(self, 'on_launch') then
self.on_launch(self, cx, cy, h)
end
if cx then
self:set_x(cx)
end
if cy then
self:set_y(cy)
end
if h then
self:set_height(h)
end
end
function tb:stop()
if not self.running then return;
end
self.running = false
tb._moving:remove(self)
if rawget(self, 'on_stop') then
self.on_stop(self)
end
end
-- Configurable callback functions.
function tb:config_launch(func)
if not is_function(func) then return;
end
rawset(self, 'on_launch', func)
end
function tb:config_move(func)
if not is_function(func) then return;
end
rawset(self, 'on_update', func)
end
function tb:config_destroy(func)
if not is_function(func) then return;
end
rawset(self, 'on_destroy', func)
end
function tb:config_stop(func)
if not is_function(func) then return;
end
rawset(self, 'on_stop', func)
end
end
do
local tb = protected_table()
local mtb = {}
local co = {pointer={}}
MissileInterface = setmetatable({}, tb)
local function request_table()
if co[#co] then
local t = co[#co]
co[#co] = nil
co.pointer[t] = nil
return t
end
return {}
end
local function restore_table(t)
if co.pointer[t] then return;
end
co[#co + 1] = t
co.pointer[t] = #co
end
function tb.is_missile_type(o)
return mtb[o]
end
-- Helper functions
function tb:config_launch(func)
self.missile:config_launch(func)
end
function tb:config_stop(func)
self.missile:config_stop(func)
end
function tb:config_move(func)
self.missile:config_move(func)
end
function tb:config_destroy(func)
self.missile:config_destroy(func)
end
function tb:show(flag)
self.missile:show(flag)
end
function tb:launch(...)
self.missile:launch(...)
end
function tb:stop()
self.missile:stop()
end
function tb:visible()
return self.missile:visible()
end
function tb:is_moving()
return self.missile:is_moving()
end
function tb.apply_turn_rate(theta, facing, turn_rate)
if turn_rate <= 0 then return theta;
end
local mag = math.abs(facing - theta)
if (mag < turn_rate) then return theta;
elseif (mag >= 2*math.pi - turn_rate) then return theta;
end
if mag > math.pi then
if facing < theta then
theta = facing - turn_rate
else
theta = facing + turn_rate
end
else
if facing < theta then
theta = facing + turn_rate
else
theta = facing - turn_rate
end
end
if theta < -math.pi then
theta = theta + 2*math.pi
elseif theta > math.pi then
theta = theta - 2*math.pi
end
return theta
end
function tb:__constructor(path, cx, cy)
self.missile = Missile(path, cx, cy)
self.facing = 0.00
self.col_size = 0.00
end
function tb:__destructor(path, cx, cy)
self.col_size = nil
self.facing = nil
self.missile = nil
end
function tb:__call(o, const, dest)
o = o or protected_table()
mtb[o] = true
if const then
o.__constructor = const
end
if dest then
o.__destructor = dest
end
o.__call = function(t, path, cx, cy, ...)
local oo = request_table()
tb.__constructor(oo, path, cx, cy)
if o.__constructor then
o.__constructor(oo, ...)
end
setmetatable(oo, o)
return oo
end
o.destroy = function(self)
if co.pointer[self] then return;
end
local mt = o.__metatable
o.__metatable = nil
setmetatable(self, nil)
o.__metatable = mt
if o.__destructor then
o.__destructor(self)
end
self.missile:destroy()
tb.__destructor(self)
restore_table(self)
end
o.switch = function(self, other, ...)
if not mtb[other] then
other = getmetatable(other)
end
if not mtb[other] then return;
elseif not self.missile then return;
end
local mt = o.__metatable
o.__metatable = nil
setmetatable(self, nil)
o.__metatable = mt
if o.__destructor then
o.__destructor(self)
end
if other.__constructor then
other.__constructor(self, ...)
end
setmetatable(self, other)
end
o.config_launch = tb.config_launch
o.config_stop = tb.config_stop
o.config_move = tb.config_move
o.config_destroy = tb.config_destroy
o.show = tb.show
o.visible = tb.visible
o.launch = tb.launch
o.stop = tb.stop
o.is_moving = tb.is_moving
o.apply_turn_rate = tb.apply_turn_rate
return o
end
end
do
local tb = MissileInterface()
local grad = Missile.GRADIENT
local moving = {}
local otb = {}
local mtb = {stopped={}}
HomingMissile = setmetatable({}, tb)
function tb:__constructor(target, speed, turn)
self.target = target or 0
self.speed = speed or 0.
self.turn_rate = turn or -1.
mtb[self.missile] = self
end
function tb:__destructor()
self.target = nil
self.speed = nil
self.turn_rate = nil
self.tx = nil
self.ty = nil
moving[self] = nil
mtb[self.missile] = nil
end
-- Attribute getters and setters
function tb:get_speed(cmd)
if cmd == 'raw' then
return self.speed
end
return self.speed/grad
end
function tb:set_speed(amt, cmd)
amt = amt or 0
if cmd == 'raw' then
self.speed = amt
else
self.speed = amt*grad
end
end
function tb:get_turn_rate(cmd)
if cmd == 'raw' then
return self.turn_rate
end
return self.turn_rate/grad
end
function tb:set_turn_rate(amt, cmd)
amt = amt or 0
if cmd == 'raw' then
self.turn_rate = amt
else
self.turn_rate = amt*grad
end
end
function tb:set_target(targ)
self.target = targ
end
function tb:get_target()
return self.target
end
otb.launch = tb.launch
otb.config_move = tb.config_move
-- Convenience methods
function tb:launch(...)
if (self.target == 0) or (not self.target) then return;
elseif self:is_moving() then return;
end
if mtb.stopped[self] then
local t = {...}
doAfter(0.00, tb.launch, self, table.unpack(t))
return
end
otb.config_move(self, tb.while_moving)
otb.launch(self, ...)
end
function tb:config_move(func)
if not is_function(func) then return;
end
moving[self] = func
end
local temp = {}
function tb.while_moving(self)
self = mtb[self]
-- Consider a 0-collision size missile first
-- With an instant turn rate (<= 0)
temp.tx, temp.ty = GetWidgetX(self.target), GetWidgetY(self.target)
temp.cx, temp.cy = self.missile:get_x(), self.missile:get_y()
temp.theta = math.atan(temp.ty - temp.cy, temp.tx - temp.cx)
temp.theta = tb.apply_turn_rate(temp.theta, self.facing, self.turn_rate)
temp.cos = math.cos(temp.theta)
temp.sin = math.sin(temp.theta)
temp.cx = temp.cx + self.speed*temp.cos
temp.cy = temp.cy + self.speed*temp.sin
temp.dx = temp.cx + self.col_size*temp.cos
temp.dy = temp.cy + self.col_size*temp.sin
temp.disp = self.speed*self.speed
temp.dist = (temp.tx-temp.dx)*(temp.tx-temp.dx) + (temp.ty-temp.dy)*(temp.ty-temp.dy)
self.facing = temp.theta
temp.h = 0
if moving[self] then
temp.h = moving[self](self, temp.theta) or 0
end
if not self:is_moving() then return Vector2D(0, 0), temp.h;
end
if temp.disp < temp.dist then
return Vector2D(self.speed*temp.cos, self.speed*temp.sin), temp.h
else
mtb.stopped[self] = true
self:stop()
mtb.stopped[self] = false
return Vector2D(temp.tx - self.missile:get_x(), temp.ty - self.missile:get_y()), temp.h
end
end
end
do
local tb = MissileInterface()
local grad = Missile.GRADIENT
local moving = {}
local otb = {}
local mtb = {stopped={}}
PointMissile = setmetatable({}, tb)
function tb:__constructor(speed, turn)
self.speed = speed or 0.
self.turn_rate = turn or -1.
mtb[self.missile] = self
end
function tb:__destructor()
self.speed = nil
self.turn_rate = nil
self.tx = nil
self.ty = nil
moving[self] = nil
mtb[self.missile] = nil
end
-- Attribute getters and setters
function tb:get_speed(cmd)
if cmd == 'raw' then
return self.speed
end
return self.speed/grad
end
function tb:set_speed(amt, cmd)
amt = amt or 0
if cmd == 'raw' then
self.speed = amt
else
self.speed = amt*grad
end
end
function tb:get_turn_rate(cmd)
if cmd == 'raw' then
return self.turn_rate
end
return self.turn_rate/grad
end
function tb:set_turn_rate(amt, cmd)
amt = amt or 0
if cmd == 'raw' then
self.turn_rate = amt
else
self.turn_rate = amt*grad
end
end
otb.launch = tb.launch
otb.config_move = tb.config_move
-- Convenience methods
function tb:mark(tx, ty)
rawset(self, 'tx', tx or 0)
rawset(self, 'ty', ty or 0)
end
function tb:launch(...)
if not rawget(self, 'tx') then return;
elseif self:is_moving() then return;
end
if mtb.stopped[self] then
local t = {...}
doAfter(0.00, tb.launch, self, table.unpack(t))
return
end
otb.config_move(self, tb.while_moving)
otb.launch(self, ...)
end
function tb:config_move(func)
if not is_function(func) then return;
end
moving[self] = func
end
local temp = {}
function tb.while_moving(self)
self = mtb[self]
-- Consider a 0-collision size missile first
-- With an instant turn rate (<= 0)
temp.tx, temp.ty = self.tx, self.ty
temp.cx, temp.cy = self.missile:get_x(), self.missile:get_y()
temp.theta = math.atan(temp.ty - temp.cy, temp.tx - temp.cx)
temp.theta = tb.apply_turn_rate(temp.theta, self.facing, self.turn_rate)
temp.cos = math.cos(temp.theta)
temp.sin = math.sin(temp.theta)
temp.cx = temp.cx + self.speed*temp.cos
temp.cy = temp.cy + self.speed*temp.sin
temp.dx = temp.cx + self.col_size*temp.cos
temp.dy = temp.cy + self.col_size*temp.sin
temp.disp = self.speed*self.speed
temp.dist = (temp.tx-temp.dx)*(temp.tx-temp.dx) + (temp.ty-temp.dy)*(temp.ty-temp.dy)
self.facing = temp.theta
temp.h = 0
if moving[self] then
temp.h = moving[self](self, temp.theta) or 0
end
if not self:is_moving() then return Vector2D(0, 0), temp.h;
end
if temp.disp < temp.dist then
return Vector2D(self.speed*temp.cos, self.speed*temp.sin), temp.h
else
mtb.stopped[self] = true
self:stop()
mtb.stopped[self] = false
return Vector2D(temp.tx - self.missile:get_x(), temp.ty - self.missile:get_y()), temp.h
end
end
end
do
local tb = protected_table()
_G['CustomBuff'] = {}
CustomBuff.Main = tb
CustomBuff.Debug = "ReplaceableTextures\\CommandButtons\\BTNHeroPaladin.blp"
tb.WIDTH = 0.22
tb.HEIGHT = 0.04
tb.ABS_X = 0
tb.ABS_X = tb.ABS_X - ((tb.ABS_X - 0.4)/0.4)*tb.WIDTH/2
tb.ABS_Y = 0.56
tb.WIDTH_REMAIN = tb.WIDTH
Initializer("SYSTEM", function()
local world = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
tb.WORLD = world
tb.panel = BlzCreateFrame("QuestButtonBaseTemplate", world, 0, 0)
BlzFrameSetSize(tb.panel, tb.WIDTH, tb.HEIGHT)
BlzFrameSetAbsPoint(tb.panel, FRAMEPOINT_CENTER, tb.ABS_X, tb.ABS_Y)
end)
function tb.visual_frame(whichframe)
local world = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
local dframe = BlzCreateFrame("QuestButtonBaseTemplate", world, 0, 0)
BlzFrameClearAllPoints(dframe)
BlzFrameSetAllPoints(dframe, whichframe)
BlzFrameSetTexture(dframe, CustomBuff.Debug, 0, true)
end
end
do
local tb = protected_table()
local main = CustomBuff.Main
CustomBuff.Status = setmetatable({}, tb)
tb.WIDTH = 0.032
tb.HEIGHT = main.HEIGHT
tb.SCALE = 1.25
tb.REL_X = 0.01
tb.REL_Y = 0
tb.WIDTH = math.min(tb.WIDTH, main.WIDTH_REMAIN/tb.SCALE - tb.REL_X)
main.WIDTH_REMAIN = main.WIDTH_REMAIN - (tb.WIDTH*tb.SCALE + tb.REL_X)
Initializer("SYSTEM", function()
tb.status = BlzCreateFrame("TeamLabelTextTemplate", main.panel, 0, 0)
BlzFrameSetSize(tb.status, tb.WIDTH, tb.HEIGHT)
BlzFrameClearAllPoints(tb.status)
BlzFrameSetAllPoints(copy, tb.status)
BlzFrameSetPoint(tb.status, FRAMEPOINT_LEFT, main.panel, FRAMEPOINT_LEFT, tb.REL_X, tb.REL_Y)
BlzFrameSetText(tb.status, "Buffs:")
BlzFrameSetScale(tb.status, tb.SCALE)
end)
end
do
local tb = protected_table()
local main = CustomBuff.Main
local status = CustomBuff.Status
CustomBuff.ListPanel = setmetatable({}, tb)
tb.WIDTH = 0.10
tb.HEIGHT = 0.025
tb.REL_X = 0.01
tb.REL_Y = 0
tb.HEIGHT = math.min(tb.HEIGHT, main.HEIGHT)
tb.WIDTH = math.min(tb.WIDTH, main.WIDTH_REMAIN - tb.REL_X)
main.WIDTH_REMAIN = main.WIDTH_REMAIN - (tb.WIDTH + tb.REL_X)
Initializer("SYSTEM", function()
tb.panel = BlzCreateFrame("QuestButtonDisabledBackdropTemplate", main.panel, 0, 0)
BlzFrameSetSize(tb.panel, tb.WIDTH, tb.HEIGHT)
BlzFrameClearAllPoints(tb.panel)
BlzFrameSetPoint(tb.panel, FRAMEPOINT_LEFT, status.status, FRAMEPOINT_RIGHT, tb.REL_X, tb.REL_Y)
end)
end
do
local tb = protected_table()
local list = CustomBuff.ListPanel
CustomBuff.ListIcons = setmetatable({}, tb)
tb.TOC_PATH = "war3mapImported\\BoxedText.toc"
tb._ICON_LIST = {}
tb.ICON_PATH = "UI\\Custom\\EmptyInventory.blp"
tb.ICON_HEIGHT = 0.015
tb.ICON_MAX_COUNT = 5
tb.ICON_REL_X = 0.002
tb.ICON_REL_Y = 0
tb.ICON_HEIGHT = math.min(tb.ICON_HEIGHT, list.HEIGHT)
tb.ICON_WIDTH = tb.ICON_HEIGHT
tb.ICON_MAX_COUNT = math.floor(tb.ICON_MAX_COUNT + 0.2)
tb.TOOLTIP_MARGIN_TOP = 0.005
tb.TOOLTIP_MARGIN_BOT = 0.010
tb.TOOLTIP_MARGIN_LEFT = 0.005
tb.TOOLTIP_MARGIN_RIGHT = 0.005
tb.ICON_MARGIN_X = 0.006
tb.ICON_MARGIN_Y = 0.004
tb.TOOLTIP_HEIGHT = 0.02
tb.TOOLTIP_WIDTH = CustomBuff.Main.WIDTH
tb.ICON_TOOLTIP_HEIGHT = 0.03
tb.ICON_TOOLTIP_WIDTH = tb.ICON_TOOLTIP_HEIGHT
tb.ICON_NAME_HEIGHT = tb.ICON_TOOLTIP_HEIGHT
tb.ICON_NAME_WIDTH = tb.TOOLTIP_WIDTH - tb.ICON_TOOLTIP_WIDTH
tb.ICON_TEXT_SCALE = 1.15
tb.DESC_TEXT_SCALE = 1.15
tb.ICON_TEXT_DENSITY = 0.004
tb.ICON_TEXT_LATERAL = 0.0115
tb.ICON_TEXT_LATERAL = tb.ICON_TEXT_LATERAL*tb.DESC_TEXT_SCALE
function tb.count_newlines(str)
local i, j = 0, 0
while true do
j = select(2, str:find('\n', j))
if not j then break;
end
i = i + 1
end
return i
end
function tb.get_frame(i)
i = i or 1
return tb._ICON_LIST[i]
end
function tb._process_max_icons()
local max = tb.ICON_MAX_COUNT
local width = tb.ICON_WIDTH
local rel_x = tb.ICON_REL_X
while width*max + rel_x*(max - 1) > list.WIDTH do
max = max - 1
end
tb.ICON_MAX_COUNT = max
tb.ICONS_WIDTH = width*max + rel_x*(max - 1)
end
tb._process_max_icons()
tb.REL_X = (list.WIDTH - tb.ICONS_WIDTH)/2
function tb._create()
local o = {}
o.main = BlzCreateFrameByType("BACKDROP", "CustomBuffIconBG", list.panel, "", 0)
o.icon = BlzCreateFrameByType("BACKDROP", "CustomBuffIcon", list.panel, "", 0)
o.button = BlzCreateFrameByType("GLUEBUTTON", "CustomBuffButton", o.icon, "IconicButtonTemplate", 0)
o.tooltip = {
frame = BlzCreateFrame("BoxedText", o.button, 0, 0),
width = tb.TOOLTIP_WIDTH,
height = tb.TOOLTIP_HEIGHT + tb.ICON_TOOLTIP_HEIGHT,
}
o.tooltip.height = o.tooltip.height + tb.TOOLTIP_MARGIN_TOP + tb.TOOLTIP_MARGIN_BOT
o.tooltip.name = BlzCreateFrameByType("TEXT", "CustomBuffName", o.tooltip.frame, "", 0)
o.tooltip.icon = BlzCreateFrameByType("BACKDROP", "CustomBuffTipIcon", o.tooltip.frame, "", 0)
o.tooltip.text = BlzCreateFrameByType("TEXT", "CustomBuffDescription", o.tooltip.frame, "", 0)
setmetatable(o, tb)
tb._setup_main(o)
tb._setup_tooltip(o)
return o
end
function tb:_setup_main()
BlzFrameSetSize(self.main, tb.ICON_WIDTH, tb.ICON_HEIGHT)
BlzFrameSetTexture(self.main, tb.ICON_PATH, 0, true)
BlzFrameClearAllPoints(self.icon)
BlzFrameClearAllPoints(self.button)
BlzFrameSetAllPoints(self.icon, self.main)
BlzFrameSetAllPoints(self.button, self.icon)
BlzFrameSetParent(self.icon, nil)
BlzFrameSetVisible(self.icon, false)
BlzFrameSetEnable(self.button, false)
end
function tb:_setup_tooltip()
BlzFrameSetParent(self.tooltip.frame, nil)
BlzFrameSetVisible(self.tooltip.frame, false)
local width = self.tooltip.width - tb.TOOLTIP_MARGIN_LEFT - tb.TOOLTIP_MARGIN_RIGHT
BlzFrameSetSize(self.tooltip.frame, self.tooltip.width, self.tooltip.height + tb.ICON_MARGIN_Y)
BlzFrameSetSize(self.tooltip.icon, tb.ICON_TOOLTIP_WIDTH, tb.ICON_TOOLTIP_HEIGHT)
BlzFrameSetSize(self.tooltip.name, tb.ICON_NAME_WIDTH, tb.ICON_NAME_HEIGHT)
BlzFrameSetSize(self.tooltip.text, width/tb.DESC_TEXT_SCALE,
self.tooltip.height - tb.ICON_TOOLTIP_HEIGHT - tb.TOOLTIP_MARGIN_BOT)
BlzFrameClearAllPoints(self.tooltip.frame)
BlzFrameClearAllPoints(self.tooltip.icon)
BlzFrameClearAllPoints(self.tooltip.name)
BlzFrameClearAllPoints(self.tooltip.text)
BlzFrameSetPoint(self.tooltip.frame, FRAMEPOINT_TOPLEFT, CustomBuff.Main.panel, FRAMEPOINT_BOTTOMLEFT,
0, 0)
BlzFrameSetPoint(self.tooltip.icon, FRAMEPOINT_TOPLEFT, self.tooltip.frame, FRAMEPOINT_TOPLEFT,
tb.TOOLTIP_MARGIN_RIGHT, -tb.TOOLTIP_MARGIN_TOP)
BlzFrameSetPoint(self.tooltip.name, FRAMEPOINT_LEFT, self.tooltip.icon, FRAMEPOINT_RIGHT,
tb.ICON_MARGIN_X, 0)
BlzFrameSetPoint(self.tooltip.text, FRAMEPOINT_TOPLEFT, self.tooltip.icon, FRAMEPOINT_BOTTOMLEFT, 0, -tb.ICON_MARGIN_Y)
BlzFrameSetTextAlignment(self.tooltip.text, TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_LEFT)
BlzFrameSetTextAlignment(self.tooltip.name, TEXT_JUSTIFY_CENTER, TEXT_JUSTIFY_LEFT)
BlzFrameSetScale(self.tooltip.name, tb.ICON_TEXT_SCALE)
BlzFrameSetScale(self.tooltip.text, tb.DESC_TEXT_SCALE)
end
function tb._condense_str(...)
local t = {...}
if #t <= 0 then return "";
end
t.str = ""
for i = 1, #t do
t[i] = (type(t[i]) == 'string' and t[i]) or tostring(t[i])
t.str = t.str .. t[i]
end
return t.str
end
function tb:visible()
return BlzFrameIsVisible(self.icon)
end
function tb:show(flag)
BlzFrameSetVisible(self.icon, flag)
end
function tb:activate(flag)
BlzFrameSetEnable(self.button, flag)
end
function tb:set_icon(iconpath)
iconpath = (type(iconpath) == 'string' and iconpath) or tostring(iconpath)
BlzFrameSetTexture(self.icon, iconpath, 0, true)
BlzFrameSetTexture(self.tooltip.icon, iconpath, 0, true)
end
function tb:set_name(...)
local str = tb._condense_str(...)
BlzFrameSetText(self.tooltip.name, tb._condense_str(...))
end
function tb:set_desc(...)
local str = tb._condense_str(...)
BlzFrameSetText(self.tooltip.text, str)
local width = (self.tooltip.width - tb.TOOLTIP_MARGIN_LEFT - tb.TOOLTIP_MARGIN_RIGHT)
local lateral = math.floor(#str/width*tb.ICON_TEXT_DENSITY*tb.DESC_TEXT_SCALE)
local newlines = tb.count_newlines(str)
lateral = lateral + newlines
local size = self.tooltip.height + lateral*tb.ICON_TEXT_LATERAL
BlzFrameSetSize(self.tooltip.frame, self.tooltip.width, size + tb.ICON_MARGIN_Y)
BlzFrameSetSize(self.tooltip.text, width/tb.DESC_TEXT_SCALE, size - tb.ICON_TOOLTIP_HEIGHT - tb.TOOLTIP_MARGIN_BOT)
end
function tb:unfocus()
BlzFrameSetFocus(self.button, false)
end
Initializer("SYSTEM", function()
tb.ICON_NAME_WIDTH = tb.ICON_NAME_WIDTH - tb.TOOLTIP_MARGIN_LEFT - tb.TOOLTIP_MARGIN_RIGHT
tb.ICON_NAME_WIDTH = tb.ICON_NAME_WIDTH - tb.ICON_MARGIN_X
BlzLoadTOCFile(tb.TOC_PATH)
tb._ICON_LIST[1] = tb._create()
BlzFrameSetPoint(tb._ICON_LIST[1].main, FRAMEPOINT_LEFT, list.panel, FRAMEPOINT_LEFT, tb.REL_X, 0)
for i = 2, tb.ICON_MAX_COUNT do
tb._ICON_LIST[i] = tb._create()
BlzFrameSetPoint(tb._ICON_LIST[i].main, FRAMEPOINT_LEFT, tb._ICON_LIST[i - 1].main, FRAMEPOINT_RIGHT, tb.ICON_REL_X, 0)
end
end)
end
do
local tb = protected_table()
local icons = CustomBuff.ListIcons
local l_player
CustomBuff.ListEvent = setmetatable({}, tb)
tb._PRIMARY = {}
tb._STACK = {}
tb._BUTTON_POINT = {}
tb._BUTTON_POS = {}
function tb.get_primary_index(p)
return tb._PRIMARY[GetPlayerId(p)]
end
function tb._update(p, id, i, count)
tb._STACK[id][i] = tb._STACK[id][i] + count
local flag = tb._STACK[id][i] > 0
local self = icons.get_frame(i)
if l_player == p then
BlzFrameSetVisible(self.tooltip.frame, flag)
end
end
function tb._on_enter(p, id, self)
if tb._PRIMARY[id] ~= 0 then return;
end
local i = tb._BUTTON_POS[self.button]
tb._update(p, id, i, 1)
end
function tb._on_leave(p, id, self)
if tb._PRIMARY[id] ~= 0 then return;
end
local i = tb._BUTTON_POS[self.button]
tb._update(p, id, i, -1)
end
function tb._on_click(p, id, self)
local i = tb._BUTTON_POS[self.button]
if tb._PRIMARY[id] == i then
tb._PRIMARY[id] = 0
tb._update(p, id, i, 0)
return
end
if tb._PRIMARY[id] ~= 0 then
local j = tb._PRIMARY[id]
tb._update(p, id, j, -1)
end
tb._PRIMARY[id] = i
self:show(false)
self:show(true)
self:activate(false)
self:activate(true)
self:unfocus()
if tb._STACK[id][i] == 0 then
tb._update(p, id, i, 1)
end
end
function tb.deactivate_buff(p, i)
local id = GetPlayerId(p)
if tb._PRIMARY[id] ~= i then return;
end
tb._PRIMARY[id] = 0
if tb._STACK[id][i] > 0 then
tb._update(p, id, i, -1)
else
tb._update(p, id, i, 0)
end
end
function tb._parse(p, self, event)
local id = GetPlayerId(p)
if event == FRAMEEVENT_MOUSE_ENTER then
tb._on_enter(p, id, self, event)
elseif event == FRAMEEVENT_MOUSE_LEAVE then
tb._on_leave(p, id, self, event)
elseif event == FRAMEEVENT_CONTROL_CLICK then
tb._on_click(p, id, self, event)
end
end
Initializer("SYSTEM", function()
l_player = GetLocalPlayer()
local trig = CreateTrigger()
for id = 0, bj_MAX_PLAYER_SLOTS - 1 do
local p = Player(id)
tb._PRIMARY[id] = 0
tb._STACK[id] = {}
for i = 1, icons.ICON_MAX_COUNT do
tb._STACK[id][i] = 0
end
end
for i = 1, icons.ICON_MAX_COUNT do
local self = icons.get_frame(i)
tb._BUTTON_POINT[self.button] = self
tb._BUTTON_POS[self.button] = i
BlzTriggerRegisterFrameEvent(trig, self.button, FRAMEEVENT_MOUSE_ENTER)
BlzTriggerRegisterFrameEvent(trig, self.button, FRAMEEVENT_MOUSE_LEAVE)
BlzTriggerRegisterFrameEvent(trig, self.button, FRAMEEVENT_CONTROL_CLICK)
end
TriggerAddCondition(trig, Condition(function()
local p = GetTriggerPlayer()
local self = tb._BUTTON_POINT[BlzGetTriggerFrame()]
local event = BlzGetTriggerFrameEvent()
tb._parse(p, self, event)
end))
end)
end
do
local tb = protected_table()
local main = CustomBuff.Main
CustomBuff.Selection = setmetatable({}, tb)
tb.ICON_UP_PATH = "Ui\\Custom\\DropdownArrowUp.blp"
tb.ICON_DOWN_PATH = "Ui\\Custom\\DropdownArrowDown.blp"
tb.HEIGHT = 0.02
tb.WIDTH = 0.04
tb.REL_X = 0.002
tb.REL_Y = 0
tb.HEIGHT = math.min(tb.HEIGHT, main.HEIGHT)
tb.WIDTH = math.min(tb.WIDTH, main.WIDTH_REMAIN - tb.REL_X)
main.WIDTH_REMAIN = main.WIDTH_REMAIN - (tb.WIDTH + tb.REL_X)
tb.DISP_OFFSET = 0.015
tb.DISP_HEIGHT = tb.HEIGHT
tb.DISP_WIDTH = tb.WIDTH - tb.DISP_OFFSET
tb.DISP_SCALE = 1.25
function tb._create_framework()
tb.disp_frame = BlzCreateFrameByType("TEXT", "CustomBuffTextDisplay", main.panel, "", 0)
tb.click_frame = BlzCreateFrameByType("BACKDROP", "CustomBuffSelectionFrame", main.panel, "", 0)
BlzFrameSetSize(tb.disp_frame, tb.DISP_WIDTH, tb.DISP_HEIGHT)
BlzFrameSetSize(tb.click_frame, tb.DISP_OFFSET, tb.DISP_HEIGHT)
BlzFrameClearAllPoints(tb.disp_frame)
BlzFrameClearAllPoints(tb.click_frame)
BlzFrameSetPoint(tb.disp_frame, FRAMEPOINT_LEFT, CustomBuff.ListPanel.panel, FRAMEPOINT_RIGHT, tb.REL_X, tb.REL_Y)
BlzFrameSetPoint(tb.click_frame, FRAMEPOINT_LEFT, tb.disp_frame, FRAMEPOINT_RIGHT, 0, 0)
BlzFrameSetScale(tb.disp_frame, tb.DISP_SCALE)
BlzFrameSetTextAlignment(tb.disp_frame, TEXT_JUSTIFY_CENTER, TEST_JUSTIFY_LEFT)
BlzFrameSetText(tb.disp_frame, "0/0")
BlzFrameSetAlpha(tb.click_frame, 0)
end
function tb._create_buttons()
tb.top_disp = BlzCreateFrameByType("BACKDROP", "CustomBuffDropDownIconUp", tb.disp_frame, "", 0)
tb.bot_disp = BlzCreateFrameByType("BACKDROP", "CustomBuffDropDownIconDown", tb.disp_frame, "", 0)
tb.top_button = BlzCreateFrameByType("BUTTON", "CustomBuffDropDownButtonUp", tb.top_disp, "ScoreScreenTabButtonTemplate", 0)
tb.bot_button = BlzCreateFrameByType("BUTTON", "CustomBuffDropDownButtonDown", tb.bot_disp, "ScoreScreenTabButtonTemplate", 0)
BlzFrameSetSize(tb.top_disp, tb.DISP_OFFSET, tb.DISP_HEIGHT/2)
BlzFrameSetSize(tb.bot_disp, tb.DISP_OFFSET, tb.DISP_HEIGHT/2)
BlzFrameClearAllPoints(tb.top_disp)
BlzFrameClearAllPoints(tb.bot_disp)
BlzFrameSetPoint(tb.top_disp, FRAMEPOINT_TOP, tb.click_frame, FRAMEPOINT_TOP, 0, 0)
BlzFrameSetPoint(tb.bot_disp, FRAMEPOINT_TOP, tb.top_disp, FRAMEPOINT_BOTTOM, 0, 0)
BlzFrameSetAllPoints(tb.top_button, tb.top_disp)
BlzFrameSetAllPoints(tb.bot_button, tb.bot_disp)
BlzFrameSetTexture(tb.top_disp, tb.ICON_UP_PATH, 0, true)
BlzFrameSetTexture(tb.bot_disp, tb.ICON_DOWN_PATH, 0, true)
end
Initializer("SYSTEM", function()
tb._create_framework()
tb._create_buttons()
end)
end
do
local tb = protected_table()
CustomBuff.UnitInfo = setmetatable({}, tb)
CustomBuffSystem = CustomBuff.UnitInfo
local mtb = {}
mtb.lists = {}
mtb.disp_counter = {}
function tb._add(whichunit, bufficon, buffname, buffdesc)
local o = {}
o.icon_path = bufficon
o.icon_name = buffname
o.icon_desc = buffdesc
o.unit = whichunit
mtb.disp_counter[o] = 0
setmetatable(o, tb)
return o
end
function tb:_is_valid()
return mtb.disp_counter[self] ~= nil
end
function tb.add(whichunit, bufficon, buffname, buffdesc)
if not mtb.lists[whichunit] then
mtb.lists[whichunit] = SimpleList()
end
local o = tb._add(whichunit, bufficon, buffname, buffdesc)
mtb.lists[whichunit]:insert(o)
return o
end
function tb:destroy()
if not tb._is_valid(self) then return;
end
mtb.lists[self.unit]:remove(self)
self.icon_path = nil
self.icon_name = nil
self.icon_desc = nil
self.unit = nil
mtb.disp_counter[self] = nil
end
function tb:show_icon(flag)
if not tb._is_valid(self) then return;
end
if flag then
mtb.disp_counter[self] = mtb.disp_counter[self] + 1
else
mtb.disp_counter[self] = mtb.disp_counter[self] - 1
end
end
function tb:is_visible(flag)
if not tb._is_valid(self) then return false;
end
return mtb.disp_counter[self] >= 0
end
function tb.get_list(whichunit)
return mtb.lists[whichunit]
end
tb.remove = tb.destroy
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
if not mtb.lists[unit] then return;
end
for self in mtb.lists[unit] do
self:destroy()
end
mtb.lists[unit]:destroy()
mtb.lists[unit] = nil
end)
end
do
local tb = protected_table()
local system = CustomBuff.UnitInfo
local icons = CustomBuff.ListIcons
local event = CustomBuff.ListEvent
local selection = CustomBuff.Selection
CustomBuff.Update = setmetatable({}, tb)
local mtb = {}
mtb.select_unit = {}
mtb.select_stamp = {}
tb._INTERVAL = 1/4.
--[[
TO-DO: Include a maximum number count for convenience.
]]
function tb._unrender(p, i, j)
while j <= icons.ICON_MAX_COUNT do
local icon = icons.get_frame(j)
if mtb.player == p then
if icon:visible() then
icon:show(false)
icon:activate(false)
end
icon:set_icon(nil)
icon:set_name(nil)
icon:set_desc(nil)
end
event.deactivate_buff(p, j)
j = j + 1
end
end
function tb._render(p, i, unit, list)
local j = -mtb.select_stamp[i]
for buff in list:iterator() do
j = j + 1
if j > 0 then
if j > icons.ICON_MAX_COUNT then
j = j - 1
break;
end
local icon = icons.get_frame(j)
if mtb.player == p then
if buff:is_visible() then
if not icon:visible() then
icon:show(true)
icon:activate(true)
end
icon:set_icon(buff.icon_path)
icon:set_name(buff.icon_name)
icon:set_desc(buff.icon_desc)
else
if icon:visible() then
icon:show(false)
icon:activate(false)
end
icon:set_icon(nil)
icon:set_name(nil)
icon:set_desc(nil)
end
end
end
end
local str = tostring(j + mtb.select_stamp[i]) .. "/" .. tostring(#list)
if mtb.player == p then
BlzFrameSetText(selection.disp_frame, str)
end
j = j + 1
tb._unrender(p, i, j)
end
Initializer("SYSTEM", function()
mtb.player = GetLocalPlayer()
mtb.player_id = GetPlayerId(mtb.player)
local ptb = {}
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
local p = Player(i)
if GetPlayerController(p) == MAP_CONTROL_USER then
ptb[#ptb + 1] = p
mtb.select_stamp[i] = 0
end
end
local g = CreateGroup()
local timer = CreateTimer()
TimerStart(timer, tb._INTERVAL, true, function()
for j = 1, #ptb do
local p = ptb[j]
local i = GetPlayerId(p)
GroupEnumUnitsSelected(g, p, nil)
local unit = FirstOfGroup(g)
local list = system.get_list(unit)
if mtb.select_unit[i] ~= unit then
mtb.select_stamp[i] = 0
mtb.select_unit[i] = unit
end
if list then
tb._render(p, i, unit, list)
else
tb._unrender(p, i, 1)
if mtb.player == p then
BlzFrameSetText(selection.disp_frame, "0/0")
end
end
end
end)
local trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, selection.top_button, FRAMEEVENT_CONTROL_CLICK)
BlzTriggerRegisterFrameEvent(trig, selection.bot_button, FRAMEEVENT_CONTROL_CLICK)
TriggerAddCondition(trig, Condition(function()
local p = GetTriggerPlayer()
local i = GetPlayerId(p)
local button = BlzGetTriggerFrame()
if mtb.player == p then
BlzFrameSetEnable(button, false)
BlzFrameSetEnable(button, true)
BlzFrameSetFocus(button, false)
end
GroupEnumUnitsSelected(g, p, nil)
local unit = FirstOfGroup(g)
local list = system.get_list(unit)
if mtb.select_unit[i] ~= unit then
mtb.select_stamp[i] = 0
mtb.select_unit[i] = unit
return
end
if not list then
mtb.select_stamp[i] = 0
return
end
if #list <= icons.ICON_MAX_COUNT then return;
end
if button == selection.top_button then
mtb.select_stamp[i] = mtb.select_stamp[i] + icons.ICON_MAX_COUNT
if mtb.select_stamp[i] > #list then
mtb.select_stamp[i] = 0
end
else
mtb.select_stamp[i] = mtb.select_stamp[i] - icons.ICON_MAX_COUNT
if mtb.select_stamp[i] < 0 then
mtb.select_stamp[i] = math.floor(#list/icons.ICON_MAX_COUNT)*icons.ICON_MAX_COUNT
end
end
event.deactivate_buff(p, event.get_primary_index(p))
tb._render(p, i, unit, list)
end))
end)
end
do
local tb = AllocTableEx(50)
tb._BASE = 852001 -- base index translation
tb._removed = {}
tb.table = {}
tb.items = {[0] = {}}
InventoryWatch = setmetatable({}, tb)
function GetUnitItemIdCount(whichunit, itemid)
return tb.table[whichunit][itemid] or 0
end
function GetUnitItemCount(whichunit)
return tb.items[0][whichunit] or 0
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM, function()
local unit = GetManipulatingUnit()
local item = GetManipulatedItem()
local flag = BlzGetItemBooleanField(item, ITEM_BF_USE_AUTOMATICALLY_WHEN_ACQUIRED)
if flag then return;
end
local id = GetItemTypeId(item)
-- Always ensure the object exists.
tb.table[unit] = tb.table[unit] or tb.request()
tb.items[0][unit] = tb.items[0][unit] or 0
tb.items[0][unit] = tb.items[0][unit] + 1
tb.table[unit][id] = tb.table[unit][id] or 0
tb.table[unit][id] = tb.table[unit][id] + 1
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM, function()
local unit = GetManipulatingUnit()
local item = GetManipulatedItem()
local flag = BlzGetItemBooleanField(item, ITEM_BF_USE_AUTOMATICALLY_WHEN_ACQUIRED)
if flag then return;
elseif not tb.table[unit] then return;
end
local id = GetItemTypeId(item)
-- Always assume that the object exists
tb.items[0][unit] = tb.items[0][unit] - 1
tb.table[unit][id] = tb.table[unit][id] - 1
if tb.table[unit][id] <= 0 then
tb.table[unit][id] = nil
end
end)
UnitDex.register("ENTER_EVENT", function(unit)
tb.table[unit] = tb.table[unit] or tb.request()
tb.items[0][unit] = tb.items[0][unit] or 0
end)
UnitDex.register("LEAVE_EVENT", function(unit)
tb.restore(tb.table[unit])
tb.items[0][unit] = nil
tb.table[unit] = nil
end)
end
do
local tb = protected_table()
local techid = {}
local techused = {}
tb._pointer = {}
tb._selves = SimpleList()
tb._handler = EventListener:create()
TieredResearch = setmetatable({}, tb)
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
techused[i] = {}
end
function tb:add(levels, ...)
levels = levels or 1
levels = math.max(math.floor(levels), 1)
local j = select('#', ...)
if j <= 0 then return;
end
local self = {list = SimpleList(), max = levels, handler = EventListener:create()}
setmetatable(self, tb)
tb._selves:insert(self)
for i = 1, j do
techid[i] = select(i, ...)
techid[i] = (type(techid[i]) == 'string' and FourCC(techid[i])) or techid[i]
if not tb._pointer[techid[i]] then
tb._pointer[techid[i]] = self
self.list:insert(techid[i])
end
techid[i] = nil
end
if Initializer.initialized("SYSTEM") then
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
local usedlvl = 0
local player = Player(i)
for tech in self.list:iterator() do
usedlvl = usedlvl + GetPlayerTechCount(player, tech, true)
end
techused[i][self] = usedlvl
tb._attempt_restrict(self, nil, i)
end
end
return self
end
function tb:_attempt_restrict(tech, id)
local usedlvl = techused[id][self]
local player = Player(id)
local lvl = 0
for research in self.list:iterator() do
lvl = lvl + GetPlayerTechCount(player, research, true)
if research == tech then
lvl = lvl + 1
end
end
usedlvl = lvl
techused[id][self] = usedlvl
for research in self.list:iterator() do
local lvl = GetPlayerTechCount(player, research, true)
if research == tech then
lvl = lvl + 1
end
SetPlayerTechMaxAllowed(player, research, self.max - usedlvl + lvl)
end
local is_max = (not tech) and (usedlvl == self.max)
self.handler:execute(player, usedlvl, is_max)
end
function tb:register(func)
self.handler:register(func)
end
function tb.subscribe(func)
tb._handler:register(func)
end
ResearchEvent.subscribe(function(player, tech, eventtype)
if not tb._pointer[tech] then return;
end
local id = GetPlayerId(player)
local self = tb._pointer[tech]
if eventtype == "start" then
tb._attempt_restrict(self, tech, id)
else
tb._attempt_restrict(self, nil, id)
end
end)
Initializer("SYSTEM", function()
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
local player = Player(i)
for self in tb._selves:iterator() do
local usedlvl = 0
for tech in self.list:iterator() do
usedlvl = usedlvl + GetPlayerTechCount(player, usedlvl, true)
end
techused[i][self] = usedlvl
tb._attempt_restrict(self, nil, i)
end
end
end)
end
--[[
TerrainPathability
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This script can be used to detect the type of pathing at a specific point.
//* It is valuable to do it this way because the IsTerrainPathable is very
//* counterintuitive and returns in odd ways and aren't always as you would
//* expect. This library, however, facilitates detecting those things reliably
//* and easily.
//*
//******************************************************************************
//*
//* > function IsTerrainDeepWater takes real x, real y returns boolean
//* > function IsTerrainShallowWater takes real x, real y returns boolean
//* > function IsTerrainLand takes real x, real y returns boolean
//* > function IsTerrainPlatform takes real x, real y returns boolean
//* > function IsTerrainWalkable takes real x, real y returns boolean
//*
//* These functions return true if the given point is of the type specified
//* in the function's name and false if it is not. For the IsTerrainWalkable
//* function, the MAX_RANGE constant below is the maximum deviation range from
//* the supplied coordinates that will still return true.
//*
//* The IsTerrainPlatform works for any preplaced walkable destructable. It will
//* return true over bridges, destructable ramps, elevators, and invisible
//* platforms. Walkable destructables created at runtime do not create the same
//* pathing hole as preplaced ones do, so this will return false for them. All
//* other functions except IsTerrainWalkable return false for platforms, because
//* the platform itself erases their pathing when the map is saved.
//*
//* After calling IsTerrainWalkable(x, y), the following two global variables
//* gain meaning. They return the X and Y coordinates of the nearest walkable
//* point to the specified coordinates. These will only deviate from the
//* IsTerrainWalkable function arguments if the function returned false.
//*
//*
]]
do
local tb = protected_table()
tb.MAX_RANGE = 10.
tb.DUMMY_ITEM_ID = FourCC('wolg')
TerrainPathability = setmetatable({}, tb)
function IsTerrainDeepWater(x, y)
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
end
function IsTerrainShallowWater(x, y)
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
and IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
end
function IsTerrainLand(x, y)
return IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
end
function IsTerrainPlatform(x, y)
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
and not IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
end
function tb._HideItem()
if IsItemVisible(GetEnumItem()) then
tb._Hid[tb._HidMax] = GetEnumItem()
SetItemVisible(tb._Hid[tb._HidMax], false)
tb._HidMax = tb._HidMax + 1
end
end
function IsTerrainWalkable(x, y)
--Hide any items in the area to avoid conflicts with our item
MoveRectTo(tb._Find, x, y)
EnumItemsInRect(tb._Find, nil, tb._HideItem)
--Try to move the test item and get its coords
SetItemPosition(tb._Item, x, y) --Unhides the item
local X = GetItemX(tb._Item)
local Y = GetItemY(tb._Item)
SetItemVisible(tb._Item, false)--Hide it again
--Unhide any items hidden at the start
while tb._HidMax > 0 do
tb._HidMax = tb._HidMax - 1
SetItemVisible(Hid[tb._HidMax], true)
Hid[tb._HidMax] = null
end
--Return walkability
return ((X-x)*(X-x)+(Y-y)*(Y-y) <= tb.MAX_RANGE*tb.MAX_RANGE
and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)), X, Y
end
Initializer("SYSTEM", function()
tb._Find = Rect(0., 0., 128., 128.)
tb._Item = CreateItem(tb.DUMMY_ITEM_ID, 0, 0)
tb._Hid = {}
tb._HidMax = 0
SetItemVisible(tb._Item, false)
end)
end
do
local tb = AllocTable(100)
local temp = {}
local data = {}
local list = {}
tb.DEF_PRIORITY = 2
tb.INTERVAL = Missile.INTERVAL
tb.GRADIENT = Missile.GRADIENT
tb._NORMAL = Vector2D.ORIGIN
tb._FLY_ABIL = FourCC("Amrf")
UnitMovement = setmetatable({}, tb)
tb._moving = TimerIterator:create(tb.INTERVAL, function(self)
if rawget(self, 'on_update') then
temp.vect, temp.dz = self.on_update(self)
else
temp.vect, temp.dz = tb._NORMAL, 0
end
if not self.unit then return;
elseif not self.running then return;
end
local tx, ty, h = self:get_x(), self:get_y(), self:get_height()
local flag = true
tx, ty, h = tx + temp.vect.x, ty + temp.vect.y, h + temp.dz
if self.checkpathing then
if not IsUnitType(self.unit, UNIT_TYPE_FLYING) then
flag, tx, ty = IsTerrainWalkable(tx, ty)
end
end
if flag then
self:set_x(tx)
self:set_y(ty)
end
self:set_height(h)
end)
function tb:get_x()
return GetUnitX(self.unit)
end
function tb:get_y()
return GetUnitY(self.unit)
end
function tb:get_height()
return GetUnitFlyHeight(self.unit)
end
function tb:set_data(value)
data[self] = value
end
function tb:set_x(val)
val = val or 0
SetUnitX(self.unit, val)
end
function tb:set_y(val)
val = val or 0
SetUnitY(self.unit, val)
end
function tb:set_height(val)
val = val or GetUnitDefaultFlyHeight(self.unit)
if UnitAddAbility(self.unit, tb._FLY_ABIL) then
UnitRemoveAbility(self.unit, tb._FLY_ABIL)
end
SetUnitFlyHeight(self.unit, val, 0)
end
function tb:get_data()
return data[self]
end
function tb:show(flag)
ShowUnit(self.unit, flag)
end
function tb:visible()
return not IsUnitHidden(self.unit)
end
function tb:__constructor(unit, priority)
self.unit = unit
self.running = false
self.checkpathing = true
self.priority = priority or tb.DEF_PRIORITY
if not list[unit] then
list[unit] = SimpleList()
end
list[unit]:insert(self)
end
function tb:__destructor()
if rawget(self, 'running') == nil then return;
end
self:stop()
self.running = nil
if rawget(self, 'on_destroy') then
local func = self.on_destroy
rawset(self, 'on_destroy', nil)
func(self)
end
rawset(self, 'on_stop', nil)
rawset(self, 'on_launch', nil)
rawset(self, 'on_update', nil)
local unit = self.unit
data[self] = nil
self.unit = nil
self.checkpathing = nil
self.priority = nil
list[unit]:remove(self)
end
-- Generic launch and stop methods
function tb:is_moving()
return self.running
end
function tb:launch(cx, cy, h)
if self.running then return;
end
self.running = true
tb._moving:insert(self)
if rawget(self, 'on_launch') then
self.on_launch(self, cx, cy, h)
end
if cx then
self:set_x(cx)
end
if cy then
self:set_y(cy)
end
if h then
self:set_height(h)
end
end
function tb:stop(interrupted)
if not self.running then return;
end
self.running = false
tb._moving:remove(self)
if rawget(self, 'on_stop') then
self.on_stop(self, interrupted)
end
end
-- Configurable callback functions.
function tb:config_launch(func)
if not is_function(func) then return;
end
rawset(self, 'on_launch', func)
end
function tb:config_move(func)
if not is_function(func) then return;
end
rawset(self, 'on_update', func)
end
function tb:config_destroy(func)
if not is_function(func) then return;
end
rawset(self, 'on_destroy', func)
end
function tb:config_stop(func)
if not is_function(func) then return;
end
rawset(self, 'on_stop', func)
end
-- Interruption destroys instances.
function tb.interrupt(unit, prio)
if not list[unit] then return;
end
prio = prio or tb.DEF_PRIORITY
for self in list[unit]:iterator() do
if self.priority <= prio then
self:stop(true)
self:destroy()
end
end
end
UnitDex.register("LEAVE_EVENT", function(unit)
if not list[unit] then return;
end
for self in list[unit]:iterator() do
self:destroy()
end
list[unit]:destroy()
list[unit] = nil
end)
end
do
local tb = AllocTable(10)
local mtb = {}
local list = {}
local stun = {
ABIL_ID = FourCC("uKup"),
BUFF_ID = FourCC("BKup"),
ORDER = "creepthunderbolt",
}
tb._DEBUG = false
tb.DEF_PRIORITY = UnitMovement.DEF_PRIORITY + 2
Knockback = setmetatable({}, tb)
Initializer("SYSTEM", function()
local dummy = DummyUtils.request()
UnitAddAbility(dummy, stun.ABIL_ID)
BlzUnitDisableAbility(dummy, FourCC("Amov"), true, true)
tb.DUMMY = dummy
end)
function tb._apply_knockup(unit, dostun)
if not dostun then return;
end
if not stun[unit] then
stun[unit] = 0
end
SetUnitX(tb.DUMMY, GetUnitX(unit))
SetUnitY(tb.DUMMY, GetUnitY(unit))
IssueTargetOrder(tb.DUMMY, stun.ORDER, unit)
stun[unit] = stun[unit] + 1
end
function tb._remove_knockup(unit)
if not stun[unit] then return;
end
stun[unit] = stun[unit] - 1
if stun[unit] <= 0 then
UnitRemoveAbility(unit, stun.BUFF_ID)
end
end
function tb:__constructor(unit, duration, dostun)
self.movement = UnitMovement(unit, tb.DEF_PRIORITY)
self.vel_xy = 0.
self.vel_z = 0.
self.stunflag = (dostun == true)
mtb[self.movement] = self
self.movement:config_move(tb._on_knockback)
self.movement:config_destroy(tb._on_destroy)
tb.set_duration(self, duration or 0)
if not list[unit] then
list[unit] = SimpleList()
end
tb._apply_knockup(unit, dostun)
list[unit]:insert(self)
end
function tb:__destructor()
list[self.movement.unit]:remove(self)
if self.stunflag then
tb._remove_knockup(self.movement.unit)
end
mtb[self.movement] = nil
self.vel_xy = nil
self.vel_z = nil
self.duration = nil
self.theta = nil
self.stunflag = nil
end
function tb:config_move(func)
if not is_function(func) then return;
end
rawset(self, 'on_update', func)
end
function tb:config_stop(func)
if not is_function(func) then return;
end
rawset(self, 'on_stop', func)
end
function tb:config_launch(func)
self.movement:config_launch(func)
end
function tb:config_destroy(func)
self.movement:config_destroy(func)
end
function tb:set_velocity(speed, z_speed)
speed = speed or 0
z_speed = z_speed or 0
self.vel_xy = speed*UnitMovement.GRADIENT
self.vel_z = z_speed*UnitMovement.GRADIENT
if rawget(self, 'duration') then
self.dxy = -2*self.vel_xy/self.duration
self.dz = -2*self.vel_z/self.duration
end
end
function tb:set_theta(value)
rawset(self, 'theta', value)
end
function tb:set_duration(value)
value = value or UnitMovement.GRADIENT
value = math.max(math.floor(value*UnitMovement.INTERVAL + 0.5), 1)
rawset(self, 'duration', value)
self.dxy = -2*self.vel_xy/self.duration
self.dz = -2*self.vel_z/self.duration
end
function tb:get_theta()
return rawget(self, 'theta') or 0
end
function tb:get_velocity()
return self.vel_xy*UnitMovement.INTERVAL, self.vel_z*UnitMovement.INTERVAL
end
function tb:get_duration()
local dur = rawget(self, 'duration') or 0
dur = dur*UnitMovement.GRADIENT
return dur
end
function tb:launch()
if self.duration <= 0 then return;
elseif not rawget(self, 'theta') then return;
end
self.movement:config_move(tb._on_knockback)
self.movement:config_stop(tb._on_knockback_stop)
self.movement:config_destroy(tb._on_destroy)
UnitMovement.interrupt(self.movement.unit)
self.movement:launch()
end
function tb:get_data()
return self.movement:get_data()
end
function tb:set_data(value)
self.movement:set_data(value)
end
function tb._on_knockback(movement)
local self = mtb[movement]
local flag = true
if rawget(self, 'on_update') then
flag = pcall(self.on_update, self)
end
if not flag and tb._DEBUG then
print("Knockback.on_update >> Instance failed to terminate!")
print("Knockback.on_update >> Instance:", self)
PauseGame(true)
return
end
local dx, dy = self.vel_xy*math.cos(self.theta), self.vel_xy*math.sin(self.theta)
local dz = self.vel_z
self.vel_xy = self.vel_xy + self.dxy
self.vel_z = self.vel_z + self.dz
self.duration = self.duration - 1
if self.duration <= 0 then
if #list[movement.unit] == 1 then
dz = GetUnitDefaultFlyHeight(movement.unit) - movement:get_height()
end
movement:set_height(movement:get_height() + dz)
movement:stop()
return Vector2D.ORIGIN, 0
end
return Vector2D(dx, dy), dz
end
function tb._on_destroy(movement)
local self = mtb[movement]
self:destroy()
end
function tb._on_knockback_stop(movement)
local self = mtb[movement]
local flag = true
if rawget(self, 'on_stop') then
flag = pcall(self.on_stop, self)
end
if not flag and tb._DEBUG then
print("Knockback.on_stop >> Instance failed to terminate")
end
movement:destroy()
end
UnitDex.register("LEAVE_EVENT", function(unit)
if stun[unit] then
stun[unit] = nil
end
if not list[unit] then return;
end
list[unit]:destroy()
list[unit] = nil
end)
end
do
local tb = protected_table()
tb._DEF_CHANCE = 0.05
FixedChance = setmetatable({}, tb)
function tb:create(chance)
local o = {}
o._chance = 1/(chance or tb._DEF_CHANCE)
o._cur = 0
setmetatable(o, tb)
return o
end
function tb:destroy()
self._chance = nil
self._cur = nil
end
function tb:test()
self._cur = self._cur + 1
if self._cur >= self._chance then
-- Wrap around
self._cur = math.fmod(self._cur, self._chance)
return true
end
return false
end
function tb:set_chance(chance)
self._chance = 1/(chance or tb._DEF_CHANCE)
end
function tb:get_chance()
return 1/self._chance
end
function tb:cur_progress()
return self._cur/self._chance
end
end
do
local tb = {pointer={}}
function tb.request()
local obj
if not tb[#tb] then
obj = {group = CreateGroup(), index=0, max=0}
obj.callback = function()
local unit = nil
while (not unit) and (obj.index <= obj.max) do
unit = BlzGroupUnitAt(obj.group, obj.index)
obj.index = obj.index + 1
end
if obj.index > obj.max then
PauseTimer(obj.timer)
tb.recycle(obj)
return nil;
end
return unit
end
obj.timer = CreateTimer()
SetTimerData(obj.timer, obj)
TimerStart(obj.timer, 0.00, false, tb.recycle_obj)
return obj
end
obj = tb[#tb]
tb[#tb] = nil
tb.pointer[obj] = nil
return obj
end
function tb.recycle(obj)
if tb.pointer[obj] then return;
end
tb[#tb + 1] = obj
tb.pointer[obj] = #tb
end
function tb.recycle_obj()
tb.recycle(GetTimerData(GetExpiredTimer()))
end
function tb.enum_factory(func)
return function(...)
local obj = tb.request()
func(obj.group, ...)
obj.index = 0
obj.max = BlzGroupGetSize(obj.group)
ResumeTimer(obj.timer)
return obj.callback
end
end
EnumUnitsInRange = tb.enum_factory(GroupEnumUnitsInRange)
EnumUnitsOfPlayer = tb.enum_factory(GroupEnumUnitsOfPlayer)
end
do
local tb = {}
tb.DELTA = 0.01
tb.FACTOR = 100
function GetUnitMaxHP(whichunit)
local curHP = GetWidgetLife(whichunit)
local maxHP = BlzGetUnitMaxHP(whichunit)
local amount
SetWidgetLife(whichunit, maxHP)
while true do
amount = GetWidgetLife(whichunit)
SetWidgetLife(whichunit, amount*tb.FACTOR)
if math.abs(GetWidgetLife(whichunit) - amount) <= tb.DELTA then break
end
end
SetWidgetLife(whichunit, curHP)
return amount
end
function GetUnitMaxMana(whichunit)
local curMP = GetUnitState(whichunit, UNIT_STATE_MANA)
local maxMP = BlzGetUnitMaxMana(whichunit)
local amount
SetUnitState(whichunit, UNIT_STATE_MANA, maxMP)
while true do
amount = GetUnitState(whichunit, UNIT_STATE_MANA)
SetUnitState(whichunit, UNIT_STATE_MANA, maxMP*tb.FACTOR)
if math.abs(GetUnitState(whichunit, UNIT_STATE_MANA) - amount) <= tb.DELTA then break
end
end
SetWidgetLife(whichunit, curMP)
return amount
end
function UnitStopMovement(whichunit, hide)
hide = (hide == nil and true) or hide
BlzUnitDisableAbility(whichunit, FourCC("Amov"), true, hide)
end
function UnitStartMovement(whichunit, hide)
hide = (hide == nil and false) or hide
BlzUnitDisableAbility(whichunit, FourCC("Amov"), false, hide)
end
function UnitSuspendAttack(whichunit, hide)
hide = (hide == nil and true) or hide
BlzUnitDisableAbility(whichunit, FourCC("Aatk"), true, hide)
end
function UnitRestoreAttack(whichunit, hide)
hide = (hide == nil and false) or hide
BlzUnitDisableAbility(whichunit, FourCC("Aatk"), false, hide)
end
end
do
local tb = protected_table()
tb._TICKS = 32
tb._DECAY = 3
tb._DUR = 3
tb._alpha = {}
GameError = setmetatable({}, tb)
tb._fade = TimerIterator:create(tb._TICKS, function(i)
tb._alpha[i] = math.max(tb._alpha[i] - tb._RATE, 0)
if GetLocalPlayer() == Player(i) then
BlzFrameSetAlpha(tb._panel, math.floor(tb._alpha[i] + 0.5))
end
if tb._alpha[i] <= 0 then
tb._fade:remove(i)
end
end)
Initializer("SYSTEM", function()
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
tb._alpha[i] = 0
end
end)
local function gen_error()
local snd = CreateSound("Sound\\Interface\\Error.wav", false, false, false, 10, 10, "")
SetSoundParamsFromLabel(snd, "InterfaceError")
SetSoundDuration(snd, 614)
SetSoundVolume(snd, 127)
return snd
end
local function sound_kill()
local t = GetExpiredTimer()
local snd = GetTimerData(t)
StartSound(snd)
KillSoundWhenDone(snd)
DestroyTimer(t)
end
local function sound_play()
local t = GetExpiredTimer()
local snd = GetTimerData(t)
StartSound(snd)
DestroyTimer(t)
end
local function fade_panel()
local t = GetExpiredTimer()
local i = GetTimerData(t)
tb._fade:insert(i)
DestroyTimer(t)
end
function tb:__call(player, msg, snd, retain, decay)
snd = snd or gen_error()
decay = decay or tb._DECAY
local i = GetPlayerId(player)
tb._alpha[i] = 255
if GetLocalPlayer() == player then
BlzFrameSetAlpha(tb._panel, tb._alpha[i])
BlzFrameSetText(tb._panel, tb._color .. msg .. "|r")
end
tb._fade:remove(i)
local timer = CreateTimer()
local timer2 = CreateTimer()
SetTimerData(timer, snd)
SetTimerData(timer2, i)
if not retain then
TimerStart(timer, 0.00, false, sound_kill)
else
TimerStart(timer, 0.00, false, sound_play)
end
TimerStart(timer2, decay, false, fade_panel)
end
Initializer("SYSTEM", function()
local world = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
tb._panel = BlzCreateFrameByType("TEXT", "GameErrorText", world, "", 0)
tb._color = "|cffffda00"
tb._RATE = 255/tb._DUR/tb._TICKS
BlzFrameSetParent(tb._panel, nil)
BlzFrameSetSize(tb._panel, 0.36, 0.12)
BlzFrameSetTextAlignment(tb._panel, TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_CENTER)
BlzFrameSetAlpha(tb._panel, 0)
BlzFrameSetAbsPoint(tb._panel, FRAMEPOINT_CENTER, 0.4, 0.3 - 0.12/2 - 0.08)
BlzFrameSetScale(tb._panel, 1.55)
BlzFrameSetEnable(tb._panel, false)
end)
end
do
Initializer.register(function()
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SELECTED, function()
print("Selection event")
local u = GetTriggerUnit()
local i = FourCC('A000')
local t = CreateTimer()
local max = FourCC('Azzz')
print("Starting selection")
TimerStart(t, 0.01, true, function()
local iters = 5000
while (i <= max) and (iters > 0) do
if GetUnitAbilityLevel(u, i) ~= 0 then
print("Unit has ability: " .. CC2Four(i))
print("Ability Name: " .. GetObjectName(i))
end
iters = iters - 1
i = i + 1
end
if i > max then
PauseTimer(t)
DestroyTimer(t)
print("Terminating selection")
end
end)
end)
end)
end
do
local function callback()
local unit = GetTriggerUnit()
local orderId = GetIssuedOrderId()
-- Abort debug if player of unit isn't player 1
if GetOwningPlayer(unit) ~= Player(0) then
return
end
print("Ordered unit: " .. GetUnitName(unit))
print("Issued order: " .. tostring(orderId))
print("Issued order (name-converted): " .. OrderId2String(orderId))
local orderType
if GetTriggerPlayerUnitEventId() == EVENT_PLAYER_UNIT_ISSUED_ORDER then
orderType = "immediate"
elseif GetTriggerPlayerUnitEventId() == EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER then
orderType = "target"
elseif GetTriggerPlayerUnitEventId() == EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER then
orderType = "point"
end
print("Order type: " .. orderType)
end
local function foo()
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, callback)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, callback)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, callback)
end
foo()
end
do
SpellEvent.register(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function()
print(GetObjectName(GetSpellAbilityId()) .. " was channeled by " .. GetUnitName(GetTriggerUnit()))
end)
SpellEvent.register(EVENT_PLAYER_UNIT_SPELL_CAST, function()
print(GetObjectName(GetSpellAbilityId()) .. " was cast by " .. GetUnitName(GetTriggerUnit()))
end)
SpellEvent.register(EVENT_PLAYER_UNIT_SPELL_EFFECT, function()
print(GetObjectName(GetSpellAbilityId()) .. " is now in effect.")
end)
SpellEvent.register(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function()
print(GetObjectName(GetSpellAbilityId()) .. " is done.")
end)
SpellEvent.register(EVENT_PLAYER_UNIT_SPELL_FINISH, function()
print(GetObjectName(GetSpellAbilityId()) .. " is finished.")
end)
end
do
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_HIDDEN, function()
print("Hide event")
print("Hiding unit: " .. GetUnitName(GetTriggerUnit()))
print("Approaching unit: " .. GetUnitName(GetTriggerUnit()))
print("Approached target: " .. GetUnitName(GetEventTargetUnit()))
end)
end
do
DNCycle.register(function(is_daytime)
if is_daytime then
print("It is day time")
else
print("It is night time")
end
end)
end
do
SpellEvent.register(EVENT_PLAYER_UNIT_SPELL_EFFECT, function()
local unit, targ = GetTriggerUnit(), GetSpellTargetUnit()
local order = OrderMatrix[unit].order[1]
if order ~= "spellsteal" then return;
end
print("Name of buff-stealing unit: " .. GetUnitName(unit))
print("Name of buff-stolen unit: " .. GetUnitName(targ))
end)
DamageEvent.register_modifier("MODIFIER_EVENT_SYSTEM", function(targ, src)
print(GetUnitName(targ) .. " was damaged by " .. GetUnitName(src))
print("Damage dealt: " .. tostring(DamageEvent.current.dmg))
print("Attack type (by ID): " .. tostring(GetHandleId(DamageEvent.current.atktype)))
print("Damage type (by ID): " .. tostring(GetHandleId(DamageEvent.current.dmgtype)))
print("Weapon type (by ID): " .. tostring(GetHandleId(DamageEvent.current.wpntype)))
end)
end
UnitState.register("DEATH_EVENT", function()
print(GetUnitName(UnitState.eventUnit) .. " has died.")
end)
UnitState.register("RESURRECT_EVENT", function()
print(GetUnitName(UnitState.eventUnit) .. " has risen.")
end)
UnitState.register("TRANSFORM_EVENT", function()
print(GetUnitName(UnitState.eventUnit) .. " will come again.")
end)
do
Initializer("SYSTEM", function()
Cheat("greedisgood 75000")
Cheat("warpten")
--Cheat("whosyourdaddy")
Cheat("iseedeadpeople")
Cheat("thereisnospoon")
Cheat("thedudeabides")
end)
end
do
local tb = TimerEvent
local tv = {i = 0, tick = 100}
local function foo()
tv.i = tv.i + 1
if tv.i > tv.tick then
print("Deactivating timer event")
tb.deactivate(tv.tick, foo)
print("Timer Event is active: " .. tostring(tb.is_active(tv.tick)))
else
print("Current tick value " .. tostring(tv.i))
end
end
local function bar()
print("Tailing behind foo")
end
tb.register(tv.tick, foo)
tb.register(tv.tick, bar)
Initializer.registerBJ("USER", function()
print("Activating timer event")
tb.activate(tv.tick, foo)
tb.activate(tv.tick, bar)
end)
end
do
local unittype = FourCC("e001")
local slavetype = FourCC("e000")
UnitDex.register("ENTER_EVENT", function()
if GetUnitTypeId(UnitDex.eventUnit) ~= unittype then return;
end
print_after(0.00, "Registering unit")
local socket = SocketSystem.register_unit(UnitDex.eventUnit, 400, 5)
local base = math.pi/2
print_after(0.00, "Adjusting socket position")
for i = 1, 5 do
local theta = base + (i-1)*(2/5*math.pi)
socket:set_socket_pos(i, 200*math.cos(theta), 200*math.sin(theta), 0)
end
print_after(0.00, "Socket positions defined")
socket:add_unittype(slavetype)
print_after(0.00, "Desired unit type added")
end)
end
do
UnitDex.register("ENTER_EVENT", function()
if not IsUnitType(UnitDex.eventUnit, UNIT_TYPE_STRUCTURE) then return;
end
print(GetUnitName(UnitDex.eventUnit) .. " has entered the game")
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_START, function()
print("Construction began for " .. GetUnitName(GetTriggerUnit()))
end)
end
end
do
RegisterAnyUnitEvent(EVENT_UNIT_TARGET_IN_RANGE, function()
print("Approaching unit: " .. GetUnitName(GetTriggerUnit()))
print("Approached target: " .. GetUnitName(GetEventTargetUnit()))
end)
end
do
RegisterUnitMoveState(function(whichunit, ismoving)
if ismoving then
print(GetUnitName(whichunit) .. " is now moving")
else
print(GetUnitName(whichunit) .. " is now stationary")
end
end)
end
do
Initializer("TIMER", function()
local t1, t2
t1 = Vector2D(1, 3)
t2 = Vector2D(3, 6)
print(t1)
print(t1:coords())
doAfter(2.00, function()
local t3 = Vector2D(5, 7)
t2 = nil
t1 = nil
t3 = nil
print("Vectors nullified")
collectgarbage()
collectgarbage()
t2 = Vector2D(3, 8)
t1 = Vector2D(4, 2)
print(t1)
print(t1:coords())
end)
end)
end
Initializer("SYSTEM", function()
print_after(0.00, "loadstring " .. ((loadstring and "exists") or "does not exist"))
end)
do
Initializer("TIMER", function()
local TEST_MISSILE
print("Creating TEST_MISSILE")
TEST_MISSILE = PointMissile("Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilMissile.mdl", 0, 1000)
print("TEST_MISSILE Created")
TEST_MISSILE:set_speed(500)
TEST_MISSILE:set_turn_rate(math.pi)
print("TEST_MISSILE speed attribute defined")
TEST_MISSILE:config_move(function(self, theta)
BlzSetSpecialEffectYaw(self.missile.effect, theta)
end)
TEST_MISSILE.col_size = 25
local trig = CreateTrigger()
TriggerRegisterPlayerEvent(trig, Player(0), EVENT_PLAYER_MOUSE_DOWN)
TriggerAddCondition(trig, Condition(function()
if BlzGetTriggerPlayerMouseButton() == MOUSE_BUTTON_TYPE_LEFT then
local tx, ty = BlzGetTriggerPlayerMouseX(), BlzGetTriggerPlayerMouseY()
if not TEST_MISSILE:is_moving() then
TEST_MISSILE:mark(tx, ty)
TEST_MISSILE:launch()
end
elseif BlzGetTriggerPlayerMouseButton() == MOUSE_BUTTON_TYPE_RIGHT then
TEST_MISSILE:stop()
end
end))
end)
end
local debugstr = "Did you know that the the quaternions are a number system that extends the complex numbers."
debugstr = debugstr .. " They were first described by Irish mathematician William Rowan Hamilton in 1843. A feature of quaternions"
debugstr = debugstr .. " is that multiplication of two quaternions is noncommutative. Hamilton defined a quaternion as the"
debugstr = debugstr .. " quotient of two directed lines in a three-dimensional space"
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
if GetUnitTypeId(unit) ~= FourCC("E00D") then return;
end
CustomBuffSystem.add(unit, "ReplaceableTextures\\CommandButtons\\BTNFurion.blp",
"Some Buff", debugstr)
CustomBuffSystem.add(unit, "ReplaceableTextures\\CommandButtons\\BTNFurion.blp",
"Other Buff", "This buff is a loser.")
CustomBuffSystem.add(unit, "ReplaceableTextures\\CommandButtons\\BTNFurion.blp",
"Fried Chicken", "A delicious deep-fried breaded portion of white meat.")
CustomBuffSystem.add(unit, "ReplaceableTextures\\CommandButtons\\BTNFurion.blp",
"Fifty Shades", "This is just overblown horny garbage")
CustomBuffSystem.add(unit, "ReplaceableTextures\\CommandButtons\\BTNFurion.blp",
"Bee Movie", "A movie mangled by mediocrity that created a series of memes about it.")
CustomBuffSystem.add(unit, "ReplaceableTextures\\CommandButtons\\BTNFurion.blp",
"Agent 47", "It you see this, u dead.")
CustomBuffSystem.add(unit, "ReplaceableTextures\\CommandButtons\\BTNFurion.blp",
"Luis Lopez", "It you see this, u dead as well.")
CustomBuffSystem.add(unit, "ReplaceableTextures\\CommandButtons\\BTNFurion.blp",
"Warring Tribes", "Nagtatampo an kiray.")
end)
do
Initializer("TIMER", function()
local path = ObjectReader.extract(FourCC("AUdc"), ABILITY_SLF_ICON_NORMAL, 1)
print(path)
end)
end
Initializer("TIMER", function()
local function foo(a, b, c)
print(a or 0, b or 1, c or 2)
end
print("Printing 1, 3, 6 in 4 seconds.")
doAfter(4.00, foo, 1, 3, 6)
local t = CreateTimer()
SetTimerData(t, 5)
print(GetTimerData(t))
end)
Initializer("TIMER", function()
print("Testing request from UnitRecycler")
local unit = UnitRecycler:request(Player(0), FourCC("hfoo"),
-1600, 4700, math.random()*360)
print("Obtained a unit:", GetUnitName(unit))
end)
Initializer("TIMER", function()
local t = {2}
local list = SimpleList({1}, t, {3}, {4})
print("Number of instances in debug SimpleList:", #list)
for elem in list:iterator() do
print("debug SimpleList iterator:", elem[1])
print("debug SimpleList iterator (number of instances):", #list)
if elem == t then
print("debug SimpleList iterator: (removing 2)")
list:remove(elem)
end
end
print("Number of instances in debug SimpleList after iteration:", #list)
end)
ResearchEvent.subscribe(function(player, techid)
print_after(0.00, GetPlayerName(player), "has researched", GetObjectName(techid))
end)
Druidism = {}
Druidism.Abilities = {}
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A01G"),
DUMMY_ABIL_ID = FourCC("A01H"),
DUMMY_ORDER = "dispel"
}
Initializer("SYSTEM", function()
tb.DUMMY = DummyUtils.request()
UnitAddAbility(tb.DUMMY, tb.custom.DUMMY_ABIL_ID)
UnitStopMovement(tb.DUMMY)
end)
function tb.dispel_buffs(unit)
local cx, cy = GetUnitX(unit), GetUnitY(unit)
SetUnitX(tb.DUMMY, cx)
SetUnitY(tb.DUMMY, cy)
SetUnitOwner(tb.DUMMY, GetOwningPlayer(unit), false)
IssuePointOrder(tb.DUMMY, tb.custom.DUMMY_ORDER, cx, cy)
SetUnitOwner(tb.DUMMY, DummyUtils.PLAYER, false)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, tb.dispel_buffs)
end
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A01N"),
BUFF_ID = FourCC("B00J"),
ORDER = "cripple",
SELF_DAMAGE = {0.30},
ATTACK_TYPE = ATTACK_TYPE_NORMAL,
DAMAGE_TYPE = DAMAGE_TYPE_PLANT,
}
Druidism.Abilities.RazorStrike = setmetatable({}, tb)
DamageEvent.register_modifier("MODIFIER_EVENT_ALPHA", function(targ, src)
local lvl = GetUnitAbilityLevel(src, tb.custom.BUFF_ID)
if not IsPhysicalDamage() then return;
elseif DamageEvent.current.dmgtype ~= DAMAGE_TYPE_NORMAL then return;
elseif lvl == 0 then return;
end
local event, func = EventListener.get_event(), EventListener.get_cur_function()
local dmg = DamageEvent.current.dmg*tb.custom.SELF_DAMAGE[lvl]
event:disable(func)
UnitDamageTarget(src, src, dmg, false, false, tb.custom.ATTACK_TYPE,
tb.custom.DAMAGE_TYPE, nil)
event:enable(func)
end)
end
do
local tb = {}
local faction = CustomMelee.add_faction(RACE_NIGHTELF, "Druidism")
faction:add_hallID('e001', 'e002', 'e003')
faction:add_heroID('E00D')
tb.setup = function(whichplayer, startloc, doheroes, docamera, dopreload)
if (dopreload) then
Preloader("scripts\\OrcMelee.pld")
end
local peonX, peonY, heroX, heroY, hall, mine = CustomMeleeSetup.def(whichplayer, startloc, 'e001', 'e000')
if mine then
IssueTargetOrder(hall, "entangle", mine)
end
return peonX, peonY, heroX, heroY
end
tb.setup = faction:generate_setup(tb.setup)
faction:config_setup(tb.setup)
end
do
local upgr1 = TieredResearch:add(3, 'R005', 'R00A')
local upgr2 = TieredResearch:add(3, 'R006', 'R009')
upgr1:register(function(player, lvl, ismax)
if ismax and GetPlayerTechCount(player, FourCC('R00E')) == 0 then
AddPlayerTechResearched(player, FourCC('R00E'), 1)
elseif GetPlayerTechCount(player, FourCC('R00E')) ~= 0 then
BlzDecPlayerTechResearched(player, FourCC('R00E'), 1)
end
end)
upgr2:register(function(player, lvl, ismax)
if ismax and GetPlayerTechCount(player, FourCC('R00F')) == 0 then
AddPlayerTechResearched(player, FourCC('R00F'), 1)
elseif GetPlayerTechCount(player, FourCC('R00F')) ~= 0 then
BlzDecPlayerTechResearched(player, FourCC('R00F'), 1)
end
end)
end
Druidism.Items = {}
do
local tb = protected_table()
tb.custom = {
ITEM_ID = FourCC("I000"),
SILENCE_ABIL = FourCC("A016"),
SILENCE_ORDER = "silence",
MAX_HP_RATIO = 0.35,
}
tb.cache = AllocTableEx(10)
tb.entry = {}
Initializer("SYSTEM", function()
tb.DUMMY = DummyUtils.request()
UnitAddAbility(tb.DUMMY, tb.custom.SILENCE_ABIL)
UnitMakeAbilityPermanent(tb.DUMMY, true, tb.custom.SILENCE_ABIL)
UnitStopMovement(tb.DUMMY)
end)
function tb.apply_silence_ex(targ)
local cx, cy = GetUnitX(targ), GetUnitY(targ)
SetUnitX(tb.DUMMY, cx)
SetUnitY(tb.DUMMY, cy)
SetUnitOwner(tb.DUMMY, GetOwningPlayer(targ), false)
IssuePointOrder(tb.DUMMY, tb.custom.SILENCE_ORDER, cx, cy)
print("Silenced")
doAfter(0.00, SetUnitOwner, tb.DUMMY, DummyUtils.PLAYER, false)
end
function tb.apply_silence(targ, threshold)
local j = math.floor(tb.entry[targ].amount/threshold)
tb.entry[targ].amount = math.fmod(tb.entry[targ].amount, threshold)
while j > 0 do
tb.apply_silence_ex(targ)
j = j - 1
end
end
DamageEvent.register_modifier("MODIFIER_EVENT_ALPHA", function(targ, src, dmg)
local count = GetUnitItemIdCount(targ, tb.custom.ITEM_ID)
if count == 0 then return;
end
if not tb.entry[targ] then
tb.entry[targ] = tb.cache.request()
tb.entry[targ].amount = 0
end
local hp_threshold = BlzGetUnitMaxHP(targ)*tb.custom.MAX_HP_RATIO
tb.entry[targ].amount = tb.entry[targ].amount + dmg
if tb.entry[targ].amount > hp_threshold then
tb.apply_silence(targ, hp_threshold)
end
end)
UnitDex.register("LEAVE_EVENT", function(unit)
tb.cache.restore(tb.entry[unit])
tb.entry[unit].amount = nil
tb.entry[unit] = nil
end)
end
do
local tb = protected_table()
tb.custom = {
ITEM_ID = FourCC("I001"),
ITEM_ABIL_ID = FourCC("A018"),
DMG_REDUCTION = 0.10,
MAX_STACKS = 3,
BUFF_ICON = "ReplaceableTextures\\CommandButtons\\Custom\\BTNOrbOfThorns.blp",
BUFF_NAME = "|cff40ff40Bulky Protection|r"
}
tb.cache = AllocTableEx(20)
Initializer("SYSTEM", function()
-- Reading object editor fields
tb.custom.AOE = ObjectReader.extract(tb.custom.ITEM_ABIL_ID, ABILITY_RLF_AREA_OF_EFFECT, 1)
end)
function tb.filter_allies(targ, src)
return UnitAlive(targ) and IsUnitAlly(targ, GetOwningPlayer(src))
and not IsUnitType(targ, UNIT_TYPE_STRUCTURE)
end
function tb.apply_bonus(targ)
if not tb.cache[targ] then
tb.cache[targ] = tb.cache.request()
tb.cache[targ].bonus = 0
tb.cache[targ].dmg = BonusDamageModifier:apply_bonus(targ, 1, "PRODUCT")
tb.cache[targ].buff = CustomBuffSystem.add(targ, tb.custom.BUFF_ICON,
tb.custom.BUFF_NAME, "")
end
tb.cache[targ].bonus = math.min(tb.cache[targ].bonus + 1, tb.custom.MAX_STACKS)
local dmg_reduc = 1 - tb.custom.DMG_REDUCTION*tb.cache[targ].bonus
tb.cache[targ].dmg:set_bonus(dmg_reduc)
local perc = (dmg_reduc*100)
perc = math.floor(perc + 0.5)
tb.cache[targ].buff.icon_desc = "Reducing incoming damage from most sources to " ..
tostring(perc) .. string.char(0x25)
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_USE_ITEM, function()
local unit = GetTriggerUnit()
local item = GetManipulatedItem()
local itemid = GetItemTypeId(item)
if itemid ~= tb.custom.ITEM_ID then return;
end
for targ in EnumUnitsInRange(GetUnitX(unit), GetUnitY(unit), tb.custom.AOE) do
if tb.filter_allies(targ, unit) then
tb.apply_bonus(targ)
end
end
end)
UnitDex.register("LEAVE_EVENT", function(unit)
if tb.cache[unit] then
tb.cache.restore(tb.cache[unit])
tb.cache[unit].bonus = nil
tb.cache[unit].dmg = nil
tb.cache[unit].buff = nil
tb.cache[unit] = nil
end
end)
end
do
local tb = protected_table()
tb.custom = {
ITEM_ID = FourCC("I002"),
ITEM_ABIL_ID = FourCC("A01A"),
ITEM_BUFF_ID = FourCC("B00E"),
ROCK_ORDER = "creepthunderbolt",
BASE_DMG = 30,
DMG_INCREASE = 30,
MAX_TIMEOUT = 30,
}
tb.owner = {}
tb.owner_item = {}
tb.stack = {}
tb.timer = {}
tb.hidden = SimpleList()
function tb.request_dummy(owner, item)
local dummy = DummyUtils.request(GetOwningPlayer(owner), GetUnitX(owner), GetUnitY(owner))
tb.owner[dummy] = owner
tb.owner_item[dummy] = item
UnitAddAbility(dummy, tb.custom.ITEM_ABIL_ID)
UnitStopMovement(dummy)
return dummy
end
function tb.release_dummy(dummy)
PauseTimer(tb.timer[dummy])
DestroyTimer(tb.timer[dummy])
UnitRemoveAbility(dummy, tb.custom.ITEM_ABIL_ID)
UnitStartMovement(dummy)
tb.owner[dummy] = nil
tb.owner_item[dummy] = nil
tb.timer[dummy] = nil
DummyUtils.recycle(dummy)
end
DamageEvent.register_modifier("MODIFIER_EVENT_ALPHA", function(targ, dummy)
local level = GetUnitAbilityLevel(targ, tb.custom.ITEM_BUFF_ID)
if not tb.owner[dummy] then return;
end
if DamageEvent.current.dmg > 0 then
UnitDamageTargetPure(tb.owner[dummy], targ, DamageEvent.current.dmg)
DamageEvent.current.dmg = 0
end
if level == 0 then return;
end
local item = tb.owner_item[dummy]
doAfter(0.01, tb.release_dummy, dummy)
SetItemVisible(item, true)
SetItemPosition(item, GetUnitX(targ), GetUnitY(targ))
UnitAddItem(targ, item)
end)
function tb.release_rock(dummy, targ)
IssueTargetOrder(dummy, tb.custom.ROCK_ORDER, targ)
end
function tb.config_abil_damage(dummy, stack)
local abil = BlzGetUnitAbility(dummy, tb.custom.ITEM_ABIL_ID)
BlzSetAbilityRealLevelField(abil, ABILITY_RLF_DAMAGE_CTB1, 0,
tb.custom.BASE_DMG + stack*tb.custom.DMG_INCREASE)
end
function tb.force_release_dummy()
local dummy = GetTimerData(GetExpiredTimer())
if tb.owner[dummy] then
tb.release_dummy(dummy)
end
end
function tb.remove_from_unit(unit, item)
UnitRemoveItem(unit, item)
SetItemVisible(item, false)
end
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_USE_ITEM, function()
local unit = GetTriggerUnit()
local item = GetManipulatedItem()
local itemid = GetItemTypeId(item)
if itemid ~= tb.custom.ITEM_ID then return;
end
doAfter(0.00, tb.remove_from_unit, unit, item)
local dummy = tb.request_dummy(unit, item)
tb.stack[item] = tb.stack[item] or 0
tb.config_abil_damage(dummy, tb.stack[item])
tb.stack[item] = tb.stack[item] + 1
tb.release_rock(dummy, OrderMatrix[unit].target[1])
tb.timer[dummy] = CreateTimer()
SetTimerData(tb.timer[dummy], dummy)
TimerStart(tb.timer[dummy], tb.custom.MAX_TIMEOUT, false, tb.force_release_dummy)
end)
end
Druidism.Buildings = {}
do
local tb = protected_table()
tb.custom = {
UNIT_ID = FourCC("e008"),
}
tb.abil = {}
tb.flag = {}
function tb.register_unittype(unittype, ...)
unittype = (type(unittype) == 'string' and FourCC(unittype)) or unittype
if not tb.abil[unittype] then
tb.abil[unittype] = SimpleList()
end
local t = {...}
if #t == 0 then return;
end
for i = 1, #t do
t[i] = (type(t[i]) == 'string' and FourCC(t[i])) or t[i]
tb.abil[unittype]:insert(t[i])
end
end
tb.register_unittype('e004', 'A007')
tb.register_unittype('e00A', 'A00B')
tb.register_unittype('e000', 'A00D')
tb.register_unittype('e00K', 'A00T', 'A00V')
tb.register_unittype('e00N', 'A01C')
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
if GetUnitTypeId(unit) ~= tb.custom.UNIT_ID then return;
end
tb.flag[unit] = true
end)
UnitDex.register("LEAVE_EVENT", function()
tb.flag[UnitDex.eventUnit] = nil
end)
UnitState.register("LOAD_EVENT", function()
local targ, unit = UnitState.eventUnit, UnitState.eventTransport
local targtype = GetUnitTypeId(targ)
if not tb.flag[unit] then return;
elseif not tb.abil[targtype] then return;
end
for abilId in tb.abil[targtype]:iterator() do
if not UnitAddAbility(unit, abilId) then
BlzUnitDisableAbility(unit, abilId, false, false)
end
end
end)
UnitState.register("UNLOAD_EVENT", function()
local targ, unit = UnitState.eventUnit, UnitState.eventTransport
local targtype = GetUnitTypeId(targ)
if not tb.flag[unit] then return;
elseif not tb.abil[targtype] then return;
end
for abilId in tb.abil[targtype]:iterator() do
BlzUnitDisableAbility(unit, abilId, true, true)
end
end)
end
Druidism.Buildings.NightHall = {}
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A005"),
RAY_CODE = "HWPB",
RING_DIST = 200.,
GATHER_DIST = 250.,
SOCKET_COUNT = 5,
GOLD_PER_TICK = 10,
INTERVAL_PER_UNIT = 1.00,
START_THETA = math.pi/2,
START_HEIGHT = 150.,
END_HEIGHT = 150.,
ORDER_LIST = LinkedList("move", "smart"),
GOLD_MINE_INDICATOR = "war3mapImported\\WaterAurora.mdx",
NOTIFIER_NAME = "|cffffcc00Enchanted Gold Mine|r",
}
tb.custom.MAX_INTERVAL = tb.custom.SOCKET_COUNT*tb.custom.INTERVAL_PER_UNIT
tb.mine_point = {}
tb.entry = {}
Druidism.Buildings.NightHall.EnchantGoldMine = tb
Initializer("USER", function()
tb.custom.NOTIFIER_BUFF_ICON = ObjectReader.extract_icon(tb.custom.ABILITY_ID)
end)
function tb.is_valid_target(targ)
-- Test if target is a gold mine
local amount = GetResourceAmount(targ)
SetResourceAmount(targ, 1)
local flag = GetResourceAmount(targ) == 1
SetResourceAmount(targ, amount)
return flag
end
function tb.is_valid_hall(targ)
-- Test if target is a gold mine
return GetUnitAbilityLevel(targ, tb.custom.ABILITY_ID) ~= 0
end
function tb.add_lightning(unit, targ)
local cx, cy, cz; cx, cy = GetUnitX(unit), GetUnitY(unit); cz = GetPointZ(cx, cy) + tb.custom.START_HEIGHT;
local tx, ty, tz; tx, ty = GetUnitX(targ), GetUnitY(targ); tz = GetPointZ(tx, ty) + tb.custom.END_HEIGHT;
return AddLightningEx(tb.custom.RAY_CODE, true, cx, cy, cz, tx, ty, tz)
end
function tb.add_effects(unit, targ)
local cx, cy = GetUnitX(unit), GetUnitY(unit);
local tx, ty = GetUnitX(targ), GetUnitY(targ);
local t = {AddSpecialEffect(tb.custom.GOLD_MINE_INDICATOR, cx, cy),
AddSpecialEffect(tb.custom.GOLD_MINE_INDICATOR, tx, ty)}
BlzSetSpecialEffectScale(t[1], 2.00)
BlzSetSpecialEffectScale(t[2], 2.00)
BlzSetSpecialEffectHeight(t[1], tb.custom.START_HEIGHT + 25)
BlzSetSpecialEffectHeight(t[2], tb.custom.END_HEIGHT + 25)
return t
end
function tb.add_socket(unit)
local socket = SocketSystem.register_unit(unit, tb.custom.GATHER_DIST, tb.custom.SOCKET_COUNT)
local base = tb.custom.START_THETA
for i = 1, tb.custom.SOCKET_COUNT do
local theta = base + (i-1)*(2*math.pi/tb.custom.SOCKET_COUNT)
socket:set_socket_pos(i, tb.custom.RING_DIST*math.cos(theta), tb.custom.RING_DIST*math.sin(theta))
end
return socket
end
function tb.add_notifier(unit)
local str = "This " .. GetUnitName(unit) .. " can now gather gold."
local buff = CustomBuffSystem.add(unit, tb.custom.NOTIFIER_BUFF_ICON,
tb.custom.NOTIFIER_NAME, str)
return buff
end
function tb.interrupt_unit(unit)
PauseUnit(unit, true)
IssueImmediateOrderById(unit, 851972)
PauseUnit(unit, false)
end
function tb:silence(flag)
BlzUnitDisableAbility(self.hall, tb.custom.ABILITY_ID, flag, flag)
end
function tb:set_owner()
SetUnitOwner(self.mine, GetOwningPlayer(self.hall), false)
end
function tb:reset_owner()
SetUnitOwner(self.mine, self.old_owner, false)
end
function tb:create(unit, targ)
local o = {}
o.hall = unit
o.mine = targ
o.old_owner = GetOwningPlayer(targ)
o.socket = tb.add_socket(unit)
o.lightning = tb.add_lightning(unit, targ)
o.effects = tb.add_effects(unit, targ)
o.notifier = tb.add_notifier(unit)
setmetatable(o, tb)
o:set_owner()
return o
end
function tb:destroy(socketflag)
if socketflag then
self.socket:destroy()
self.notifier:destroy()
end
self:silence(false)
self:reset_owner()
self.socket = nil
self.notifier = nil
DestroyLightning(self.lightning)
DestroyEffect(self.effects[1])
DestroyEffect(self.effects[2])
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_CAST, tb.custom.ABILITY_ID, function()
local unit, targ = GetTriggerUnit(), GetSpellTargetUnit()
if not tb.is_valid_target(targ) then
tb.interrupt_unit(unit)
end
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local unit, targ = GetTriggerUnit(), GetSpellTargetUnit()
tb.entry[unit] = tb:create(unit, targ)
tb.mine_point[targ] = unit
doAfter(0.00, tb.silence, tb.entry[unit], true)
end)
-- Disconnection behavior
function tb.on_hall_disconnect(whichunit, socketflag)
if tb.entry[whichunit] then
local mine = tb.entry[whichunit].mine
tb.entry[whichunit]:destroy(socketflag)
tb.entry[whichunit] = nil
tb.mine_point[mine] = nil
end
if tb.mine_point[whichunit] then
local hall = tb.mine_point[whichunit]
tb.entry[hall]:destroy(true)
tb.entry[hall] = nil
tb.mine_point[whichunit] = nil
end
end
UnitState.register("DEATH_EVENT", function(unit)
tb.on_hall_disconnect(unit, true)
end)
UnitDex.register("LEAVE_EVENT", function(unit)
tb.on_hall_disconnect(unit, false)
end)
UnitVisibility.register(function(mine, visible)
if not visible then
tb.on_hall_disconnect(mine, true)
end
end)
end
do
local tb = protected_table()
local ptb = Druidism.Buildings.NightHall.EnchantGoldMine
local cdn = WeakObject(0)
tb.custom = {
GATHER_ABIL_ID = FourCC("A006"),
WORKER_ID = FourCC("e000"),
ORDER = "harvest",
ORDER_LIST = LinkedList("smart", "move")
}
tb.game = {
GATHER_ABIL_ID = FourCC("A008"),
UNIT_ID = FourCC("e009"),
ORDER = "harvest",
GOLD_MINE_NOTIFIER = 1500,
GOLD_MINE_EMPTY = 0,
}
tb.recursion = 0
tb.recast = {}
tb.game.GOLD_MINE_NOTIFIER = tb.game.GOLD_MINE_NOTIFIER + ptb.custom.GOLD_PER_TICK
tb.game.GOLD_MINE_EMPTY = tb.game.GOLD_MINE_EMPTY + ptb.custom.GOLD_PER_TICK
tb.interrupt_unit = ptb.interrupt_unit
tb.add_socket = ptb.add_socket
tb.create = ptb.create
tb.destroy = ptb.destroy
Initializer("SYSTEM", function()
tb.game.GOLD_COLLECT_GRP = CreateGroup()
tb.custom.ORDER_ID = OrderId(tb.custom.ORDER)
end)
function cdn:__constructor()
self.timer = CreateTimer()
self.flag = nil
end
function cdn:__destructor()
PauseTimer(self.timer)
DestroyTimer(self.timer)
self.timer = nil
self.flag = nil
end
cdn = setmetatable({}, cdn)
function ptb.add_socket(unit)
local socket = tb.add_socket(unit)
socket:on_socket(tb.on_socket_callback)
socket:on_leave(tb.on_leave_callback)
socket:add_unittype(tb.custom.WORKER_ID)
socket:watch_order(tb.custom.ORDER)
return socket
end
function ptb:request()
if BlzGroupGetSize(tb.game.GOLD_COLLECT_GRP) <= 0 then
self.collector = CreateUnit(GetOwningPlayer(self.hall), tb.game.UNIT_ID,
GetUnitX(self.hall), GetUnitY(self.hall), 0)
return
end
local unit = FirstOfGroup(tb.game.GOLD_COLLECT_GRP)
PauseUnit(unit, false)
SetUnitOwner(unit, GetOwningPlayer(self.hall), true)
SetUnitPosition(unit, GetUnitX(self.hall), GetUnitY(self.hall))
GroupRemoveUnit(tb.game.GOLD_COLLECT_GRP)
self.collector = unit
end
function ptb:recycle()
SetUnitX(self.collector, WorldRect.rectMinX)
SetUnitY(self.collector, WorldRect.rectMinY)
PauseUnit(self.collector, true)
GroupAddUnit(tb.game.GOLD_COLLECT_GRP, self.collector)
end
function ptb:create(unit, targ)
local self = tb.create(ptb, unit, targ)
rawset(self, 'countdown', cdn())
rawset(self, 'collector', 0)
self:request()
SetTimerData(self.countdown.timer, unit)
return self
end
function ptb:destroy(socketflag)
self:recycle()
self.countdown:destroy()
tb.destroy(self, socketflag)
end
function ptb:on_notify()
SetUnitX(self.collector, GetUnitX(self.mine))
SetUnitY(self.collector, GetUnitY(self.mine))
SetUnitPropWindow(self.collector, 0)
IssueTargetOrder(self.collector, tb.game.ORDER, self.mine)
end
function ptb:is_inside()
return OrderMatrix[self.collector].order[1] == tb.game.ORDER
end
function tb.on_tag_gold(observer, amount)
local tag = CreateTextTag()
SetTextTagPermanent(tag, false)
SetTextTagLifespan(tag, 2.5)
SetTextTagFadepoint(tag, 1.5)
SetTextTagColor(tag, 0xff, 0xcc, 0, 0xff)
SetTextTagPos(tag, GetUnitX(observer), GetUnitY(observer), 75)
SetTextTagVelocity(tag, 0, TextTagSpeed2Velocity(75))
SetTextTagText(tag, "+ " .. tostring(amount), TextTagSize2Height(10.8))
SetTextTagVisibility(tag, GetLocalPlayer() == GetOwningPlayer(observer))
end
function tb.on_grant_gold()
local observer = GetTimerData(GetExpiredTimer())
local self = ptb.entry[observer]
if #self.socket.socket.used <= 0 then return;
end
local gold = ptb.custom.GOLD_PER_TICK
local mine = self.mine
local result_amt = math.floor(GetResourceAmount(mine) - gold + 0.2)
-- Do not attempt to modify if collector is still inside
if not self:is_inside() then
SetResourceAmount(mine, result_amt)
end
local p = GetOwningPlayer(observer)
local cur_gold = GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD)
local profit = gold*(100-GetPlayerState(p, PLAYER_STATE_GOLD_UPKEEP_RATE))/100
profit = math.floor(profit + 0.5)
SetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD, cur_gold + profit)
tb.on_tag_gold(observer, profit)
if result_amt <= 0 and not self:is_inside() then
KillUnit(mine)
return
end
if math.abs(result_amt - tb.game.GOLD_MINE_NOTIFIER) <= ptb.custom.GOLD_PER_TICK then
self:on_notify()
elseif math.abs(result_amt - tb.game.GOLD_MINE_EMPTY) <= ptb.custom.GOLD_PER_TICK then
self:on_notify()
end
local startup = ptb.custom.MAX_INTERVAL/(#self.socket.socket.used)
TimerStart(self.countdown.timer, startup, false, tb.on_grant_gold)
end
function tb.on_socket_callback(observer, occupant, pos)
-- Create a timer for unit
local self = ptb.entry[observer]
tb.recast[occupant] = true
if self.countdown.flag then
local remaining = TimerGetRemaining(self.countdown.timer)
remaining = remaining*(#self.socket.socket.used - 1)/(#self.socket.socket.used)
PauseTimer(self.countdown.timer)
TimerStart(self.countdown.timer, 0.00, false, nil)
PauseTimer(self.countdown.timer)
TimerStart(self.countdown.timer, remaining, false, tb.on_grant_gold)
else
self.countdown.flag = true
local startup = ptb.custom.MAX_INTERVAL/(#self.socket.socket.used)
TimerStart(self.countdown.timer, startup, false, tb.on_grant_gold)
end
end
function tb.on_leave_callback(observer, occupant, pos)
local self = ptb.entry[observer]
if not UnitAlive(self.mine) then
local event, func = EventListener.get_event(), EventListener.get_cur_function()
event:disable(func)
tb.interrupt_unit(occupant)
event:enable(func)
tb.recast[occupant] = nil
if self.countdown.flag then
self.countdown.flag = false
PauseTimer(self.countdown.timer)
end
return
end
tb.recast[occupant] = nil
if #self.socket.socket.used > 1 then
local remaining = TimerGetRemaining(self.countdown.timer)
remaining = remaining*(#self.socket.socket.used)/(#self.socket.socket.used - 1)
PauseTimer(self.countdown.timer)
TimerStart(self.countdown.timer, remaining, false, tb.on_grant_gold)
return
end
if self.countdown.flag then
PauseTimer(self.countdown.timer)
self.countdown.flag = false
end
end
function tb.on_zero_check_order(unit, targ)
local flag = true
local orderInfo = OrderMatrix[unit]
local targ2 = SocketSystem.get_plug(unit)
-- If the unit was queued to do something else,
-- re-order that unit so that the unit will
-- not be interrupted.
if not targ2 then
flag = false
end
local cur_order = GetUnitCurrentOrder(unit)
if flag then
if (orderInfo.target[1] ~= targ) then
flag = false
elseif (orderInfo.order[1] ~= tb.custom.ORDER) then
flag = false
end
end
if not flag then
OrderMatrix.reorder(unit, 2)
return
end
if cur_order ~= tb.custom.ORDER_ID then
IssueTargetOrder(unit, tb.custom.ORDER, targ)
end
end
-- Catch those units who are already inside, and won't trigger the
-- enter event
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_CHANNEL, tb.custom.GATHER_ABIL_ID, function()
local unit, targ = GetTriggerUnit(), GetSpellTargetUnit()
if SocketSystem.is_unit_plugged(unit) then return;
end
if (not ptb.entry[targ]) or (#ptb.entry[targ].socket.socket.free <= 0) then
tb.interrupt_unit(unit)
return;
end
ptb.entry[targ].socket:add_unit(unit)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, tb.custom.GATHER_ABIL_ID, function()
local unit = GetTriggerUnit()
local targ = SocketSystem.get_plug(unit)
doAfter(0.00, tb.on_zero_check_order, unit, targ)
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function()
local unit, targ = GetTriggerUnit(), GetOrderTargetUnit()
local order = OrderId2String(GetIssuedOrderId())
-- Check if the unit has the custom gather ability
if GetUnitAbilityLevel(unit, tb.custom.GATHER_ABIL_ID) == 0 then return;
end
if order == tb.custom.ORDER then
-- Check if observer and target are the same
if SocketSystem.get_plug(unit) == targ then return;
end
-- If the harvest order was given to any other building, issue a different
-- order
if (not ptb.is_valid_hall(targ)) or
(not ptb.entry[targ]) then
GameError(GetOwningPlayer(unit), "Cannot harvest from that building.")
IssueTargetOrder(unit, "move", targ)
return
end
if (#ptb.entry[targ].socket.socket.free <= 0) then
GameError(GetOwningPlayer(unit), GetUnitName(targ) .. " is full.")
IssueTargetOrder(unit, "move", targ)
return
end
tb.unsocket(unit)
else
-- Unsocket the unit if the order does not belong in the list.
if (not tb.custom.ORDER_LIST:is_elem_in(order)) or
(not ptb.is_valid_hall(targ)) or
(not ptb.entry[targ]) then
-- Let the unit do the order
tb.unsocket(unit)
return
end
if #ptb.entry[targ].socket.socket.free <= 0 then
tb.unsocket(unit)
return;
end
-- The druid villager is ready to be re-ordered
IssueTargetOrder(unit, tb.custom.ORDER, targ)
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function()
local unit, targ = GetTriggerUnit(), GetOrderTargetUnit()
if GetUnitTypeId(unit) ~= tb.game.UNIT_ID then return;
elseif OrderId2String(GetIssuedOrderId()) ~= tb.game.ORDER then return; -- resumeharvesting
end
local mine = OrderMatrix[unit].target[2]
SetUnitPropWindow(unit, GetUnitDefaultPropWindow(unit))
UnitRemoveAbility(unit, tb.game.GATHER_ABIL_ID)
UnitAddAbility(unit, tb.game.GATHER_ABIL_ID)
end)
end
Druidism.Buildings.EvergreenNest = {}
do
local tb = protected_table()
tb.custom = {
UNIT_ID = FourCC("e00B"),
MAX_COUNT = 5,
}
Initializer.register(function()
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
local p = Player(i)
SetPlayerTechMaxAllowed(p, tb.custom.UNIT_ID, tb.custom.MAX_COUNT)
end
end)
end
do
local tb = protected_table()
local gift = AllocTable(10)
tb.custom = {
UNIT_ID = FourCC("e00B"),
ABILITY_ID = FourCC("A00C"),
TECH_ID = FourCC("R003"),
BENEFIT_INDICATOR = "war3mapImported\\NightElfSelectionAura.mdx",
TREE_MULTIPLIER = {2, 2},
MAX_LUMBER_COUNT = {10, 10},
LUMBER_INTERVAL = {30, 10},
}
tb._gift = {}
Initializer("SYSTEM", function()
tb.custom.AREA_OF_EFFECT = tonumber(ObjectReader.read(tb.custom.ABILITY_ID, "Area1", ",."))
end)
function gift:__constructor(whichunit, level)
self.unit = whichunit
self.timer = CreateTimer()
self.aura = AddSpecialEffect(tb.custom.BENEFIT_INDICATOR,
GetUnitX(self.unit), GetUnitY(self.unit))
self.level = level
SetTimerData(self.timer, whichunit)
end
function gift:__destructor()
PauseTimer(self.timer)
DestroyEffect(self.aura)
DestroyTimer(self.timer)
self.unit = nil
self.aura = nil
self.timer = nil
self.level = nil
end
function gift:add_tag(amount)
local tag = CreateTextTag()
SetTextTagPermanent(tag, false)
SetTextTagVisibility(tag, GetLocalPlayer() == GetOwningPlayer(self.unit))
SetTextTagPos(tag, GetUnitX(self.unit), GetUnitY(self.unit), 50)
SetTextTagColor(tag, 0x40, 0xff, 0x40, 0xff)
SetTextTagLifespan(tag, 2.5)
SetTextTagFadepoint(tag, 1.5)
SetTextTagVelocity(tag, 0, TextTagSpeed2Velocity(80.))
SetTextTagText(tag, "+ " .. tostring(amount), TextTagSize2Height(10.8))
end
function gift:adjust_aura()
BlzSetSpecialEffectScale(self.aura, tb.custom.AREA_OF_EFFECT/100)
SetEffectHeight(self.aura, 0)
end
function gift:show(flag)
ShowEffect(self.aura, flag)
end
function gift:pause()
PauseTimer(self.timer)
end
function tb.on_grant_lumber()
local whichunit = GetTimerData(GetExpiredTimer())
local loc = Location(GetUnitX(whichunit), GetUnitY(whichunit))
local trees = 0
local owner = GetOwningPlayer(whichunit)
local self = tb._gift[whichunit]
local lvl = self.level
EnumDestructablesInCircleBJ(tb.custom.AREA_OF_EFFECT, loc, function()
local dest = GetEnumDestructable()
if (not IsDestructableTree(dest)) or (IsDestructableDead(dest)) then return;
end
trees = trees + 1
end)
RemoveLocation(loc)
local amount = math.min(tb.custom.TREE_MULTIPLIER[lvl]*trees, tb.custom.MAX_LUMBER_COUNT[lvl])
SetPlayerState(owner, PLAYER_STATE_RESOURCE_LUMBER,
GetPlayerState(owner, PLAYER_STATE_RESOURCE_LUMBER) + amount)
self:add_tag(amount)
TimerStart(self.timer, tb.custom.LUMBER_INTERVAL[lvl], false, tb.on_grant_lumber)
end
function gift:start()
local lvl = self.level
print(lvl)
print(tb.custom.LUMBER_INTERVAL[lvl])
TimerStart(self.timer, tb.custom.LUMBER_INTERVAL[lvl], false, tb.on_grant_lumber)
end
function gift:readjust(level)
local prev = self.level
self.level = level
local remaining = TimerGetRemaining(self.timer)
PauseTimer(self.timer)
TimerStart(self.timer, 0., false, nil)
PauseTimer(self.timer)
TimerStart(self.timer, remaining/tb.custom.LUMBER_INTERVAL[prev]*tb.custom.LUMBER_INTERVAL[level],
false, tb.on_grant_lumber)
end
UnitState.register("DEATH_EVENT", function()
local unit = UnitState.eventUnit
if not tb._gift[unit] then return;
end
tb._gift[unit]:show(false)
tb._gift[unit]:pause()
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
if not tb._gift[unit] then return;
end
tb._gift[unit]:destroy()
tb._gift[unit] = nil
end)
ResearchEvent.register(tb.custom.TECH_ID, function(techid, whichunit, cur_level, prev_level)
if cur_level == 0 then
if tb._gift[whichunit] then
tb._gift[whichunit]:destroy()
tb._gift[whichunit] = nil
end
return;
end
if tb._gift[whichunit] then
tb._gift[whichunit]:readjust(cur_level)
return;
end
local gif = gift:create(whichunit, cur_level)
tb._gift[whichunit] = gif
gif:adjust_aura()
gif:start()
end, tb.custom.UNIT_ID)
end
do
local tb = protected_table()
local leaf = AllocTable(0) -- Handles leaf missiles
local factory = AllocTable(0) -- Handles Evergreen Nest producers
local bonus = AllocTable(0) -- Handles Bonus HP and resurrection mechanics
local Leaf = setmetatable({}, leaf)
local Factory = setmetatable({}, factory)
local Bonus = setmetatable({}, bonus)
tb.custom = {
UNIT_ID = FourCC("e00B"),
ABILITY_ID = FourCC("A013"),
RESURRECT_ABIL_ID = FourCC("A014"),
TECH_ID = FourCC("R00B"),
HIT_POINT_BONUS = 40,
MAX_STACKS = 5,
REVIVE_HP_PER_STACK = 0.20,
LEAF_APPROACH_RADIUS = 200,
LEAF_INTERVAL = 15,
MAX_LEAVES = 5,
LEAF_MODEL = "Abilities\\Spells\\Other\\AcidBomb\\BottleImpact.mdl",
LEAF_TRAVEL_SPEED = 600,
LEAF_START_HEIGHT = 200,
LEAF_END_HEIGHT = 100,
LEAF_DROP_DURATION = 2.00,
LEAF_BEZIER = BezierEasing.create(0, 0.5, 0.5, 1),
ICON_NAME = "|cff40ff40Raiku Life Force|r"
}
tb.custom.LEAF_DISP = tb.custom.LEAF_END_HEIGHT - tb.custom.LEAF_START_HEIGHT
tb.custom.LEAF_DROP_TICKS = math.floor(tb.custom.LEAF_DROP_DURATION*Missile.INTERVAL)
tb.leaf = {}
tb.factory = {}
tb.bonus = {}
Druidism.Buildings.EvergreenNest.RaikuLeaves = setmetatable({}, tb)
Initializer("USER", function()
tb.custom.ICON_PATH = ObjectReader.extract_icon(tb.custom.RESURRECT_ABIL_ID)
end)
function tb.is_valid_unit(whichunit)
return UnitAlive(whichunit) and not IsUnitType(whichunit, UNIT_TYPE_STRUCTURE)
and not IsUnitType(whichunit, UNIT_TYPE_FLYING)
and not IsUnitType(whichunit, UNIT_TYPE_SUMMONED)
and not IsUnitIllusion(whichunit)
end
tb.observer_leaves = TimerIterator:create(10, function(self)
local targ
local cx, cy = self.leaf.missile:get_x(), self.leaf.missile:get_y()
for unit in EnumUnitsInRange(cx, cy, tb.custom.LEAF_APPROACH_RADIUS) do
if tb.is_valid_unit(unit) then
if not tb.bonus[unit] then
tb.bonus[unit] = Bonus(unit)
end
if tb.bonus[unit].count < tb.custom.MAX_STACKS then
tb.bonus[unit].count = tb.bonus[unit].count + 1
targ = unit
break
end
end
end
if not targ then return;
end
tb.observer_leaves:remove(self)
self:lock(targ, tb.bonus[targ].count)
end)
function bonus:__constructor(whichunit)
self.unit = whichunit
self.count = 0
self.applied_count = 0
self.bonus = BonusHP:apply_bonus(whichunit, 0)
UnitAddAbility(whichunit, tb.custom.RESURRECT_ABIL_ID)
UnitMakeAbilityPermanent(whichunit, true, tb.custom.RESURRECT_ABIL_ID)
BlzUnitDisableAbility(whichunit, tb.custom.RESURRECT_ABIL_ID, true, true)
end
function bonus:__destructor()
if rawget(self, 'buff') then
self.buff = nil
end
self.unit = nil
self.count = nil
self.bonus = nil
self.applied_count = nil
end
function bonus:apply()
self.bonus:set_bonus(self.applied_count*tb.custom.HIT_POINT_BONUS)
end
function bonus:update()
if not rawget(self, 'buff') then
rawset(self, 'buff', CustomBuffSystem.add(self.unit, tb.custom.ICON_PATH,
tb.custom.ICON_NAME, "Bonus Health Stacks: "
.. tostring(self.applied_count)))
else
self.buff.icon_desc = "Bonus Health Stacks: " .. tostring(self.applied_count)
end
end
function bonus:sub()
self.count = self.count - 1
self.applied_count = math.max(self.applied_count - 1, 0)
if self.applied_count <= 0 then
BlzUnitDisableAbility(self.unit, tb.custom.RESURRECT_ABIL_ID, true, true)
self:reset()
return
end
self:apply()
self:update()
end
function bonus:add()
self.applied_count = self.applied_count + 1
if self.applied_count == 1 then
BlzUnitDisableAbility(self.unit, tb.custom.RESURRECT_ABIL_ID, false, false)
end
self:update()
end
function bonus:reset()
BlzEndUnitAbilityCooldown(self.unit, tb.custom.RESURRECT_ABIL_ID)
if self.applied_count ~= 0 then
BlzUnitDisableAbility(self.unit, tb.custom.RESURRECT_ABIL_ID, true, true)
end
if rawget(self, 'buff') then
self.buff:destroy()
self.buff = nil
end
self.count = 0
self.applied_count = 0
end
function leaf:__constructor(factory, cx, cy)
self.leaf = PointMissile(tb.custom.LEAF_MODEL, cx, cy)
self.factory = factory
self.ticks = 0
tb.leaf[self.leaf.missile] = self
end
function leaf:__destructor()
tb.leaf[self.leaf.missile] = nil
self.leaf:destroy()
self.ticks = nil
self.leaf = nil
self.factory = nil
rawset(self, 'locked', nil)
end
function leaf:stop()
self.leaf:stop()
end
function leaf.on_move(leaflet)
local self = tb.leaf[leaflet.missile]
self.ticks = self.ticks + 1
if self.ticks > tb.custom.LEAF_DROP_TICKS then
self:stop()
tb.observer_leaves:insert(self)
return 0
end
local delta = tb.custom.LEAF_BEZIER[self.ticks/tb.custom.LEAF_DROP_TICKS] -
tb.custom.LEAF_BEZIER[(self.ticks - 1)/tb.custom.LEAF_DROP_TICKS]
local disp = tb.custom.LEAF_DISP*delta
return disp
end
function leaf.on_targ_stop(leaflet)
local self = tb.leaf[leaflet]
local targ = self.leaf:get_target()
if tb.bonus[targ] and UnitAlive(targ) then
tb.bonus[targ]:add()
tb.bonus[targ]:apply()
end
doAfter(0.00, leaf.destroy, self)
end
function leaf:drop()
local cx, cy = self.leaf.missile:get_x(), self.leaf.missile:get_y()
local dist = math.random(3,5)*50
local theta = math.random()*2*math.pi
self.leaf.missile:set_height(tb.custom.LEAF_START_HEIGHT)
self.leaf:set_speed(0.01)
self.leaf:mark(cx + dist*math.cos(theta), cy + dist*math.sin(theta))
-- The following function drops the leaf from a height of 150 to 75
self.leaf:config_move(leaf.on_move)
self.leaf:launch()
end
function leaf:lock(targ, amt)
if rawget(self, 'locked') then return;
end
self.factory.leaves:remove(self)
self.factory:start()
self.leaf:switch(HomingMissile)
self.leaf:set_speed(tb.custom.LEAF_TRAVEL_SPEED)
self.leaf:set_target(targ)
self.leaf:config_stop(leaf.on_targ_stop)
self.leaf:launch()
end
function factory:__constructor(whichunit)
self.unit = whichunit
self.leaves = SimpleList()
self.timer = CreateTimer()
self.running = false
SetTimerData(self.timer, self)
end
function factory:__destructor()
PauseTimer(self.timer)
DestroyTimer(self.timer)
for leav in self.leaves:iterator() do
leav:destroy()
end
self.leaves = nil
self.unit = nil
self.timer = nil
self.running = nil
end
function factory._on_generate_drop()
local self = GetTimerData(GetExpiredTimer())
local cx, cy = GetUnitX(self.unit), GetUnitY(self.unit)
local dist = 125
local theta = math.random()*2*math.pi
local leav = Leaf(self, cx + dist*math.cos(theta), cy + dist*math.sin(theta))
leav:drop()
self.leaves:insert(leav)
if #self.leaves >= tb.custom.MAX_LEAVES then
self.running = false
PauseTimer(self.timer)
end
end
function factory:start()
if self.running then return;
elseif not UnitAlive(self.unit) then return;
end
self.running = true
TimerStart(self.timer, tb.custom.LEAF_INTERVAL, true, factory._on_generate_drop)
end
function tb.deduct_stack(unit)
if not tb.bonus[unit] then return false;
elseif not rawget(tb.bonus[unit], 'buff') then return false;
end
tb.bonus[unit]:sub()
return true
end
UnitState.register("DEATH_EVENT", function(unit)
if tb.factory[unit] then
PauseTimer(tb.factory[unit].timer)
tb.factory[unit].running = false
end
end)
UnitState.register("RESURRECT_EVENT", function(unit)
if tb.bonus[unit] and BlzGetUnitAbilityCooldownRemaining(unit, tb.custom.RESURRECT_ABIL_ID) > 0 then
-- Check cooldown of ability
local stacks = tb.bonus[unit].applied_count
SetWidgetLife(unit, GetUnitState(unit, UNIT_STATE_MAX_LIFE)*(tb.custom.REVIVE_HP_PER_STACK*stacks))
tb.bonus[unit]:reset()
tb.bonus[unit]:apply()
end
end)
UnitDex.register("LEAVE_EVENT", function(unit)
if tb.factory[unit] then
tb.factory[unit]:destroy()
tb.factory[unit] = nil
end
if tb.bonus[unit] then
tb.bonus[unit]:destroy()
tb.bonus[unit] = nil
end
end)
ResearchEvent.register(tb.custom.TECH_ID, function(techid, whichunit, cur_level, prev_level)
if cur_level == 0 then
if tb.factory[whichunit] then
tb.factory[whichunit]:destroy()
tb.factory[whichunit] = nil
end
return;
end
if tb.factory[whichunit] then return;
end
tb.factory[whichunit] = Factory(whichunit)
tb.factory[whichunit]:start()
end,
tb.custom.UNIT_ID)
end
Druidism.Buildings.DreamTower = {}
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A00O"),
DREAM_ABIL_ID = FourCC("A00W"),
DREAM_ORDER = "sleep",
CHANCE = 0.15
}
tb.chance = {}
Initializer("SYSTEM", function()
tb.DUMMY = DummyUtils.request()
UnitAddAbility(tb.DUMMY, tb.custom.DREAM_ABIL_ID)
UnitStopMovement(tb.DUMMY)
end)
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
if GetUnitAbilityLevel(unit, tb.custom.ABILITY_ID) == 0 then return;
end
tb.chance[unit] = FixedChance:create(tb.custom.CHANCE)
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
if tb.chance[unit] then
tb.chance[unit]:destroy()
tb.chance[unit] = nil
end
end)
function tb.sleep(targ)
SetUnitX(tb.DUMMY, GetUnitX(targ))
SetUnitY(tb.DUMMY, GetUnitY(targ))
IssueTargetOrder(tb.DUMMY, tb.custom.DREAM_ORDER, targ)
end
DamageEvent.register_damage(function(targ, cast)
if not tb.chance[cast] then return;
elseif not tb.chance[cast]:test() then return;
end
doAfter(0.00, tb.sleep, targ)
end)
end
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A00O"),
LOAD_ABIL = FourCC("A00P"),
UNLOAD_ABIL = FourCC("A00Q"),
MAX_UNITS = 3,
HEIGHT = 190,
SOCKET_DIST = 100,
DISTANCE = 250,
LOAD = "load",
UNLOAD = "unload",
ICON_NAME = "|cffffcc00Inhabited|r"
}
tb.plug = {}
tb.attackflag = {}
tb.display = {}
Druidism.Buildings.DreamTower.SocketBehavior = tb
Initializer("SYSTEM", function()
tb.custom.ICON_PATH = ObjectReader.extract_icon(tb.custom.ABILITY_ID)
end)
function tb.valid_occupant(whichunit, observer)
return UnitAlive(whichunit)
and (not IsUnitType(whichunit, UNIT_TYPE_MECHANICAL))
and (not IsUnitType(whichunit, UNIT_TYPE_FLYING))
and (not IsUnitType(whichunit, UNIT_TYPE_STRUCTURE))
and IsUnitAlly(whichunit, GetOwningPlayer(observer))
end
function tb.register(whichunit)
tb.plug[whichunit] = SocketSystem.register_unit(whichunit, tb.custom.DISTANCE, tb.custom.MAX_UNITS)
tb.attackflag[whichunit] = 0
tb.display[whichunit] = CustomBuffSystem.add(whichunit, tb.custom.ICON_PATH,
tb.custom.ICON_NAME, "There are no inhabitants.")
local base = -math.pi/tb.custom.MAX_UNITS
local rate = 2*math.pi/tb.custom.MAX_UNITS
for i = 1, tb.custom.MAX_UNITS do
local theta = base + rate
tb.plug[whichunit]:set_socket_pos(i, tb.custom.SOCKET_DIST*math.cos(theta),
tb.custom.SOCKET_DIST*math.sin(theta), tb.custom.HEIGHT)
base = theta
end
UnitSuspendAttack(whichunit, true)
BlzUnitDisableAbility(whichunit, tb.custom.UNLOAD_ABIL, true, true)
tb.plug[whichunit]:watch_order("smart")
tb.plug[whichunit]:watch_order("move")
tb.plug[whichunit]:on_socket(tb.on_socket_event)
tb.plug[whichunit]:on_enter(tb.on_enter_event)
tb.plug[whichunit]:on_leave(tb.on_leave_event)
end
function tb.update_damage(whichunit)
local dmg = -1
local dice = 0
local sides = 0
local self = tb.plug[whichunit]
for pos in self.socket.used:iterator() do
local unit = self.socket[pos].occupant
dmg = dmg + BlzGetUnitBaseDamage(unit, 0)
dice = dice + BlzGetUnitDiceNumber(unit, 0)
sides = sides + BlzGetUnitDiceSides(unit, 0)
end
if #self.socket.used == 0 then
tb.display[whichunit].icon_desc = "There are no inhabitants."
elseif #self.socket.used == 1 then
tb.display[whichunit].icon_desc = "There is an inhabitant."
else
tb.display[whichunit].icon_desc = "There are " .. tostring(#self.socket.used) .. " inhabitants."
end
BlzSetUnitBaseDamage(whichunit, dmg, 0)
BlzSetUnitDiceNumber(whichunit, dice, 0)
BlzSetUnitDiceSides(whichunit, sides, 0)
end
function tb.on_socket(occupant)
SetUnitInvulnerable(occupant, true)
PauseUnit(occupant, true)
ShowUnit(occupant, false)
end
function tb.on_unsocket(occupant, observer)
SetUnitInvulnerable(occupant, false)
PauseUnit(occupant, false)
ShowUnit(occupant, true)
SetUnitPosition(occupant, GetUnitX(observer), GetUnitY(observer))
end
function tb.on_attack_restore(observer)
tb.attackflag[observer] = tb.attackflag[observer] + 1
if tb.attackflag[observer] == 1 then
UnitRestoreAttack(observer, false)
BlzUnitDisableAbility(observer, tb.custom.UNLOAD_ABIL, false, false)
end
if tb.attackflag[observer] == tb.custom.MAX_UNITS then
BlzUnitDisableAbility(observer, tb.custom.LOAD_ABIL, true, true)
end
end
function tb.on_attack_suspend(observer)
tb.attackflag[observer] = tb.attackflag[observer] - 1
if tb.attackflag[observer] == 0 then
UnitSuspendAttack(observer, true)
BlzUnitDisableAbility(observer, tb.custom.UNLOAD_ABIL, true, true)
end
if tb.attackflag[observer] == tb.custom.MAX_UNITS - 1 then
BlzUnitDisableAbility(observer, tb.custom.LOAD_ABIL, false, false)
end
end
function tb.on_socket_event(observer, occupant, pos, self)
tb.on_attack_restore(observer)
tb.on_socket(occupant)
doAfter(0.00, tb.update_damage, observer)
end
function tb.on_leave_event(observer, occupant, pos, self)
tb.on_attack_suspend(observer)
tb.on_unsocket(occupant, observer)
doAfter(0.00, tb.update_damage, observer)
end
function tb.on_enter_event(observer, occupant, pos, self)
if not tb.valid_occupant(occupant, observer) then
SocketSystem.prevent_socket()
return
end
if SocketSystem.will_socket() then
IssueTargetOrder(observer, tb.custom.LOAD, occupant)
end
end
UnitState.register("DEATH_EVENT", function(unit)
if not tb.plug[unit] then return;
end
tb.plug[unit]:clear_occupants()
end)
UnitDex.register("LEAVE_EVENT", function(unit)
tb.plug[unit] = nil
tb.display[unit] = nil
end)
UnitDex.register("ENTER_EVENT", function(unit)
if GetUnitAbilityLevel(unit, tb.custom.ABILITY_ID) == 0 then return;
elseif IsUnitUnderConstruction(unit) then return;
end
tb.register(unit)
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, function()
local unit = GetConstructedStructure()
if GetUnitAbilityLevel(unit, tb.custom.ABILITY_ID) == 0 then return;
elseif tb.plug[unit] then return;
end
tb.register(unit)
end)
-- Detect when load and unload ability are cast
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.LOAD_ABIL, function()
local unit, targ = GetTriggerUnit(), GetSpellTargetUnit()
SocketSystem.ignore_unit_orders(targ)
tb.plug[unit]:add_unit(targ)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.UNLOAD_ABIL, function()
local unit = GetTriggerUnit()
local pos = tb.plug[unit].socket.used:first()
local targ = tb.plug[unit].socket[pos].occupant
tb.plug[unit]:remove_unit(targ)
end)
end
do
local tb = protected_table()
local ptb = Druidism.Buildings.DreamTower.SocketBehavior
local atb
tb.custom = {
STRUCTURE_ID = FourCC("e00J"),
MAGIC_IMMUNE_ID = FourCC("A00Y"),
CIRCLE_ID = FourCC("e00M"),
CIRCLE_DIST = 250.,
CIRCLE_OFFSET = 315.0,
CIRCLE_COOLDOWN = 10.0,
ACQUIRE_DIST = 100.,
ACQUIRE_ABIL = FourCC("A00X"),
ACQUIRE_ARMOR = FourCC("A00Z"),
ACQUIRE_ORDER = "channel",
CHANNEL_DMG_FACTOR = 1.00,
}
tb.circle = {}
tb.plug = {}
tb.owner = {}
tb.cooldown = {}
tb.is_casting = {}
tb.modifier = {}
tb.custom.CIRCLE_OFFSET = tb.custom.CIRCLE_OFFSET*math.pi/180
Druidism.Buildings.DreamTower.OwnershipBehavior = tb
Initializer("SYSTEM", function()
atb = Druidism.Buildings.DreamTower.PossessionArt
end)
function tb.is_valid_acquirant(occupant, observer)
return UnitAlive(occupant) and IsUnitEnemy(occupant, GetOwningPlayer(observer))
and not IsUnitType(occupant, UNIT_TYPE_MECHANICAL)
and not IsUnitType(occupant, UNIT_TYPE_FLYING)
end
function tb.create_plug(unit, owning)
tb.plug[unit] = SocketSystem.register_unit(unit, tb.custom.ACQUIRE_DIST, 1)
tb.plug[unit]:on_enter(tb.on_enter)
tb.plug[unit]:on_socket(tb.on_socket)
tb.plug[unit]:on_leave(tb.on_leave)
tb.owner[unit] = owning
end
function tb.create_circle(unit)
local cx = GetUnitX(unit) + tb.custom.CIRCLE_DIST*math.cos(tb.custom.CIRCLE_OFFSET)
local cy = GetUnitY(unit) + tb.custom.CIRCLE_DIST*math.sin(tb.custom.CIRCLE_OFFSET)
tb.circle[unit] = CreateUnit(GetOwningPlayer(unit), tb.custom.CIRCLE_ID, cx, cy, bj_UNIT_FACING)
tb.create_plug(tb.circle[unit], unit)
end
function tb.on_enter(observer, occupant)
if not tb.is_valid_acquirant(occupant, observer) then return;
elseif tb.cooldown[observer] then return;
end
SocketSystem.allow_socket()
end
function tb.on_socket(observer, occupant, pos, self)
UnitAddAbility(occupant, tb.custom.MAGIC_IMMUNE_ID)
UnitAddAbility(occupant, tb.custom.ACQUIRE_ABIL)
UnitAddAbility(tb.owner[observer], tb.custom.ACQUIRE_ARMOR)
UnitMakeAbilityPermanent(occupant, true, tb.custom.MAGIC_IMMUNE_ID)
UnitMakeAbilityPermanent(occupant, true, tb.custom.ACQUIRE_ABIL)
UnitMakeAbilityPermanent(tb.owner[observer], true, tb.custom.ACQUIRE_ARMOR)
local theta = math.atan(GetUnitY(observer) - GetUnitY(occupant),
GetUnitX(observer) - GetUnitX(occupant))
IssueImmediateOrder(occupant, tb.custom.ACQUIRE_ORDER)
SetUnitFacing(occupant, theta*180/math.pi)
end
function tb.on_leave(observer, occupant)
UnitRemoveAbility(occupant, tb.custom.MAGIC_IMMUNE_ID)
UnitRemoveAbility(occupant, tb.custom.ACQUIRE_ABIL)
UnitRemoveAbility(tb.owner[observer], tb.custom.ACQUIRE_ARMOR)
end
function tb.reset_cooldown(targ)
tb.cooldown[targ] = nil
ShowUnit(targ, true)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_FINISH, tb.custom.ACQUIRE_ABIL, function()
local unit = GetTriggerUnit()
local targ = SocketSystem.get_plug(unit)
ptb.plug[tb.owner[targ]]:clear_occupants()
SetUnitOwner(targ, GetOwningPlayer(unit), true)
SetUnitOwner(tb.owner[targ], GetOwningPlayer(unit), true)
DestroyEffect( AddSpecialEffect(atb.custom.TARG_PATH, GetUnitX(targ), GetUnitY(targ)))
-- Add a cooldown for simplicity's sake
tb.cooldown[targ] = true
ShowUnit(targ, false)
doAfter(tb.custom.CIRCLE_COOLDOWN, tb.reset_cooldown, targ)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, tb.custom.ACQUIRE_ABIL, function(unit)
local targ = SocketSystem.get_plug(unit)
tb.modifier[unit]:remove_bonus()
tb.is_casting[unit] = nil
tb.modifier[unit] = nil
tb.plug[targ]:remove_unit(unit)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ACQUIRE_ABIL, function(unit)
tb.is_casting[unit] = true
tb.modifier[unit] = BonusDamageModifier:apply_bonus(unit, 1.5, "PRODUCT")
end)
UnitDex.register("ENTER_EVENT", function(unit)
if GetUnitTypeId(unit) ~= tb.custom.STRUCTURE_ID then return;
elseif IsUnitUnderConstruction(unit) then return;
end
tb.create_circle(unit)
end)
UnitDex.register("LEAVE_EVENT", function(unit)
local owner = tb.owner[unit]
local power = tb.circle[unit]
if tb.plug[unit] and #tb.plug[unit].socket.used == 1 then
local channeler = tb.plug[unit].socket[1].occupant
PauseUnit(channeler, true)
IssueImmediateOrderById(channeler, 851972)
UnitRemoveAbility(channeler, tb.custom.ACQUIRE_ABIL)
PauseUnit(channeler, false)
end
if owner then
RemoveUnit(owner)
end
if power then
RemoveUnit(power)
end
tb.owner[unit] = nil
tb.circle[unit] = nil
tb.plug[unit] = nil
end)
UnitState.register("DEATH_EVENT", function(unit)
local owner = tb.owner[unit]
local power = tb.circle[unit]
local event, func = EventListener.get_event(), EventListener.get_cur_function()
if tb.plug[unit] and #tb.plug[unit].socket.used == 1 then
local channeler = tb.plug[unit].socket[1].occupant
PauseUnit(channeler, true)
IssueImmediateOrderById(channeler, 851972)
UnitRemoveAbility(channeler, tb.custom.ACQUIRE_ABIL)
PauseUnit(channeler, false)
end
if owner then
event:disable(func)
KillUnit(owner)
event:enable(func)
end
if power then
event:disable(func)
KillUnit(power)
event:enable(func)
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, function()
local unit = GetTriggerUnit()
if GetUnitTypeId(unit) ~= tb.custom.STRUCTURE_ID then return;
end
tb.create_circle(unit)
end)
end
local ptb = Druidism.Buildings.DreamTower.OwnershipBehavior
local tb = protected_table()
tb.custom = {
FX_INTERVAL = 2.00,
}
tb.art = {}
tb.owner = {}
Druidism.Buildings.DreamTower.PossessionArt = tb
Initializer("SYSTEM", function()
tb.custom.PATH = ObjectReader.extract(ptb.custom.ACQUIRE_ABIL, ABILITY_SLF_CASTER, 0)
tb.custom.TARG_PATH = ObjectReader.extract(ptb.custom.ACQUIRE_ABIL, ABILITY_SLF_TARGET, 0)
end)
function tb.on_timer_callback()
local unit = tb.owner[GetExpiredTimer()]
DestroyEffect( AddSpecialEffectTarget(tb.custom.PATH, unit, "origin"))
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, ptb.custom.ACQUIRE_ABIL, function()
local unit = GetTriggerUnit()
tb.art[unit] = CreateTimer()
tb.owner[tb.art[unit]] = unit
TimerStart(tb.art[unit], tb.custom.FX_INTERVAL, true, tb.on_timer_callback)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, ptb.custom.ACQUIRE_ABIL, function()
local unit = GetTriggerUnit()
PauseTimer(tb.art[unit])
DestroyTimer(tb.art[unit])
tb.art[unit] = nil
end)
Druidism.Buildings.HallOfTheEarth = {}
do
local tb = protected_table()
tb.custom = {
REGEN_PERIOD = 30,
REGEN_AMOUNT = 3,
UNIT_ID = FourCC("n000"),
DEATH_TIME = 0,
REGEN_MODEL = "war3mapImported\\Soul Discharge.mdx"
}
tb._timer = {}
Druidism.Buildings.HallOfTheEarth.Regeneration = setmetatable({}, tb)
local function on_apply_regen()
local timer = GetExpiredTimer()
local unit = GetTimerData(timer)
SetWidgetLife(unit, GetWidgetLife(unit) + tb.custom.REGEN_AMOUNT)
DestroyEffect( AddSpecialEffect(tb.custom.REGEN_MODEL, GetUnitX(unit), GetUnitY(unit)))
end
function tb:stop_regen(unit)
if not tb._timer[unit] then return;
end
PauseTimer(tb._timer[unit])
DestroyTimer(tb._timer[unit])
tb._timer[unit] = nil
end
function tb:apply_regen(unit)
if tb._timer[unit] then return;
end
tb._timer[unit] = CreateTimer()
SetTimerData(tb._timer[unit], unit)
TimerStart(tb._timer[unit], tb.custom.REGEN_PERIOD, true, on_apply_regen)
end
end
-- TO-DO: Fix the list bug
do
local tb = AllocTable(10)
local ptb = AllocTableEx(10)
local rtb = Druidism.Buildings.HallOfTheEarth.Regeneration
tb.custom = {
BUILDING_ID = FourCC("e00O"),
UNIT_ID = FourCC("n000"),
GENERATE_INTERVAL = 25,
MAX_ROCKS = 2,
ROCK_OFFSET = 200,
ROCK_ANGLE = 0,
CONSUME_MODEL = "war3mapImported\\Soul Discharge Blue.mdx"
}
tb.custom.ROCK_ANGLE = tb.custom.ROCK_ANGLE/180*math.pi
tb.rocks = SimpleList()
tb.used_rock = SimpleList()
tb.self_pointer = {}
tb.entry = AllocTableEx(10)
local ftb = setmetatable({}, tb)
Druidism.Buildings.HallOfTheEarth.GenerateZionite = ftb
tb._update = TimerIterator:create(Missile.INTERVAL, function(bar)
local self = bar.self
BlzSetSpecialEffectTime(self.bar, TimerGetElapsed(self.timer)/tb.custom.GENERATE_INTERVAL)
end)
function tb:__constructor(unit)
self.unit = unit
self.timer = CreateTimer()
self.list = SimpleList()
self.running = false
self.bar = AddSpecialEffect(ProgressBar.MODEL, GetUnitX(unit), GetUnitY(unit))
SetTimerData(self.timer, self)
SetEffectHeight(self.bar, 400)
BlzSetSpecialEffectTimeScale(self.bar, 0)
BlzSetSpecialEffectMatrixScale(self.bar, 3.30, 1.15, 1.15)
ptb[self.bar] = ptb.request()
ptb[self.bar].self = self
end
function tb:__destructor(unit)
ptb.restore(ptb[self.bar])
ptb[self.bar].self = nil
PauseTimer(self.timer)
DestroyTimer(self.timer)
BlzSetSpecialEffectAlpha(self.bar, 0)
DestroyEffect(self.bar)
self.list:destroy()
self.running = nil
self.unit = nil
self.timer = nil
self.list = nil
self.bar = nil
end
function tb.release_rock(rock)
UnitRecycler:simulate_death(rock)
doAfter(rtb.custom.DEATH_TIME, UnitRecycler.recycle, UnitRecycler, rock)
end
function tb.request_rock(owner, x, y, face)
local unit = UnitRecycler:request(owner, tb.custom.UNIT_ID, x, y, face)
UnitStopMovement(unit)
return unit
end
function tb.on_generate_zionite()
local self = GetTimerData(GetExpiredTimer())
local cx, cy = GetUnitX(self.unit), GetUnitY(self.unit)
cx = cx + tb.custom.ROCK_OFFSET*math.cos(tb.custom.ROCK_ANGLE)
cy = cy + tb.custom.ROCK_OFFSET*math.sin(tb.custom.ROCK_ANGLE)
local rock = tb.request_rock(GetOwningPlayer(self.unit), cx, cy, 0)
rtb:apply_regen(rock)
self.list:insert(rock)
tb.rocks:insert(rock)
tb.self_pointer[rock] = self
self:pause()
end
function tb:pause()
if not self.running then return;
end
if #self.list >= tb.custom.MAX_ROCKS then
self.running = false
PauseTimer(self.timer)
BlzSetSpecialEffectTime(self.bar, 0)
tb._update:remove(ptb[self.bar])
end
end
function tb:generate()
if self.running then return;
elseif #self.list >= tb.custom.MAX_ROCKS then return;
end
self.running = true
TimerStart(self.timer, tb.custom.GENERATE_INTERVAL, true, tb.on_generate_zionite)
tb._update:insert(ptb[self.bar])
end
function tb._cost_enum_unit(amount, range, user, rock)
if GetOwningPlayer(user) ~= GetOwningPlayer(rock) then
return amount
elseif not IsUnitInRange(user, rock, range) then
return amount
end
amount = math.max(amount - GetWidgetLife(rock), 0)
tb.used_rock:insert(rock)
return amount
end
function tb:apply_cost(amount, range, user, consume)
amount = amount or 0
local amt = amount
for rock in tb.rocks:iterator() do
amount = tb._cost_enum_unit(amount, range, user, rock)
end
if amount > 0 then
return false, 0, 0
end
if not consume then
tb.used_rock:clear()
return true, 0, 0
end
local cx, cy
for rock in tb.used_rock:iterator() do
DestroyEffect( AddSpecialEffect(tb.custom.CONSUME_MODEL, GetUnitX(rock), GetUnitY(rock)))
if GetWidgetLife(rock) > amt then
SetWidgetLife(rock, GetWidgetLife(rock) - amt)
amt = 0
cx = GetUnitX(rock)
cy = GetUnitY(rock)
break
else
amt = amt - GetWidgetLife(rock)
SetWidgetLife(rock, 0)
tb.rocks:remove(rock)
rtb:stop_regen(rock)
tb.release_rock(rock)
local this = tb.self_pointer[rock]
if this then
this.list:remove(rock)
this:generate()
tb.self_pointer[rock] = nil
end
end
end
return true, cx, cy
end
local function remove_entry(unit)
if not tb.entry[unit] then return;
end
local self = tb.entry[unit]
tb._update:remove(ptb[self.bar])
for rock in self.list:iterator() do
self.list:remove(rock)
tb.self_pointer[rock] = nil
end
self:destroy()
tb.entry[unit] = nil
end
UnitState.register("DEATH_EVENT", remove_entry)
UnitDex.register("LEAVE_EVENT", remove_entry)
UnitDex.register("ENTER_EVENT", function(unit)
local unitid = GetUnitTypeId(unit)
if unitid ~= tb.custom.BUILDING_ID then return;
elseif IsUnitUnderConstruction(unit) then return;
end
tb.entry[unit] = ftb(unit)
tb.entry[unit]:generate()
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, function()
local unit = GetConstructedStructure()
local unitid = GetUnitTypeId(unit)
if unitid ~= tb.custom.BUILDING_ID then return;
elseif tb.entry[unit] then return;
end
tb.entry[unit] = ftb(unit)
tb.entry[unit]:generate()
end)
end
Druidism.Units = {}
do
local tb = protected_table()
local rtb = Druidism.Abilities.RazorStrike
tb.custom = {
ABILITY_ID = FourCC("A01M"),
CHANCE = 0.20,
}
Initializer("SYSTEM", function()
tb.DUMMY = DummyUtils.request()
UnitAddAbility(tb.DUMMY, rtb.custom.ABILITY_ID)
UnitStopMovement(tb.DUMMY, true)
end)
function tb.strike_target(targ)
SetUnitX(tb.DUMMY, GetUnitX(targ))
SetUnitY(tb.DUMMY, GetUnitY(targ))
IssueTargetOrder(tb.DUMMY, rtb.custom.ORDER, targ)
end
DamageEvent.register_modifier("MODIFIER_EVENT_ALPHA", function(targ, src)
if not IsPhysicalDamage() then return;
elseif DamageEvent.current.dmgtype ~= DAMAGE_TYPE_NORMAL then return;
elseif GetUnitAbilityLevel(src, tb.custom.ABILITY_ID) == 0 then return;
end
local chance = math.random()
if chance <= tb.custom.CHANCE then
tb.strike_target(targ)
end
end)
end
do
local tb = protected_table()
local ptb
tb.custom = {
ABILITY_ID = FourCC("A00I")
}
Initializer("SYSTEM", function()
ptb = Druidism.Heroes.Archdruid.EntangledTrees
end)
DamageEvent.register_damage(function(targ, src, dmg)
if not ptb.owner.spawn[src] then return;
end
ptb.owner.spawn[src]:root(targ)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local src = GetTriggerUnit()
if not ptb.owner.spawn[src] then return;
end
ptb.owner.spawn[src]:start_decay()
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_CAST, tb.custom.ABILITY_ID, function()
SetUnitAnimation(GetTriggerUnit(), "attack")
end)
end
do
local tb = {}
tb.custom = {
ABILITY_LIST = LinkedList(FourCC('AHbu'), FourCC('A006')),
RESULT_TRANSFORM_ID = FourCC('e005'),
REVERT_TRANSFORM_ID = FourCC('e000'),
}
tb.is_bear = {}
function tb.disable_abil(whichunit, flag)
for abilId in tb.custom.ABILITY_LIST:iterator() do
BlzUnitDisableAbility(whichunit, abilId, flag, flag)
end
end
UnitState.register("TRANSFORM_EVENT", function()
local unit = UnitState.eventUnit
local typeid = GetUnitTypeId(unit)
if typeid == tb.custom.RESULT_TRANSFORM_ID then
tb.disable_abil(unit, true)
elseif typeid == tb.custom.REVERT_TRANSFORM_ID then
if tb.is_bear[unit] then
tb.is_bear[unit] = nil
return
end
tb.disable_abil(unit, false)
end
end)
UnitDex.register("ENTER_EVENT", function()
local unit = UnitDex.eventUnit
local typeid = GetUnitTypeId(unit)
if typeid == tb.custom.RESULT_TRANSFORM_ID then
tb.is_bear[unit] = true
end
end)
end
do
local tb = protected_table()
tb.custom = {
RESULT_UNIT_ID = FourCC("e006"),
UNIT_ID = FourCC("e004"),
ABILITY_ID = FourCC("A002"),
MIN_HP_RATIO = 0.50,
MORPH_ORDER = "unravenform",
TICKS_PER_SECOND = 10,
}
tb.disable = {}
tb.invul = {}
tb.flying = TimerIterator:create(tb.custom.TICKS_PER_SECOND, function(unit)
local maxHP = BlzGetUnitMaxHP(unit)
local curHP = GetWidgetLife(unit)
-- If current hp ratio is less than 50%, remove the harpy
-- from the list
if curHP/maxHP < tb.custom.MIN_HP_RATIO and IssueImmediateOrder(unit, tb.custom.MORPH_ORDER) then
tb.flying:remove(unit)
tb.invul[unit] = true
SetUnitInvulnerable(unit, true)
end
end)
tb.ground = TimerIterator:create(tb.custom.TICKS_PER_SECOND, function(unit)
local maxHP = BlzGetUnitMaxHP(unit)
local curHP = GetWidgetLife(unit)
-- If current hp ratio is less than 50%, disable the ability
if curHP/maxHP < tb.custom.MIN_HP_RATIO then
if tb.disable[unit] == 0 then
BlzUnitDisableAbility(unit, tb.custom.ABILITY_ID, true, false)
end
tb.disable[unit] = -1
else
if tb.disable[unit] == -1 then
BlzUnitDisableAbility(unit, tb.custom.ABILITY_ID, false, false)
end
tb.disable[unit] = 0
end
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
if tb.ground:is_elem_in(unit) then
tb.ground:remove(unit)
end
end)
function tb.register_unit(whichunit)
local unitType = GetUnitTypeId(whichunit)
if unitType == tb.custom.RESULT_UNIT_ID then
if tb.flying:is_elem_in(whichunit) then return;
end
tb.flying:insert(whichunit)
end
if unitType == tb.custom.UNIT_ID then
if tb.ground:is_elem_in(whichunit) then return;
end
tb.disable[whichunit] = 0
tb.ground:insert(whichunit)
end
if tb.invul[whichunit] then
SetUnitInvulnerable(whichunit, false)
tb.invul[whichunit] = nil
end
end
UnitState.register("RESURRECT_EVENT", function()
tb.register_unit(UnitState.eventUnit)
end)
UnitState.register("TRANSFORM_EVENT", function()
tb.register_unit(UnitState.eventUnit)
end)
UnitDex.register("ENTER_EVENT", function()
tb.register_unit(UnitDex.eventUnit)
end)
UnitDex.register("LEAVE_EVENT", function()
local unit = UnitDex.eventUnit
tb.disable[unit] = nil
if tb.flying:is_elem_in(unit) then
tb.flying:remove(unit)
end
if tb.ground:is_elem_in(unit) then
tb.ground:remove(unit)
end
tb.invul[unit] = nil
end)
end
local tb = protected_table()
local drop = AllocTable(0)
local buffdata = AllocTable(0)
local feather = AllocTable(0)
tb.custom = {
ABILITY_ID = FourCC("A007"),
DUMMY_ABILITY_ID = FourCC("A009"),
DUMMY_BUFF_ID = FourCC("B000"),
DUMMY_ORDER = "acidbomb",
NOVA_DAMAGE = 40.,
ATTACK_TYPE = ATTACK_TYPE_NORMAL,
DAMAGE_TYPE = DAMAGE_TYPE_NORMAL,
DROPPING_EFFECT = "war3mapImported\\Feather.mdx",
DROPPING_INTERVAL = 0.5, -- This is the spawn interval
DROPPING_DESCENT = 100., -- This is the descent distance
DROPPING_DURATION = 2.0,
DROPPING_LIFETIME = 8.0,
NOVA_EFFECT = "war3mapImported\\EnergyBurst.mdx",
NOVA_FEATHER_EFFECT = "war3mapImported\\FeatherMissile.mdl",
NOVA_FEATHER_COUNT = 20,
NOVA_FEATHER_VELOC = 600,
NOVA_FEATHER_DECAY = 0.25,
BEZIER = BezierEase.linear,
}
tb.buffer = {}
tb.drop = {}
tb.feather = {}
Initializer("SYSTEM", function()
tb.custom.AREA_OF_EFFECT = tonumber(ObjectReader.read(tb.custom.ABILITY_ID, "Area1", ",."))
tb.custom.NOVA_SPREAD_DIST = tb.custom.AREA_OF_EFFECT
tb.custom.DROPPING_INTERVAL = math.floor(tb.custom.DROPPING_INTERVAL/BuffWatcher.INTERVAL + 0.5)
tb.custom.AREA_OF_EFFECT = tb.custom.AREA_OF_EFFECT*1.1
tb.custom.DUMMY = DummyUtils.request()
UnitAddAbility(tb.custom.DUMMY, tb.custom.DUMMY_ABILITY_ID)
ShowUnit(tb.custom.DUMMY, false)
BlzUnitDisableAbility(tb.custom.DUMMY, FourCC("Amov"), true, false)
end)
function tb.filter_target(targ, unit)
return UnitAlive(targ) and IsUnitEnemy(targ, GetOwningPlayer(unit))
and (not IsUnitType(targ, UNIT_TYPE_STRUCTURE))
and (not IsUnitType(targ, UNIT_TYPE_MAGIC_IMMUNE))
and IsUnitInRange(targ, unit, tb.custom.NOVA_SPREAD_DIST)
end
function drop:__constructor(whichunit)
self.drop = Missile(tb.custom.DROPPING_EFFECT, GetUnitX(whichunit), GetUnitY(whichunit))
self.ticks = tb.custom.DROPPING_DURATION/Missile.GRADIENT
self.max = self.ticks
tb.drop[self.drop] = self
end
function drop:__destructor()
tb.drop[self.drop] = nil
self.drop:destroy()
self.drop = nil
self.ticks = nil
self.max = nil
end
function drop:force_stop()
self.drop:stop()
end
function drop.on_move(dropping)
local self = tb.drop[dropping]
local dh = tb.custom.BEZIER[(self.ticks - 1)/self.max] - tb.custom.BEZIER[self.ticks/self.max]
self.ticks = self.ticks - 1
dh = dh*tb.custom.DROPPING_DESCENT
if self.ticks <= 0 then
doAfter(0.00, drop.force_stop, self)
end
return Vector2D(0, 0), dh
end
function drop.on_stop(dropping)
local self = tb.drop[dropping]
doAfter(tb.custom.DROPPING_LIFETIME, drop.destroy, self)
end
function buffdata:__constructor(buffer)
self.buffer = buffer
self.tick = tb.custom.DROPPING_INTERVAL
end
function buffdata:__destructor()
self.buffer = nil
self.tick = nil
end
function buffdata.check_buff(whichunit, whichbuff)
local buffer = tb.buffer[whichunit]
buffer.tick = buffer.tick - 1
if buffer.tick > 0 then return;
end
buffer.tick = tb.custom.DROPPING_INTERVAL
tb.create_drop(whichunit)
end
function buffdata.remove_buff(whichunit, whichbuff)
tb.buffer[whichunit]:destroy()
tb.buffer[whichunit] = nil
end
function feather:__constructor(tx, ty)
self.feather = PointMissile(tb.custom.NOVA_FEATHER_EFFECT, tx, ty)
end
function feather:__destructor()
self.feather:destroy()
self.feather = nil
end
function feather.on_stop(missile)
local self = tb.feather[missile]
doAfter(tb.custom.NOVA_FEATHER_DECAY, feather.destroy, self)
end
-- At this point, treat the tables as their own metatable.
local Drop = setmetatable({}, drop)
local Buffdata = setmetatable({}, buffdata)
local Feather = setmetatable({}, feather)
function tb.add_nova(tx, ty)
local nova_fx = AddSpecialEffect(tb.custom.NOVA_EFFECT, tx, ty)
BlzSetSpecialEffectScale(nova_fx, 1.50)
DestroyEffect(nova_fx)
end
function tb.add_feathers(tx, ty)
local theta = 0
local incr = 2*math.pi/tb.custom.NOVA_FEATHER_COUNT
for i = 1, tb.custom.NOVA_FEATHER_COUNT do
local f = Feather(tx, ty)
tb.feather[f.feather.missile] = f
f.feather.missile:set_height(20)
f.feather:set_speed(tb.custom.NOVA_FEATHER_VELOC)
f.feather:mark(tx + tb.custom.NOVA_SPREAD_DIST*math.cos(theta),
ty + tb.custom.NOVA_SPREAD_DIST*math.sin(theta))
f.feather:launch()
f.feather:config_stop(feather.on_stop)
BlzSetSpecialEffectScale(f.feather.missile.effect, 0.65)
theta = theta + incr
end
end
function tb.create_drop(whichunit)
local poop = Drop(whichunit)
poop.drop:set_height(70)
poop.drop:launch()
poop.drop:config_move(drop.on_move)
poop.drop:config_stop(drop.on_stop)
end
function tb.attempt_cast(whichunit)
SetUnitX(tb.custom.DUMMY, GetUnitX(whichunit))
SetUnitY(tb.custom.DUMMY, GetUnitY(whichunit))
if not IssueTargetOrder(tb.custom.DUMMY, tb.custom.DUMMY_ORDER, whichunit) then
SetUnitOwner(tb.custom.DUMMY, GetOwningPlayer(whichunit), false)
IssueTargetOrder(tb.custom.DUMMY, tb.custom.DUMMY_ORDER, whichunit)
SetUnitOwner(tb.custom.DUMMY, tb.custom.PASSIVE, true)
end
end
function tb.watch_unit(whichunit)
tb.attempt_cast(whichunit)
local buffer = BuffWatcher.watch(whichunit, tb.custom.DUMMY_BUFF_ID)
if tb.buffer[whichunit] then return;
end
tb.buffer[whichunit] = Buffdata(buffer)
buffer:on_buff_check(buffdata.check_buff)
buffer:on_buff_remove(buffdata.remove_buff)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local unit, tx, ty = GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY()
tb.add_nova(tx, ty)
tb.add_feathers(tx, ty)
for enum_unit in EnumUnitsInRange(tx, ty, tb.custom.AREA_OF_EFFECT) do
if tb.filter_target(enum_unit, unit) then
UnitDamageTarget(unit, enum_unit, tb.custom.NOVA_DAMAGE, true, true,
tb.custom.ATTACK_TYPE, tb.custom.DAMAGE_TYPE, nil)
tb.watch_unit(enum_unit)
end
end
end)
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A00A"),
SOUND_PATH = "Units\\Other\\Rokhan\\RokhanWarcry1.wav",
SOUND_FX = "war3mapImported\\Singularity II Green.mdx",
DURATION = 1195,
BONUS_ARMOR = 8,
BONUS_DECAY = 2,
BONUS_INTERVAL = 2.00,
ICON_PATH = "ReplaceableTextures\\CommandButtons\\Custom\\BTNTrollSpite.blp",
ICON_NAME = "|cff40ff40Fortitude|r",
ICON_DESC = {"This unit temporarily has ", " increased armor"}
}
Initializer("USER", function()
tb.custom.ICON_PATH = ObjectReader.extract_icon(tb.custom.ABILITY_ID)
end)
tb.bonus = {}
function tb._new_sound()
local snd = CreateSound(tb.custom.SOUND_PATH, false, true, true, 10, 10, "HeroAcksEAX")
SetSoundParamsFromLabel( snd, "RokhanWarcry" )
SetSoundDuration( snd, tb.custom.DURATION )
return snd
end
function tb.kill_sound(snd)
StartSound(snd)
KillSoundWhenDone(snd)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local snd = tb._new_sound()
local unit = GetTriggerUnit()
local tx, ty = GetUnitX(unit), GetUnitY(unit)
DestroyEffect( AddSpecialEffect(tb.custom.SOUND_FX, tx, ty))
SetSoundPosition(snd, tx, ty, 0)
doAfter(0.00, tb.kill_sound, snd)
if not tb.bonus[unit] then
tb.bonus[unit] = {
bonus = BonusArmor:apply_bonus(unit, tb.custom.BONUS_ARMOR, "SUM"),
timer = CreateTimer(),
buff = CustomBuffSystem.add(unit, tb.custom.ICON_PATH, tb.custom.ICON_NAME,
tb.custom.ICON_DESC[1] .. tostring(tb.custom.BONUS_ARMOR) .. tb.custom.ICON_DESC[2])
}
else
tb.bonus[unit].bonus:set_bonus(tb.custom.BONUS_ARMOR)
tb.bonus[unit].buff.icon_desc = tb.custom.ICON_DESC[1] .. tostring(tb.custom.BONUS_ARMOR) .. tb.custom.ICON_DESC[2]
PauseTimer(tb.bonus[unit].timer)
TimerStart(tb.bonus[unit].timer, 0.00, false, nil)
PauseTimer(tb.bonus[unit].timer)
end
TimerStart(tb.bonus[unit].timer, tb.custom.BONUS_INTERVAL, true, function()
tb.bonus[unit].bonus:set_bonus(tb.bonus[unit].bonus.amount - tb.custom.BONUS_DECAY)
tb.bonus[unit].buff.icon_desc = tb.custom.ICON_DESC[1] .. tostring(tb.bonus[unit].bonus.amount) .. tb.custom.ICON_DESC[2]
if tb.bonus[unit].bonus.amount <= 0 then
PauseTimer(tb.bonus[unit].timer)
DestroyTimer(tb.bonus[unit].timer)
tb.bonus[unit].buff:destroy()
tb.bonus[unit].bonus:destroy()
tb.bonus[unit] = nil
end
end)
end)
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A00B"),
BUFF_ID = FourCC("B001"),
ART_INTERVAL = 1.00,
ART_EFFECT = "war3mapImported\\CircleOutBuff.mdx",
REGEN_EFFECT = "Abilities\\Spells\\Other\\ANrm\\ANrmTarget.mdl",
REGEN_RANGE = 250.00,
REGEN_PERIOD = 10.00,
REGEN_INTERVAL = 0.50,
REGEN_BASE = 200,
REGEN_MAX_HP = 0.50,
MAX_STACKS = 3,
}
tb.custom.REGEN_BASE = tb.custom.REGEN_BASE/tb.custom.REGEN_PERIOD*tb.custom.REGEN_INTERVAL
tb.custom.REGEN_MAX_HP = tb.custom.REGEN_MAX_HP/tb.custom.REGEN_PERIOD*tb.custom.REGEN_INTERVAL
tb.custom.STACK_DUR = tb.custom.REGEN_INTERVAL - 0.01
tb.regen = {}
tb.stacks = {}
function tb.is_valid_target(targ, unit)
return UnitAlive(targ) and IsUnitAlly(targ, GetOwningPlayer(unit)) and
(not IsUnitType(targ, UNIT_TYPE_STRUCTURE)) and
(not IsUnitType(targ, UNIT_TYPE_MECHANICAL)) and (targ ~= unit)
end
function tb.reduce_stacks(targ)
if not tb.stacks[targ] then return;
end
tb.stacks[targ] = tb.stacks[targ] - 1
end
function tb.apply_heal(targ, heal)
local curHP = GetWidgetLife(targ)
if not tb.stacks[targ] or (tb.stacks[targ] < tb.custom.MAX_STACKS) then
SetWidgetLife(targ, curHP + heal)
tb.stacks[targ] = (not tb.stacks[targ] and 1) or tb.stacks[targ] + 1
if tb.stacks[targ] < 2 then
DestroyEffect(AddSpecialEffectTarget(tb.custom.REGEN_EFFECT, targ, "chest"), tb.custom.STACK_DUR)
end
doAfter(tb.custom.STACK_DUR, tb.reduce_stacks, targ)
return heal - GetWidgetLife(targ) + curHP
end
return heal
end
function tb.can_heal(targ)
local result = GetUnitMaxHP(targ) - GetWidgetLife(targ)
return result >= 0.01
end
function tb.heal_targ()
local targ = GetTimerData(GetExpiredTimer())
local heal = tb.custom.REGEN_BASE + tb.custom.REGEN_MAX_HP*GetUnitMaxHP(targ)
heal = tb.apply_heal(targ, heal)
if heal <= 0 then return;
end
local t = {}
for enum_unit in EnumUnitsInRange(GetUnitX(targ), GetUnitY(targ), tb.custom.REGEN_RANGE) do
if tb.is_valid_target(enum_unit, targ) and tb.can_heal(enum_unit) then
t[#t+1] = enum_unit
end
end
local extra = 0
for i = 1, #t do
extra = tb.apply_heal(t[i], heal/#t + extra)
end
end
function tb.draw_effect()
local targ = GetTimerData(GetExpiredTimer())
local fx = AddSpecialEffectTarget(tb.custom.ART_EFFECT, targ, "chest")
BlzSetSpecialEffectScale(fx, 3.25)
BlzSetSpecialEffectColor(fx, 0x40, 0xff, 0x40)
BlzSetSpecialEffectScale(fx, 2.00)
DestroyEffect(fx, 0.5)
end
function tb.on_heal_remove(targ, buff)
PauseTimer(tb.regen[targ].timer)
DestroyTimer(tb.regen[targ].timer)
PauseTimer(tb.regen[targ].art_timer)
DestroyTimer(tb.regen[targ].art_timer)
tb.regen[targ] = nil
end
function tb.regenerate(targ, buffer)
if tb.regen[targ] then return;
end
tb.regen[targ] = {buff = buffer, timer = CreateTimer(), art_timer = CreateTimer()}
buffer:on_buff_remove(tb.on_heal_remove)
SetTimerData(tb.regen[targ].timer, targ)
SetTimerData(tb.regen[targ].art_timer, targ)
TimerStart(tb.regen[targ].timer, tb.custom.REGEN_INTERVAL, true, tb.heal_targ)
TimerStart(tb.regen[targ].art_timer, tb.custom.ART_INTERVAL, true, tb.draw_effect)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local targ = GetSpellTargetUnit()
local buffer = BuffWatcher.watch(targ, tb.custom.BUFF_ID)
tb.regenerate(targ, buffer)
end)
UnitDex.register("LEAVE_EVENT", function(unit)
if not tb.stacks[unit] then return;
end
tb.stacks[unit] = nil
end)
BuffWatcher.register_function(function(unit, buffid, buffer)
if buffid ~= tb.custom.BUFF_ID then return;
end
tb.regenerate(unit, buffer)
end)
end
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A00T"),
INVIS_ABIL_ID = FourCC("A00S"),
DUMMY_ABIL_ID = FourCC("A00U"),
BUFF_WATCH_ID = FourCC("B006"),
DUMMY_ORDER = "innerfire",
REVEAL_DURATION = 1.5,
}
tb.dnc_state = false
tb.buffed = {}
tb.counter = {}
tb.disabled = {}
Initializer("SYSTEM", function()
tb.DUMMY = DummyUtils.request()
tb.custom.AOE = tonumber(ObjectReader.read(tb.custom.ABILITY_ID, "Area1", ",."))
UnitAddAbility(tb.DUMMY, tb.custom.DUMMY_ABIL_ID)
UnitStopMovement(tb.DUMMY)
end)
DNCycle.register(function(daytime)
tb.dnc_state = daytime
end)
function tb.is_ally(targ, caster)
return IsUnitAlly(targ, GetOwningPlayer(caster)) and UnitAlive(targ)
and not IsUnitType(targ, UNIT_TYPE_STRUCTURE)
and not IsUnitType(targ, UNIT_TYPE_MECHANICAL)
end
function tb.disable_hide_abil()
local targ = GetTimerData(GetExpiredTimer())
tb.disabled[targ] = true
BlzUnitDisableAbility(targ, tb.custom.INVIS_ABIL_ID, true, true)
end
function tb.on_check_buff(targ)
if not tb.dnc_state then return;
end
if IsUnitMoving(targ) and tb.counter[targ] then
PauseTimer(tb.counter[targ])
DestroyTimer(tb.counter[targ])
tb.counter[targ] = nil
if tb.disabled[targ] then
tb.disabled[targ] = nil
BlzUnitDisableAbility(targ, tb.custom.INVIS_ABIL_ID, false, false)
end
elseif tb.dnc_state and (not IsUnitMoving(targ)) and not tb.counter[targ] then
tb.counter[targ] = CreateTimer()
SetTimerData(tb.counter[targ], targ)
TimerStart(tb.counter[targ], tb.custom.REVEAL_DURATION, false, tb.disable_hide_abil)
end
end
function tb.on_remove_buff(targ)
if tb.counter[targ] then
PauseTimer(tb.counter[targ])
DestroyTimer(tb.counter[targ])
tb.counter[targ] = nil
end
UnitRemoveAbility(targ, tb.custom.INVIS_ABIL_ID)
tb.buffed[targ] = nil
tb.disabled[targ] = nil
end
function tb.check_buff(targ)
if tb.buffed[targ] then return;
end
tb.buffed[targ] = BuffWatcher.watch(targ, tb.custom.BUFF_WATCH_ID)
UnitAddAbility(targ, tb.custom.INVIS_ABIL_ID)
UnitMakeAbilityPermanent(targ, true, tb.custom.INVIS_ABIL_ID)
tb.buffed[targ]:on_buff_check(tb.on_check_buff)
tb.buffed[targ]:on_buff_remove(tb.on_remove_buff)
end
function tb.apply_buff(targ)
SetUnitX(tb.DUMMY, GetUnitX(targ))
SetUnitY(tb.DUMMY, GetUnitY(targ))
SetUnitOwner(tb.DUMMY, GetOwningPlayer(targ), false)
IssueTargetOrder(tb.DUMMY, tb.custom.DUMMY_ORDER, targ)
SetUnitOwner(tb.DUMMY, DummyUtils.PLAYER, false)
tb.check_buff(targ)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local caster, tx, ty = GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY()
for ally in EnumUnitsInRange(tx, ty, tb.custom.AOE) do
if tb.is_ally(ally, caster) then
print("Cloak of Moonlight >> Got ally:", GetUnitName(ally))
tb.apply_buff(ally)
end
end
end)
BuffWatcher.register_function(function(unit, buffid, buffer)
if buffid ~= tb.custom.BUFF_WATCH_ID then return;
end
tb.check_buff(unit)
end)
end
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A00V"),
EFFECT_MODEL = "war3mapImported\\Arcane Disperse Aura.mdx",
}
tb.efx = {}
Initializer("SYSTEM", function()
tb.custom.EFFECT_SCALE = tonumber(ObjectReader.read(tb.custom.ABILITY_ID, "Area1", ",."))
tb.custom.EFFECT_SCALE = tb.custom.EFFECT_SCALE/100.
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
tb.efx[unit] = AddSpecialEffect(tb.custom.EFFECT_MODEL, GetSpellTargetX(), GetSpellTargetY())
BlzSetSpecialEffectScale(tb.efx[unit], tb.custom.EFFECT_SCALE)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, tb.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
if not tb.efx[unit] then return;
end
DestroyEffect(tb.efx[unit])
tb.efx[unit] = nil
end)
end
do
local tb = AllocTable(50)
local ftb = setmetatable({}, tb)
tb.custom = {
ABILITY_ID = FourCC("A01C"),
DUMMY_ABIL_ID = FourCC("A01D"),
DUMMY_ORDER = "curse",
DISP_ARC = math.pi,
DISP_DURATION = 2.50,
DISP_BEZIER = BezierEasing.create(0, 0.63, 0.37, 1),
MAX_RANGE = 600,
DAMAGE_AMOUNT = 15,
ATTACK_TYPE = ATTACK_TYPE_NORMAL,
DAMAGE_TYPE = DAMAGE_TYPE_ENHANCED,
EFFECT_MODEL = "war3mapImported\\Singularity II Orange.mdx",
BASE_TIME = 1.25,
BASE_RADIUS = 300,
}
tb.custom.TICKS = tb.custom.DISP_DURATION*UnitMovement.INTERVAL
tb.moving = SimpleList()
Initializer("SYSTEM", function()
tb.custom.AOE = tonumber(ObjectReader.read(tb.custom.ABILITY_ID, "Area1", ",."))
tb.DUMMY = DummyUtils.request()
UnitAddAbility(tb.DUMMY, tb.custom.DUMMY_ABIL_ID)
UnitStopMovement(tb.DUMMY)
end)
function tb.filter_target_damage(targ, caster)
return UnitAlive(targ) and IsUnitEnemy(targ, GetOwningPlayer(caster))
and not IsUnitType(targ, UNIT_TYPE_STRUCTURE)
and not IsUnitType(targ, UNIT_TYPE_FLYING)
and not IsUnitType(targ, UNIT_TYPE_MAGIC_IMMUNE)
end
function tb.filter_target(targ, caster)
return tb.filter_target_damage(targ, caster) and not tb.moving:is_in(targ)
end
function tb:__constructor(unit, tx, ty)
self.movement = UnitMovement(unit)
self.tx = tx
self.ty = ty
local cx, cy = GetUnitX(unit), GetUnitY(unit)
local dist = math.sqrt((tx-cx)*(tx-cx) + (ty-cy)*(ty-cy))
local theta = math.atan(cy-ty, cx-tx)
self.dist = dist
self.base = theta
self.ticks = 0
end
function tb:__destructor()
self.movement:destroy()
self.movement = nil
self.dist = nil
self.tx = nil
self.ty = nil
self.base = nil
self.ticks = nil
end
function tb.disorient_target(targ)
SetUnitX(tb.DUMMY, GetUnitX(targ))
SetUnitY(tb.DUMMY, GetUnitY(targ))
IssueTargetOrder(tb.DUMMY, tb.custom.DUMMY_ORDER, targ)
-- Make the target do the previous point order if possible
local orderInfo = OrderMatrix[targ]
if orderInfo.orderType[1] ~= OrderMatrix.POINT then return;
end
local cx, cy = GetUnitX(targ), GetUnitY(targ)
local vect = Vector2D(orderInfo.point.x[1] - cx,
orderInfo.point.y[1] - cy)
IssuePointOrder(targ, orderInfo.order[1], cx - vect.x, cy - vect.y)
end
function tb.on_movement_stop(movement)
local self = movement:get_data()
tb.moving:remove(movement.unit)
self:destroy()
end
function tb.on_movement_update(movement)
local self = movement:get_data()
self.ticks = self.ticks + 1
if self.ticks > tb.custom.TICKS then
movement:stop()
return Vector2D.ORIGIN, 0
elseif not IsUnitInRangeXY(movement.unit, self.tx, self.ty, tb.custom.MAX_RANGE) then
movement:stop()
return Vector2D.ORIGIN, 0
end
local theta = self.base + tb.custom.DISP_ARC*tb.custom.DISP_BEZIER[self.ticks/tb.custom.TICKS]
local disp = tb.custom.DISP_ARC*(tb.custom.DISP_BEZIER[self.ticks/tb.custom.TICKS] -
tb.custom.DISP_BEZIER[(self.ticks - 1)/tb.custom.TICKS])
local tx = self.dist*(math.cos(theta) - math.cos(theta - disp))
local ty = self.dist*(math.sin(theta) - math.sin(theta - disp))
return Vector2D(tx, ty), 0
end
function tb.callback_target(targ, caster, tx, ty)
if tb.filter_target_damage(targ, caster) then
UnitDamageTarget(caster, targ, tb.custom.DAMAGE_AMOUNT, false, true,
tb.custom.ATTACK_TYPE, tb.custom.DAMAGE_TYPE, nil)
tb.disorient_target(targ)
end
if not tb.filter_target(targ, caster) then return;
end
local self = ftb(targ, tx, ty)
tb.moving:insert(targ)
self.movement:config_move(tb.on_movement_update)
self.movement:config_stop(tb.on_movement_stop)
self.movement:set_data(self)
self.movement:launch()
end
function tb.create_effect(tx, ty)
local fx = AddSpecialEffect(tb.custom.EFFECT_MODEL, tx, ty)
BlzSetSpecialEffectScale(fx, tb.custom.AOE/tb.custom.BASE_RADIUS)
BlzSetSpecialEffectTimeScale(fx, tb.custom.BASE_TIME/tb.custom.DISP_DURATION)
DestroyEffect(fx, tb.custom.DISP_DURATION)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function(unit)
local tx, ty = GetSpellTargetX(), GetSpellTargetY()
tb.create_effect(tx, ty)
for targ in EnumUnitsInRange(tx, ty, tb.custom.AOE) do
tb.callback_target(targ, unit, tx, ty)
end
end)
UnitDex.register("LEAVE_EVENT", function(unit)
tb.moving:remove(unit)
end)
end
do
local tb = protected_table()
local rtb = Druidism.Buildings.HallOfTheEarth.GenerateZionite
local mtb = AllocTableEx(5)
tb.custom = {
ABILITY_ID = FourCC("A01E"),
SUMMON_ID = FourCC("n001"),
CAST_RANGE = 800,
SPAWN_OFFSET = 120,
SPAWN_ANGLE = 315.,
ZIONITE_COST = 17,
MISSILE_MODEL = "war3mapImported\\Psionic Shot Orange.mdx",
MISSILE_HEIGHT_TRANS = 2.5,
MISSILE_SPEED_TRANS = 3.00,
MISSILE_SPEED_BASE = 300,
MISSILE_SPEED = 600,
MISSILE_TURN = 135,
MISSILE_HEIGHT = 200.,
MISSILE_SPEED_BEZIER = BezierEasing.create(0.5, 0, 1, 0.5),
SPAWN_SHOCK_MODEL = "Abilities\\Spells\\Orc\\WarStomp\\WarStompCaster.mdl",
GOLEM_LIFETIME = 120,
}
tb.custom.MISSILE_SPEED_TICKS = tb.custom.MISSILE_SPEED_TRANS*Missile.INTERVAL
tb.custom.MISSILE_SPEED_DELTA = tb.custom.MISSILE_SPEED - tb.custom.MISSILE_SPEED_BASE
tb.custom.SPAWN_ANGLE = tb.custom.SPAWN_ANGLE/180*math.pi
tb.DISP_CACHE = math.cache(0, 1, Missile.GRADIENT/tb.custom.MISSILE_HEIGHT_TRANS,
function(a)
local c = 0.875*math.pi
local d = c - math.pi/2
return math.cos(c*a - d)
end)
function mtb.clean(miss)
mtb.restore(miss)
miss.unit = nil
miss.tick_z = nil
miss.tick_xy = nil
miss.object = nil
end
function mtb.on_missile_stop(missile)
local miss = missile:get_data()
local tx, ty = miss.object.tx, miss.object.ty
miss.object:destroy()
tb.create_golem(miss.unit, tx, ty)
mtb.clean(miss)
end
function mtb.on_missile_move(missile)
local miss = missile.missile:get_data()
local prev_tick = miss.tick_z
if tb.DISP_CACHE:next(miss.tick_z) ~= 1 then
miss.tick_z = miss.tick_z + 1
end
if miss.tick_xy < tb.custom.MISSILE_SPEED_TICKS then
miss.tick_xy = miss.tick_xy + 1
local disp = tb.custom.MISSILE_SPEED_BEZIER[miss.tick_xy]
disp = tb.custom.MISSILE_SPEED_DELTA*disp
miss.object:set_speed(tb.custom.MISSILE_SPEED_BASE + disp)
end
local disp2 = tb.DISP_CACHE[miss.tick_z] - tb.DISP_CACHE[prev_tick]
return tb.custom.MISSILE_HEIGHT*disp2
end
function tb.create_golem(unit, cx, cy)
local golem = CreateUnit(DummyUtils.PLAYER, tb.custom.SUMMON_ID, cx, cy, bj_UNIT_FACING)
SetUnitX(golem, cx)
SetUnitY(golem, cy)
SetUnitUseFood(golem, false)
SetUnitOwner(golem, GetOwningPlayer(unit))
UnitApplyTimedLife(golem, FourCC('BTLF'), tb.custom.GOLEM_LIFETIME)
SetUnitAnimation(golem, "birth")
QueueUnitAnimation(golem, "stand")
DestroyEffect( AddSpecialEffect(tb.custom.SPAWN_SHOCK_MODEL, cx, cy))
end
function tb.summon_projectile(cx, cy, unit)
local miss = mtb.request()
local disp = tb.custom.SPAWN_OFFSET
local theta = tb.custom.SPAWN_ANGLE
local tx, ty = GetUnitX(unit) + disp*math.cos(theta), GetUnitY(unit) + disp*math.sin(theta)
miss.unit = unit
miss.tick_z = 1
miss.tick_xy = 0
miss.object = PointMissile(tb.custom.MISSILE_MODEL, cx, cy)
miss.object:set_speed(tb.custom.MISSILE_SPEED_BASE)
miss.object:set_turn_rate(tb.custom.MISSILE_TURN)
miss.object.missile:set_data(miss)
miss.object.missile:set_height(tb.custom.MISSILE_HEIGHT*tb.DISP_CACHE[1])
miss.object:config_move(mtb.on_missile_move)
miss.object:config_stop(mtb.on_missile_stop)
miss.object:mark(tx, ty)
miss.object:launch()
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_CAST, tb.custom.ABILITY_ID, function(unit)
if not rtb:apply_cost(tb.custom.ZIONITE_COST, tb.custom.CAST_RANGE, unit) then
GameError(GetOwningPlayer(unit), "Cannot cast the ability")
PauseUnit(unit, true)
IssueImmediateOrderById(unit, 851972)
PauseUnit(unit, false)
return
end
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function(unit)
local flag, cx, cy = rtb:apply_cost(tb.custom.ZIONITE_COST, tb.custom.CAST_RANGE,
unit, true)
if not flag then
GameError(GetOwningPlayer(unit), "Insufficient Zionite Rocks")
return
end
tb.summon_projectile(cx, cy, unit)
end)
end
do
local tb = protected_table()
local rtb = Druidism.Buildings.HallOfTheEarth.GenerateZionite
local mtb = AllocTableEx(5) -- Movement table
local ztb = AllocTableEx(10) -- Zion table
tb.custom = {
ABILITY_ID = FourCC("A01I"),
TARGET_ID = FourCC("n001"),
GOLEM_ID = FourCC("n002"),
TEMP_INVUL_PERIOD = 1.25,
TRAVEL_SPEED = 700,
COLLISION_EFFECT = "Abilities\\Spells\\Undead\\Possession\\PossessionMissile.mdl",
MOTION_MODEL = "war3mapImported\\Windwalk Blue Soul.mdx",
EJECT_SPEED = 750,
EJECT_DISTANCE = 500,
HARBINGER_LIFETIME = 40,
}
tb.custom.EJECT_TICKS = tb.custom.EJECT_DISTANCE/tb.custom.EJECT_SPEED
tb.custom.EJECT_TICKS = math.floor(tb.custom.EJECT_TICKS*UnitMovement.INTERVAL)
tb.custom.EJECT_SPEED = tb.custom.EJECT_SPEED*UnitMovement.GRADIENT
tb.custom.TRAVEL_SPEED = tb.custom.TRAVEL_SPEED*UnitMovement.GRADIENT
function tb.filter_golem(targ, unit)
return UnitAlive(targ) and GetUnitTypeId(targ) == tb.custom.TARGET_ID
and GetOwningPlayer(targ) == GetOwningPlayer(unit)
end
function mtb.on_eject_stop(ment)
local move = ment:get_data()
local tx, ty = GetUnitX(move.unit) + tb.custom.EJECT_DISTANCE*math.cos(move.theta),
GetUnitY(move.unit) + tb.custom.EJECT_DISTANCE*math.sin(move.theta)
DestroyEffect(move.fx)
IssuePointOrder(move.unit, "move", cx, cy)
move.ment:destroy()
move.unit = nil
move.ment = nil
move.theta = nil
move.ticks = nil
move.fx = nil
mtb.restore(move)
end
function mtb.on_eject_move(ment)
local move = ment:get_data()
local cx, cy = GetUnitX(move.unit), GetUnitY(move.unit)
local tx, ty = tb.custom.EJECT_SPEED*math.cos(move.theta),
tb.custom.EJECT_SPEED*math.sin(move.theta)
move.ticks = move.ticks + 1
if move.ticks > tb.custom.EJECT_TICKS then
move.ment:stop()
return Vector2D.ORIGIN, 0
end
return Vector2D(tx, ty), 0
end
function mtb.on_unit_stop(ment)
local move = ment:get_data()
DestroyEffect(AddSpecialEffect(tb.custom.COLLISION_EFFECT,
GetUnitX(move.targ), GetUnitY(move.targ)))
DestroyEffect(move.fx)
ment:destroy()
tb.zionize(move.targ, move.unit)
move.ment = nil
move.unit = nil
move.targ = nil
move.fx = nil
mtb.restore(move)
end
function mtb.on_unit_move(ment)
local move = ment:get_data()
local tx, ty = GetUnitX(move.targ), GetUnitY(move.targ)
local cx, cy = GetUnitX(move.unit), GetUnitY(move.unit)
local theta = math.atan(ty - cy, tx - cx)
local dist = (tx-cx)*(tx-cx) + (ty-cy)*(ty-cy)
if dist > tb.custom.TRAVEL_SPEED*tb.custom.TRAVEL_SPEED then
return Vector2D(tb.custom.TRAVEL_SPEED*math.cos(theta), tb.custom.TRAVEL_SPEED*math.sin(theta)), 0
end
move.ment:stop()
return Vector2D(tx-cx, ty-cy), 0
end
function tb.discard(targ)
RemoveUnit(targ)
end
function tb.eject(unit, golem)
SetUnitX(unit, GetUnitX(golem))
SetUnitY(unit, GetUnitY(golem))
SetUnitFacing(unit, GetUnitFacing(golem)-180)
ShowUnit(unit, true)
PauseUnit(unit, false)
doAfter(tb.custom.TEMP_INVUL_PERIOD, UnitRemoveAbility, unit, FourCC("Avul"))
if IsUnitSelected(golem, GetOwningPlayer(unit)) and
GetLocalPlayer() == GetOwningPlayer(unit) then
SelectUnit(unit, true)
end
local move = mtb.request()
move.unit = unit
move.theta = (GetUnitFacing(golem)-180.)/180*math.pi
move.ticks = 0
move.fx = AddSpecialEffectTarget(tb.custom.MOTION_MODEL, unit, "origin")
move.ment = UnitMovement(unit)
move.ment:set_data(move)
move.ment:config_move(mtb.on_eject_move)
move.ment:config_stop(mtb.on_eject_stop)
move.ment:launch()
end
function tb.zionize(targ, unit)
local cx, cy = GetUnitX(targ), GetUnitY(targ)
local player = GetOwningPlayer(unit)
local flag = IsUnitSelected(unit, player) or IsUnitSelected(targ, player)
UnitAddAbility(targ, FourCC("Avul"))
UnitAddAbility(unit, FourCC("Avul"))
PauseUnit(targ, true)
PauseUnit(unit, true)
ShowUnit(targ, false)
ShowUnit(unit, false)
local golem = CreateUnit(DummyUtils.PLAYER, tb.custom.GOLEM_ID, cx, cy, GetUnitFacing(targ))
SetUnitUseFood(golem, false)
SetUnitOwner(golem, player, true)
UnitApplyTimedLife(golem, FourCC('BTLF'), tb.custom.HARBINGER_LIFETIME)
SetWidgetLife(golem, GetWidgetLife(unit) + GetWidgetLife(targ))
if (GetLocalPlayer() == player) and flag then
SelectUnit(golem, true)
end
ztb[golem] = ztb.request()
ztb[golem].golem = targ
ztb[golem].druid = unit
end
function tb.merge_target(unit, targ)
local move = mtb.request()
move.unit = unit
move.targ = targ
move.fx = AddSpecialEffectTarget(tb.custom.MOTION_MODEL, unit, "origin")
move.ment = UnitMovement(unit)
move.ment:set_data(move)
move.ment:config_move(mtb.on_unit_move)
move.ment:config_stop(mtb.on_unit_stop)
move.ment.checkpathing = false
move.ment:launch()
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_CAST, tb.custom.ABILITY_ID, function(unit)
local targ = GetSpellTargetUnit()
if not tb.filter_golem(targ, unit) then
GameError(GetOwningPlayer(unit), "Cannot embrace that target.")
PauseUnit(unit, true)
IssueImmediateOrderById(unit, 851972)
PauseUnit(unit, false)
return
end
tb.merge_target(unit, targ)
end)
local function on_deactivation(unit)
if not ztb[unit] then return;
end
tb.eject(ztb[unit].druid, unit)
tb.discard(ztb[unit].golem)
ztb.restore(ztb[unit])
ztb[unit].druid = nil
ztb[unit].golem = nil
ztb[unit] = nil
end
UnitState.register("DEATH_EVENT", on_deactivation)
UnitDex.register("LEAVE_EVENT", on_deactivation)
end
do
local tb = protected_table()
tb.tables = AllocTableEx(10)
tb.knocks = {}
tb.custom = {
ABILITY_ID = FourCC("A01L"),
ANIM_DELAY = 0.90, -- This is to compensate for cast point
-- adjust as needed.
KNOCKUP_VELOC = 600,
KNOCKUP_DUR = 0.80,
DASH_SPEED = 1000.,
DASH_ENUM_RADIUS = 150.,
DASH_DURATION = 3.0,
TAG = "alternate",
JUMP = "morph alternate",
SAND_EFFECT = "war3mapImported\\SandExplosion.mdx",
SAND_ATTACH_EFFECT = "war3mapImported\\GreySmoke.mdx",
KNOCKBACK_EFFECT = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl",
KNOCK_EFFECT_TICKS = UnitMovement.GRADIENT*4,
SAND_START_DELAY = 0.60,
SAND_END_DELAY = 0.50,
KNOCKBACK_DAMAGE = 10,
KNOCKBACK_HP_DMG = 0.05,
ATTACK_TYPE = ATTACK_TYPE_SIEGE,
DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL,
}
tb.knockup = CreateGroup()
tb.custom.DASH_DURATION = math.floor(tb.custom.DASH_DURATION*UnitMovement.INTERVAL + 0.5)
tb.custom.KNOCK_EFFECT_TICKS = math.floor(tb.custom.KNOCK_EFFECT_TICKS*UnitMovement.INTERVAL + 0.5)
tb.custom.DASH_SPEED = tb.custom.DASH_SPEED*UnitMovement.GRADIENT
tb.custom.KNOCKUP_VELOC = 2*tb.custom.KNOCKUP_VELOC/tb.custom.KNOCKUP_DUR
function tb.filter_targ(targ, unit)
return UnitAlive(targ) and not IsUnitType(targ, UNIT_TYPE_STRUCTURE)
and not IsUnitType(targ, UNIT_TYPE_FLYING)
and not IsUnitType(targ, UNIT_TYPE_MAGIC_IMMUNE)
and not IsUnitHidden(targ)
and not IsUnitInGroup(targ, tb.knockup)
and not tb[targ]
end
function tb.on_knockback_stop(move)
local targ = move.movement.unit
local self = tb.knocks[targ]
GroupRemoveUnit(tb.knockup, targ)
tb.tables.restore(self)
tb.knocks[targ] = nil
self.ticks = nil
end
function tb.on_knockback_move(knock)
local targ = knock.movement.unit
local self = tb.knocks[targ]
self.ticks = self.ticks + 1
if self.ticks >= tb.custom.KNOCK_EFFECT_TICKS then
DestroyEffect( AddSpecialEffectTarget(tb.custom.KNOCKBACK_EFFECT, targ, "origin"))
self.ticks = 0
end
end
function tb.on_stop(move)
local self = move:get_data()
local unit = self.unit
local _, tx, ty = IsTerrainWalkable(GetUnitX(unit), GetUnitY(unit))
SetUnitX(unit, tx)
SetUnitY(unit, ty)
UnitStartMovement(unit, false)
UnitRestoreAttack(unit, false)
AddUnitAnimationProperties(unit, tb.custom.TAG, false)
SetUnitAnimation(unit, tb.custom.JUMP)
QueueUnitAnimation(unit, "stand")
BlzSetUnitIntegerField(unit, UNIT_IF_TARGETED_AS, self.targbit)
doAfter(tb.custom.SAND_END_DELAY, tb.add_sand, tx, ty)
move:destroy()
if self.attach then
DestroyEffect(self.attach)
end
self.targbit = nil
self.ticks = nil
self.attach = nil
self.unit = nil
self.move = nil
self.tx = nil
self.ty = nil
tb[unit] = nil
tb.tables.restore(self)
end
function tb.on_movement(move)
local self = move:get_data()
local unit = self.unit
local cx, cy = GetUnitX(unit), GetUnitY(unit)
for targ in EnumUnitsInRange(cx, cy, tb.custom.DASH_ENUM_RADIUS) do
if (targ ~= unit) and tb.filter_targ(targ, unit) then
local dmg = tb.custom.KNOCKBACK_DAMAGE + GetUnitMaxHP(targ)*tb.custom.KNOCKBACK_HP_DMG
GroupAddUnit(tb.knockup, targ)
UnitDamageTarget(unit, targ, dmg, false, false,
tb.custom.ATTACK_TYPE, tb.custom.DAMAGE_TYPE, nil)
local self = tb.tables.request()
local knockback = Knockback(targ, tb.custom.KNOCKUP_DUR, true)
tb.knocks[targ] = self
self.ticks = 0
DestroyEffect( AddSpecialEffectTarget(tb.custom.KNOCKBACK_EFFECT, targ, "origin"))
knockback:set_velocity(0, tb.custom.KNOCKUP_VELOC)
knockback:set_theta(0)
knockback:config_move(tb.on_knockback_move)
knockback:config_stop(tb.on_knockback_stop)
knockback:launch()
end
end
local dist = (self.tx-cx)*(self.tx-cx) + (self.ty-cy)*(self.ty-cy)
self.ticks = self.ticks - 1
if self.ticks <= 0 or (dist < tb.custom.DASH_SPEED*tb.custom.DASH_SPEED) then
move:stop()
return Vector2D.ORIGIN, 0
end
local theta = math.atan(self.ty - cy, self.tx - cx)
local dx, dy = tb.custom.DASH_SPEED*math.cos(theta),
tb.custom.DASH_SPEED*math.sin(theta)
return Vector2D(dx, dy), 0
end
function tb.add_sand(tx, ty)
DestroyEffect( AddSpecialEffect(tb.custom.SAND_EFFECT, tx, ty))
end
function tb.add_dash(unit, tx, ty)
if tb[unit] then return;
end
tb[unit] = tb.tables.request()
tb[unit].move = UnitMovement(unit, 5)
tb[unit].unit = unit
tb[unit].tx = tx
tb[unit].ty = ty
tb[unit].ticks = tb.custom.DASH_DURATION
tb[unit].attach = AddSpecialEffect(tb.custom.SAND_ATTACH_EFFECT, GetUnitX(unit), GetUnitY(unit))
BlzSetSpecialEffectScale(tb[unit].attach, 2.50)
AttachEffect(tb[unit].attach, unit, 10)
tb[unit].targbit = BlzGetUnitIntegerField(unit, UNIT_IF_TARGETED_AS)
BlzSetUnitIntegerField(unit, UNIT_IF_TARGETED_AS, GetHandleId(TARGET_FLAG_NONE))
UnitStopMovement(unit, true)
UnitSuspendAttack(unit, true)
AddUnitAnimationProperties(unit, tb.custom.TAG, true)
tb[unit].move.checkpathing = false
tb[unit].move:set_data(tb[unit])
tb[unit].move:config_move(tb.on_movement)
tb[unit].move:config_stop(tb.on_stop)
tb[unit].move:launch()
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function(unit)
doAfter(tb.custom.SAND_START_DELAY, tb.add_sand, GetUnitX(unit), GetUnitY(unit))
doAfter(tb.custom.ANIM_DELAY, tb.add_dash, unit, GetSpellTargetX(), GetSpellTargetY())
end)
end
Druidism.Heroes = {}
Druidism.Heroes.Archdruid = {}
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A00E"),
ALTERNATE_ID = FourCC("A00F"),
PASSIVE_ID = FourCC("A011"),
PASSIVE_TRIG_ID = FourCC("A012"),
TREE_ID = FourCC("ATtr"),
TREE_SPAWN_EFFECT = "Objects\\Spawnmodels\\NightElf\\EntBirthTarget\\EntBirthTarget.mdl",
TREE_DURATION = {5, 5, 5},
TREE_REMOVE_DELAY = 5.00,
}
tb.switch = {}
Initializer("SYSTEM", function()
local levels = tonumber(ObjectReader.read(tb.custom.ABILITY_ID, "levels"))
tb.custom.AREA_OF_EFFECT = {}
for i = 1, levels do
tb.custom.AREA_OF_EFFECT[i] = tonumber(ObjectReader.read(tb.custom.ABILITY_ID, "Area" .. tostring(i), ",."))
end
end)
function tb.coords_from_arc(radius, theta, cx, cy)
radius = math.max(radius - 64, 0)
local tx, ty = cx + radius*math.cos(theta), cy + radius*math.sin(theta)
tx, ty = math.floor(tx/64 + 0.5)*64, math.floor(ty/64 + 0.5)*64
return tx, ty
end
function tb.kill_trees(dest_grp)
for i = 1, #dest_grp do
if IsDestructableAlive(dest_grp[i]) then
KillDestructable(dest_grp[i])
end
end
end
function tb.remove_trees(dest_grp)
for i = 1, #dest_grp do
RemoveDestructable(dest_grp[i])
end
end
function tb.create_trees(level, cx, cy)
local t = {
aoe = tb.custom.AREA_OF_EFFECT[level],
phi = 0.,
base = math.pi/2,
dest_grp = {}
}
t.arc = 128/t.aoe
while t.phi < 2*math.pi do
local theta = t.phi + t.base
local tx, ty = tb.coords_from_arc(t.aoe, theta, cx, cy)
local dest = CreateDestructable(tb.custom.TREE_ID, tx, ty, 270, 0.8 + math.random()*0.4, 0)
SetDestructableAnimation(dest, "birth")
QueueDestructableAnimation(dets, "stand")
DestroyEffect( AddSpecialEffect(tb.custom.TREE_SPAWN_EFFECT, tx, ty))
t.dest_grp[#t.dest_grp + 1] = dest
t.phi = t.phi + t.arc
end
doAfter(tb.custom.TREE_DURATION[level], tb.kill_trees, t.dest_grp)
doAfter(tb.custom.TREE_DURATION[level] + tb.custom.TREE_REMOVE_DELAY,
tb.remove_trees, t.dest_grp)
end
function tb.activate_second_phase(unit)
BlzUnitDisableAbility(unit, tb.custom.ABILITY_ID, true, true)
BlzUnitDisableAbility(unit, tb.custom.ALTERNATE_ID, false, false)
BlzUnitHideAbility(unit, tb.custom.PASSIVE_ID, false)
UnitAddAbility(unit, tb.custom.PASSIVE_TRIG_ID)
doAfter(0.01, UnitRemoveAbility, unit, tb.custom.PASSIVE_TRIG_ID)
end
function tb.deactivate_second_phase(unit)
if not tb.switch[unit] then return;
end
tb.switch[unit] = nil
BlzUnitDisableAbility(unit, tb.custom.ALTERNATE_ID, true, true)
BlzUnitHideAbility(unit, tb.custom.PASSIVE_ID, true)
BlzUnitDisableAbility(unit, tb.custom.ABILITY_ID, false, false)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local level = GetUnitAbilityLevel(unit, tb.custom.ABILITY_ID)
tb.switch[unit] = true
BlzSetUnitAbilityCooldown(unit, tb.custom.PASSIVE_ID, 0, tb.custom.TREE_DURATION[level])
doAfter(0.00, tb.activate_second_phase, unit)
doAfter(tb.custom.TREE_DURATION[level], tb.deactivate_second_phase, unit)
tb.create_trees(level, GetSpellTargetX(), GetSpellTargetY())
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ALTERNATE_ID, function()
local unit = GetTriggerUnit()
doAfter(0.00, tb.deactivate_second_phase, unit)
end)
SpellEvent.register_spell(EVENT_PLAYER_HERO_SKILL, tb.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
if UnitAddAbility(unit, tb.custom.ALTERNATE_ID) then
UnitAddAbility(unit, tb.custom.PASSIVE_ID)
UnitMakeAbilityPermanent(unit, true, tb.custom.ALTERNATE_ID)
UnitMakeAbilityPermanent(unit, true, tb.custom.PASSIVE_ID)
BlzUnitDisableAbility(unit, tb.custom.ALTERNATE_ID, true, true)
BlzUnitHideAbility(unit, tb.custom.PASSIVE_ID, true)
end
SetUnitAbilityLevel(unit, tb.custom.ALTERNATE_ID, GetLearnedSkillLevel())
end)
end
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A00G"),
SPAWN_ABIL_ID = FourCC("A00H"),
EXPLODE_ID = FourCC("A00I"),
ROOT_ABIL_ID = FourCC("A00J"),
ROOT_BUFF_ID = FourCC("BEer"),
ACTIVE_ORDER = "selfdestructon",
SPAWN_ORDER = "ward",
ROOT_ORDER = "entanglingroots",
EFFECT_MODEL = "war3mapImported\\Green Light.mdx",
SPAWN_RANGE = 350,
SPAWN_COUNT = {2, 3, 4},
SPAWN_DURATION = {5, 6, 6},
PAUSE_DURATION = 0.75,
ATTACK_TYPE = ATTACK_TYPE_NORMAL,
DAMAGE_TYPE = DAMAGE_TYPE_PLANT,
}
tb.owner = {spawn={}}
tb.roots = {}
Druidism.Heroes.Archdruid.EntangledTrees = setmetatable({}, tb)
function tb:create(owner)
local o = {}
o.caster = owner
o.dummy = DummyUtils.request(GetOwningPlayer(owner), GetUnitX(owner), GetUnitY(owner))
o.decaying = false
o.group = CreateGroup()
o.spawn_grp = CreateGroup()
o.level = 0
o.count = 0
tb.owner[o.dummy] = o
UnitStopMovement(o.dummy)
UnitAddAbility(o.dummy, tb.custom.SPAWN_ABIL_ID)
UnitAddAbility(o.dummy, tb.custom.ROOT_ABIL_ID)
setmetatable(o, tb)
return o
end
function tb:destroy()
if self.decaying == nil then return;
end
tb.owner[self.dummy] = nil
UnitStartMovement(self.dummy)
UnitRemoveAbility(self.dummy, tb.custom.SPAWN_ABIL_ID)
UnitRemoveAbility(self.dummy, tb.custom.ROOT_ABIL_ID)
DummyUtils.recycle(self.dummy)
DestroyGroup(self.group)
DestroyGroup(self.spawn_grp)
self.decaying = nil
self.dummy = nil
end
function tb:check_for_destroy()
if not self.decaying then return;
elseif (BlzGroupGetSize(self.group) ~= 0) then return;
elseif (BlzGroupGetSize(self.spawn_grp) ~= 0) then return;
end
self:destroy()
end
function tb:root(target)
SetUnitX(self.dummy, GetUnitX(target)); SetUnitY(self.dummy, GetUnitY(target));
IssueTargetOrder(self.dummy, tb.custom.ROOT_ORDER, target)
GroupAddUnit(self.group, target)
if not tb.roots[target] then
local buffer = BuffWatcher.watch(target, tb.custom.ROOT_BUFF_ID)
tb.roots[target] = {buff = buffer, list = SimpleList()}
buffer:on_buff_remove(function()
for self in tb.roots[target].list:iterator() do
GroupRemoveUnit(self.group, target)
self:check_for_destroy()
end
tb.roots[target].list:destroy()
tb.roots[target] = nil
end)
end
if tb.roots[target].list:is_in(self) then return;
end
tb.roots[target].list:insert(self)
end
function tb:start_decay()
if self.decaying then return;
end
self.decaying = true
doAfter(0.00, tb.check_for_destroy, self)
end
function tb.force_spawn_tentacle()
local timer = GetExpiredTimer()
local self = GetTimerData(timer)
if self.count < tb.custom.SPAWN_COUNT[self.level] then
self.count = self.count + 1
local dist = math.random()*tb.custom.SPAWN_RANGE
local theta = math.random()*2*math.pi
local tx, ty = GetUnitX(self.caster) + dist*math.cos(theta),
GetUnitY(self.caster) + dist*math.sin(theta)
SetUnitX(self.dummy, tx); SetUnitY(self.dummy, ty);
IssuePointOrder(self.dummy, tb.custom.SPAWN_ORDER, tx, ty)
return
end
PauseTimer(timer)
DestroyTimer(timer)
end
function tb:spawn_tentacles(level)
self.level = level
local timer = CreateTimer()
local interval = tb.custom.SPAWN_DURATION[self.level]/tb.custom.SPAWN_COUNT[self.level]
SetTimerData(timer, self)
TimerStart(timer, interval, true, tb.force_spawn_tentacle)
end
function tb:spawn_effect()
local fx = AddSpecialEffect(tb.custom.EFFECT_MODEL, GetUnitX(self.caster), GetUnitY(self.caster))
BlzSetSpecialEffectScale(fx, 1.95)
DestroyEffect(fx, 1.50)
end
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local self = tb:create(GetTriggerUnit())
self:spawn_tentacles(GetUnitAbilityLevel(self.caster, tb.custom.ABILITY_ID))
self:spawn_effect()
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SUMMON, function()
local dummy = GetSummoningUnit()
if not tb.owner[dummy] then return;
end
local tree = GetSummonedUnit()
tb.owner.spawn[tree] = tb.owner[dummy]
GroupAddUnit(tb.owner[dummy].spawn_grp, tree)
SetUnitAbilityLevel(tree, tb.custom.EXPLODE_ID, tb.owner[dummy].level)
IssueImmediateOrder(tree, tb.custom.ACTIVE_ORDER)
PauseUnit(tree, true)
doAfter(tb.custom.PAUSE_DURATION, PauseUnit, tree, false)
end)
DamageEvent.register_modifier("MODIFIER_EVENT_DELTA", function(targ, src)
if not tb.owner[src] then return;
end
local dmg = DamageEvent.current.original_dmg
local event, func = EventListener.get_event(), EventListener.get_cur_function()
event:disable(func)
DamageEvent.current.dmg = 0
UnitDamageTarget(tb.owner[src].caster, targ, dmg, false, true,
tb.custom.ATTACK_TYPE, tb.custom.DAMAGE_TYPE, nil)
event:enable(func)
end)
UnitDex.register("LEAVE_EVENT", function(unit)
if tb.owner.spawn[unit] then
GroupRemoveUnit(tb.owner.spawn[unit].spawn_grp, unit)
end
tb.owner[unit] = nil
tb.owner.spawn[unit] = nil
end)
end
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A00K"),
BLOCK_RATIO = {0.5, 1.0, 2.0},
CHANCE = {0.1, 0.15, 0.2},
BLOCK_EFFECT = "war3mapImported\\Radiance Nature.mdx",
}
tb.entry = {}
function tb:create(whichunit)
local o = {}
o.unit = whichunit
o.chance = FixedChance:create()
o.bar = AddSpecialEffect(ProgressBar.MODEL, 0, 0)
o.refresh = false
o.level = 0
setmetatable(o, tb)
return o
end
function tb:reset_refresh()
self.refresh = false
end
function tb:do_refresh()
self.refresh = true
doAfter(0.00, tb.reset_refresh, self)
end
DamageEvent.register_damage(function(targ)
if not tb.entry[targ] then return;
end
local self = tb.entry[targ]
if self.refresh then return;
elseif DamageEvent.current.dmg == 0.00 then return;
end
self:do_refresh()
if self.chance:test() then
DamageEvent.current.block = DamageEvent.current.block +
DamageEvent.current.dmg*tb.custom.BLOCK_RATIO[self.level]
DestroyEffect(AddSpecialEffectTarget(tb.custom.BLOCK_EFFECT, self.unit, "chest"), 0.5)
end
BlzSetSpecialEffectTime(self.bar, self.chance:cur_progress())
end)
SpellEvent.register_spell(EVENT_PLAYER_HERO_SKILL, tb.custom.ABILITY_ID, function()
local unit, level = GetTriggerUnit(), GetLearnedSkillLevel()
if not tb.entry[unit] then
tb.entry[unit] = tb:create(unit)
AttachEffect(tb.entry[unit].bar, unit, 250)
BlzSetSpecialEffectColorByPlayer(tb.entry[unit].bar, Player(18))
BlzSetSpecialEffectTimeScale(tb.entry[unit].bar, 0.00)
BlzSetSpecialEffectMatrixScale(tb.entry[unit].bar, 2, 1, 1)
end
tb.entry[unit].chance:set_chance(tb.custom.CHANCE[level])
tb.entry[unit].level = level
end)
UnitState.register("DEATH_EVENT", function()
local unit = UnitState.eventUnit
if not tb.entry[unit] then return;
end
ShowEffect(tb.entry[unit].bar, false)
end)
UnitState.register("RESURRECT_EVENT", function()
local unit = UnitState.eventUnit
if not tb.entry[unit] then return;
end
ShowEffect(tb.entry[unit].bar, true)
end)
end
do
local tb = protected_table()
tb.custom = {
ABILITY_ID = FourCC("A00L"),
STAMPEDE_ID = FourCC("A00M"),
STAMPEDE_ORDER = "stampede",
RECYCLE_DELAY = 10.00,
ATTACK_TYPE = ATTACK_TYPE_NORMAL,
DAMAGE_TYPE = DAMAGE_TYPE_DIVINE,
ICON_NAME = "|cff40ff40Summoning Ancestral Spirits|r",
ICON1_NAME = "|cff40ff40Wrath of the Ancestral Spirits|r"
}
tb.vector = {}
tb.owner = {}
tb.notif = {}
Initializer("USER", function()
tb.custom.ICON_PATH = ObjectReader.extract_icon(tb.custom.ABILITY_ID)
end)
function tb.create_buff(whichunit)
return CustomBuffSystem.add(whichunit, tb.custom.ICON_PATH, tb.custom.ICON_NAME,
GetHeroProperName(whichunit) .. " is summoning the ancestral spirits.")
end
function tb.recycle_dummy(dummy)
tb.owner[dummy] = nil
DummyUtils.recycle(dummy)
end
DamageEvent.register_modifier("MODIFIER_EVENT_ALPHA", function(targ, src)
if not tb.owner[src] then return;
elseif GetUnitTypeId(tb.owner[src]) == 0 then return;
end
local event, func = EventListener.get_event(), EventListener.get_cur_function()
event:disable(func)
UnitDamageTarget(tb.owner[src], targ, DamageEvent.current.dmg, false, false,
tb.custom.ATTACK_TYPE, tb.custom.DAMAGE_TYPE)
DamageEvent.current.dmg = 0.
event:enable(func)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_EFFECT, tb.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
tb.vector[unit] = {
x = GetSpellTargetX(),
y = GetSpellTargetY(),
}
tb.vector[unit].buff = tb.create_buff(unit)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_FINISH, tb.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
local dummy = DummyUtils.request(GetOwningPlayer(unit), GetUnitX(unit), GetUnitY(unit))
tb.owner[dummy] = unit
tb.notif[dummy] = CustomBuffSystem.add(unit, tb.custom.ICON_PATH, tb.custom.ICON1_NAME,
"The Ancestral Spirits are now rampaging across the area.")
UnitAddAbility(dummy, tb.custom.STAMPEDE_ID)
UnitStopMovement(dummy, true)
IssuePointOrder(dummy, tb.custom.STAMPEDE_ORDER, tb.vector[unit].x, tb.vector[unit].y)
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, tb.custom.ABILITY_ID, function()
local unit = GetTriggerUnit()
tb.vector[unit].buff:destroy()
tb.vector[unit] = nil
end)
SpellEvent.register_spell(EVENT_PLAYER_UNIT_SPELL_ENDCAST, tb.custom.STAMPEDE_ID, function()
local dummy = GetTriggerUnit()
tb.notif[dummy]:destroy()
tb.notif[dummy] = nil
doAfter(tb.custom.RECYCLE_DELAY, tb.recycle_dummy, dummy)
end)
end